diff --git a/src/components/nodes/PromptConstructorNode.tsx b/src/components/nodes/PromptConstructorNode.tsx index eaf7676d..3ee9b4f9 100644 --- a/src/components/nodes/PromptConstructorNode.tsx +++ b/src/components/nodes/PromptConstructorNode.tsx @@ -6,6 +6,7 @@ import { BaseNode } from "./BaseNode"; import { usePromptAutocomplete } from "@/hooks/usePromptAutocomplete"; import { useWorkflowStore } from "@/store/workflowStore"; import { PromptConstructorNodeData, PromptNodeData, LLMGenerateNodeData, AvailableVariable } from "@/types"; +import { resolveTextSourcesThroughRouters } from "@/store/utils/connectedInputs"; import { parseVarTags } from "@/utils/parseVarTags"; type PromptConstructorNodeType = Node; @@ -31,10 +32,11 @@ export function PromptConstructorNode({ id, data, selected }: NodeProps tags) const availableVariables = useMemo((): AvailableVariable[] => { - const connectedTextNodes = edges + const directTextNodes = edges .filter((e) => e.target === id && e.targetHandle === "text") .map((e) => nodes.find((n) => n.id === e.source)) .filter((n): n is typeof nodes[0] => n !== undefined); + const connectedTextNodes = resolveTextSourcesThroughRouters(directTextNodes, nodes, edges); const vars: AvailableVariable[] = []; const usedNames = new Set(); diff --git a/src/store/execution/simpleNodeExecutors.ts b/src/store/execution/simpleNodeExecutors.ts index 8adf593a..137c2e72 100644 --- a/src/store/execution/simpleNodeExecutors.ts +++ b/src/store/execution/simpleNodeExecutors.ts @@ -18,6 +18,7 @@ import type { WorkflowNode, } from "@/types"; import type { NodeExecutionContext } from "./types"; +import { resolveTextSourcesThroughRouters } from "@/store/utils/connectedInputs"; import { parseTextToArray } from "@/utils/arrayParser"; import { parseVarTags } from "@/utils/parseVarTags"; @@ -109,11 +110,12 @@ export async function executePromptConstructor(ctx: NodeExecutionContext): Promi const edges = getEdges(); const nodes = getNodes(); - // Find all connected text nodes - const connectedTextNodes = edges + // Find all connected text nodes (resolving through routers) + const directTextNodes = edges .filter((e) => e.target === node.id && e.targetHandle === "text") .map((e) => nodes.find((n) => n.id === e.source)) .filter((n): n is WorkflowNode => n !== undefined); + const connectedTextNodes = resolveTextSourcesThroughRouters(directTextNodes, nodes, edges); // Build variable map: named variables from Prompt nodes take precedence const variableMap: Record = {}; diff --git a/src/store/utils/connectedInputs.ts b/src/store/utils/connectedInputs.ts index a848a380..2c8b817f 100644 --- a/src/store/utils/connectedInputs.ts +++ b/src/store/utils/connectedInputs.ts @@ -119,6 +119,41 @@ function getSourceOutput( return { type: "image", value: null }; } +/** + * Resolves text source nodes through router (passthrough) nodes. + * Given a list of directly-connected source nodes, expands any router nodes + * by recursively following their upstream text connections to find the actual + * text-producing source nodes. + */ +export function resolveTextSourcesThroughRouters( + sourceNodes: WorkflowNode[], + allNodes: WorkflowNode[], + edges: { source: string; target: string; targetHandle?: string | null }[], + visited?: Set +): WorkflowNode[] { + const seen = visited ?? new Set(); + const resolved: WorkflowNode[] = []; + + for (const node of sourceNodes) { + if (seen.has(node.id)) continue; + seen.add(node.id); + + if (node.type === "router") { + const upstreamNodes = edges + .filter((e) => e.target === node.id && e.targetHandle === "text") + .map((e) => allNodes.find((n) => n.id === e.source)) + .filter((n): n is WorkflowNode => n !== undefined); + resolved.push( + ...resolveTextSourcesThroughRouters(upstreamNodes, allNodes, edges, seen) + ); + } else { + resolved.push(node); + } + } + + return resolved; +} + /** * Get all connected inputs for a node. * Pure function version of workflowStore.getConnectedInputs.