Browse Source

fix: resolve prompt variables through router nodes for PromptConstructor

Variables from upstream Prompt nodes weren't discovered when a Router sat
between the Prompt and PromptConstructor. Added resolveTextSourcesThroughRouters()
helper that recursively traverses router nodes to find actual text sources,
used in both the component and executor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
61af53b2b8
  1. 4
      src/components/nodes/PromptConstructorNode.tsx
  2. 6
      src/store/execution/simpleNodeExecutors.ts
  3. 35
      src/store/utils/connectedInputs.ts

4
src/components/nodes/PromptConstructorNode.tsx

@ -6,6 +6,7 @@ import { BaseNode } from "./BaseNode";
import { usePromptAutocomplete } from "@/hooks/usePromptAutocomplete"; import { usePromptAutocomplete } from "@/hooks/usePromptAutocomplete";
import { useWorkflowStore } from "@/store/workflowStore"; import { useWorkflowStore } from "@/store/workflowStore";
import { PromptConstructorNodeData, PromptNodeData, LLMGenerateNodeData, AvailableVariable } from "@/types"; import { PromptConstructorNodeData, PromptNodeData, LLMGenerateNodeData, AvailableVariable } from "@/types";
import { resolveTextSourcesThroughRouters } from "@/store/utils/connectedInputs";
import { parseVarTags } from "@/utils/parseVarTags"; import { parseVarTags } from "@/utils/parseVarTags";
type PromptConstructorNodeType = Node<PromptConstructorNodeData, "promptConstructor">; type PromptConstructorNodeType = Node<PromptConstructorNodeData, "promptConstructor">;
@ -31,10 +32,11 @@ export function PromptConstructorNode({ id, data, selected }: NodeProps<PromptCo
// Get available variables from connected prompt nodes (named variables + inline <var> tags) // Get available variables from connected prompt nodes (named variables + inline <var> tags)
const availableVariables = useMemo((): AvailableVariable[] => { const availableVariables = useMemo((): AvailableVariable[] => {
const connectedTextNodes = edges const directTextNodes = edges
.filter((e) => e.target === id && e.targetHandle === "text") .filter((e) => e.target === id && e.targetHandle === "text")
.map((e) => nodes.find((n) => n.id === e.source)) .map((e) => nodes.find((n) => n.id === e.source))
.filter((n): n is typeof nodes[0] => n !== undefined); .filter((n): n is typeof nodes[0] => n !== undefined);
const connectedTextNodes = resolveTextSourcesThroughRouters(directTextNodes, nodes, edges);
const vars: AvailableVariable[] = []; const vars: AvailableVariable[] = [];
const usedNames = new Set<string>(); const usedNames = new Set<string>();

6
src/store/execution/simpleNodeExecutors.ts

@ -18,6 +18,7 @@ import type {
WorkflowNode, WorkflowNode,
} from "@/types"; } from "@/types";
import type { NodeExecutionContext } from "./types"; import type { NodeExecutionContext } from "./types";
import { resolveTextSourcesThroughRouters } from "@/store/utils/connectedInputs";
import { parseTextToArray } from "@/utils/arrayParser"; import { parseTextToArray } from "@/utils/arrayParser";
import { parseVarTags } from "@/utils/parseVarTags"; import { parseVarTags } from "@/utils/parseVarTags";
@ -109,11 +110,12 @@ export async function executePromptConstructor(ctx: NodeExecutionContext): Promi
const edges = getEdges(); const edges = getEdges();
const nodes = getNodes(); const nodes = getNodes();
// Find all connected text nodes // Find all connected text nodes (resolving through routers)
const connectedTextNodes = edges const directTextNodes = edges
.filter((e) => e.target === node.id && e.targetHandle === "text") .filter((e) => e.target === node.id && e.targetHandle === "text")
.map((e) => nodes.find((n) => n.id === e.source)) .map((e) => nodes.find((n) => n.id === e.source))
.filter((n): n is WorkflowNode => n !== undefined); .filter((n): n is WorkflowNode => n !== undefined);
const connectedTextNodes = resolveTextSourcesThroughRouters(directTextNodes, nodes, edges);
// Build variable map: named variables from Prompt nodes take precedence // Build variable map: named variables from Prompt nodes take precedence
const variableMap: Record<string, string> = {}; const variableMap: Record<string, string> = {};

35
src/store/utils/connectedInputs.ts

@ -119,6 +119,41 @@ function getSourceOutput(
return { type: "image", value: null }; 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<string>
): WorkflowNode[] {
const seen = visited ?? new Set<string>();
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. * Get all connected inputs for a node.
* Pure function version of workflowStore.getConnectedInputs. * Pure function version of workflowStore.getConnectedInputs.

Loading…
Cancel
Save