Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { NodeDataType } from "@/types/flow";
import { OutputParameter } from ".";
import {
getDisplayOutput,
separateOutputsByGroup,
shouldShowAllOutputs,
} from "./nodeOutputUtils";

export default function NodeOutputs({
outputs,
Expand All @@ -9,10 +14,8 @@ export default function NodeOutputs({
selected,
showNode,
isToolMode,
showHiddenOutputs,
selectedOutput,
handleSelectOutput,
hasExistingHiddenOutputs = false,
}: {
outputs: any;
keyPrefix: string;
Expand All @@ -21,28 +24,20 @@ export default function NodeOutputs({
selected: boolean;
showNode: boolean;
isToolMode: boolean;
showHiddenOutputs: boolean;
selectedOutput: any;
handleSelectOutput: any;
hasExistingHiddenOutputs?: boolean;
}) {
const hasLoopOutput = outputs.some((output) => output.allows_loop);
const hasGroupOutputs = outputs.some((output) => output.group_outputs);
const isConditionalRouter = data.type === "ConditionalRouter";
const hasHiddenOutputs = outputs.some((output) => output.hidden);
// Separate outputs based on group_outputs field
const { groupedOutputs, individualOutputs } = separateOutputsByGroup(
outputs,
) as { groupedOutputs: any[]; individualOutputs: any[] };

const shouldShowAllOutputs =
hasLoopOutput || hasGroupOutputs || isConditionalRouter || hasHiddenOutputs;

if (shouldShowAllOutputs) {
const outputsToRender =
keyPrefix === "hidden"
? outputs.filter((output) => output.hidden)
: outputs.filter((output) => !output.hidden);
const shouldShowAll = shouldShowAllOutputs(outputs, data);

if (shouldShowAll) {
return (
<>
{outputsToRender?.map((output, idx) => (
{outputs?.map((output: any, idx: number) => (
<OutputParameter
key={`${keyPrefix}-${output.name}-${idx}`}
output={output}
Expand All @@ -52,69 +47,75 @@ export default function NodeOutputs({
(out) => out.name === output.name,
) ?? idx
}
lastOutput={idx === outputsToRender.length - 1}
lastOutput={idx === outputs.length - 1}
data={data}
types={types}
selected={selected}
showNode={showNode}
isToolMode={isToolMode}
showHiddenOutputs={showHiddenOutputs}
handleSelectOutput={handleSelectOutput}
hidden={
keyPrefix === "hidden"
? showHiddenOutputs
? output.hidden
: true
: false
}
/>
))}
</>
);
}

const getDisplayOutput = () => {
const filteredOutputs =
keyPrefix === "hidden"
? outputs.filter((output) => output.hidden)
: outputs.filter((output) => !output.hidden);

const outputWithSelection = filteredOutputs.find(
(output) => output.name === selectedOutput?.name,
);

return outputWithSelection || filteredOutputs[0];
// Handle individual outputs (group_outputs: false)
const renderIndividualOutputs = () => {
return individualOutputs.map((output: any, idx: number) => (
<OutputParameter
key={`${keyPrefix}-individual-${output.name}-${idx}`}
output={output}
outputs={[output] as any} // Pass only this output to avoid dropdown behavior
idx={
data.node!.outputs?.findIndex(
(out: any) => out.name === output.name,
) ?? idx
}
lastOutput={
groupedOutputs.length === 0 && idx === individualOutputs.length - 1
}
data={data}
types={types}
selected={selected}
showNode={showNode}
isToolMode={isToolMode}
handleSelectOutput={handleSelectOutput}
/>
));
};

const displayOutput = getDisplayOutput();
// Handle grouped outputs (group_outputs: true) - show as dropdown
const renderGroupedOutputs = () => {
if (groupedOutputs.length === 0) return null;

if (!displayOutput) return null;
const displayOutput = getDisplayOutput(groupedOutputs, selectedOutput);

return (
<OutputParameter
key={`${keyPrefix}-grouped-${displayOutput.name}`}
output={displayOutput}
outputs={groupedOutputs as any}
idx={
data.node!.outputs?.findIndex(
(out) => out.name === displayOutput.name,
) ?? 0
}
lastOutput={true}
data={data}
types={types}
selected={selected}
handleSelectOutput={handleSelectOutput}
showNode={showNode}
isToolMode={isToolMode}
/>
);
};

return (
<OutputParameter
key={`${keyPrefix}-${displayOutput.name}`}
output={displayOutput}
outputs={outputs}
idx={
data.node!.outputs?.findIndex(
(out) => out.name === displayOutput.name,
) ?? 0
}
lastOutput={!hasExistingHiddenOutputs}
data={data}
types={types}
selected={selected}
handleSelectOutput={handleSelectOutput}
showNode={showNode}
isToolMode={isToolMode}
showHiddenOutputs={showHiddenOutputs}
hidden={
keyPrefix === "hidden"
? showHiddenOutputs
? displayOutput.hidden
: true
: false
}
/>
<>
{renderIndividualOutputs()}
{renderGroupedOutputs()}
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ export const OutputParameter = ({
types,
selected,
showNode,
showHiddenOutputs,
isToolMode,
hidden,
handleSelectOutput,
}) => {
const id = useMemo(
Expand All @@ -40,7 +38,6 @@ export const OutputParameter = ({

return (
<NodeOutputField
hidden={hidden}
index={idx}
lastOutput={lastOutput}
selected={selected}
Expand All @@ -58,7 +55,6 @@ export const OutputParameter = ({
handleSelectOutput={handleSelectOutput}
colorName={colorNames}
isToolMode={isToolMode}
showHiddenOutputs={showHiddenOutputs}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import type { NodeDataType } from "@/types/flow";
import { scapeJSONParse } from "../../../../utils/reactflowUtils";
import {
logFirstMessage,
logHasMessage,
logTypeIsError,
logTypeIsUnknown,
} from "../../../../utils/utils";

/**
* Determines if all outputs should be shown based on loop outputs and node type
*/
export const shouldShowAllOutputs = (
outputs: any[],
data: NodeDataType,
): boolean => {
const hasLoopOutput = outputs.some((output) => output.allows_loop);
const isConditionalRouter = data.type === "ConditionalRouter";
return hasLoopOutput || isConditionalRouter;
};

/**
* Separates outputs into grouped and individual outputs
*/
export const separateOutputsByGroup = (
outputs: any[],
): { groupedOutputs: any[]; individualOutputs: any[] } => {
const groupedOutputs = outputs.filter(
(output: any) => (output.group_outputs ?? false) === false,
);
const individualOutputs = outputs.filter(
(output: any) => (output.group_outputs ?? false) === true,
);
return { groupedOutputs, individualOutputs };
};

/**
* Gets the display output for grouped outputs
*/
export const getDisplayOutput = (
groupedOutputs: any[],
selectedOutput: any,
) => {
if (groupedOutputs.length === 0) return undefined;

const outputWithSelection = groupedOutputs.find(
(output) => output.name === selectedOutput?.name,
);
return outputWithSelection || groupedOutputs[0];
};

/**
* Determines the output status (preview, unknown, error)
*/
export const determineOutputStatus = (
flowPool: any,
flowPoolId: string,
internalOutputName: string,
) => {
const pool = flowPool[flowPoolId] ?? [];
const flowPoolNode = pool[pool.length - 1];

if (!flowPoolNode) {
return {
displayOutputPreview: false,
unknownOutput: false,
errorOutput: false,
};
}

const displayOutputPreview =
!!flowPool[flowPoolId] &&
logHasMessage(flowPoolNode?.data, internalOutputName);
const unknownOutput = logTypeIsUnknown(
flowPoolNode?.data,
internalOutputName,
);
const errorOutput = logTypeIsError(flowPoolNode?.data, internalOutputName);

return {
displayOutputPreview,
unknownOutput,
errorOutput,
};
};

/**
* Checks if all outputs are empty
*/
export const isOutputEmpty = (nodeData: any): boolean => {
if (!nodeData?.outputs) return true;

return Object.keys(nodeData.outputs).every(
(key) => nodeData.outputs[key]?.message?.length === 0,
);
};

/**
* Detects if there's a looping edge
*/
export const detectLooping = (edges: any[], sourceHandle: string): boolean => {
return edges.some((edge) => {
try {
const targetHandleObject = scapeJSONParse(edge.targetHandle || "{}");
return (
targetHandleObject.output_types && edge.sourceHandle === sourceHandle
);
} catch {
return false;
}
});
};

/**
* Determines if output shortcut is openable
*/
export const isOutputShortcutOpenable = ({
displayOutputPreview,
selected,
edges,
nodeData,
id,
flowPoolNode,
internalOutputName,
}: {
displayOutputPreview: boolean;
selected: boolean;
edges: any[];
nodeData: any;
id: string;
flowPoolNode: any;
internalOutputName: string;
}): boolean => {
if (!displayOutputPreview || !selected) return false;

const hasOutputs =
nodeData?.node?.outputs && nodeData.node.outputs.length > 0;
if (!hasOutputs) return false;

const sortedEdges = [...edges]
.filter((edge) => edge.source === nodeData.id)
.sort((a, b) => {
const indexA =
nodeData?.node?.outputs?.findIndex(
(output: any) => output.name === a.data?.sourceHandle?.name,
) ?? 0;
const indexB =
nodeData?.node?.outputs?.findIndex(
(output: any) => output.name === b.data?.sourceHandle?.name,
) ?? 0;
return indexA - indexB;
});

const isFirstOutput = sortedEdges[0]?.sourceHandle === id;
const hasNoEdges = !edges.some((edge) => edge.source === nodeData.id);
const isValidFirstMessage =
hasNoEdges && logFirstMessage(flowPoolNode?.data, internalOutputName);

return isFirstOutput || isValidFirstMessage;
};
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ function NodeOutputField({

useEffect(() => {
const outputHasGroupOutputsFalse =
data.node?.outputs?.[index]?.group_outputs === false;
(data.node?.outputs?.[index]?.group_outputs ?? false) === false;

if (disabledOutput && hidden && !outputHasGroupOutputsFalse) {
handleUpdateOutputHide(false);
Expand Down
Loading
Loading