diff --git a/src/components/nodes/index.ts b/src/components/nodes/index.ts index a03f13d6..27f0d61c 100644 --- a/src/components/nodes/index.ts +++ b/src/components/nodes/index.ts @@ -19,4 +19,5 @@ export { VideoTrimNode } from "./VideoTrimNode"; export { VideoFrameGrabNode } from "./VideoFrameGrabNode"; export { RouterNode } from "./RouterNode"; export { SwitchNode } from "./SwitchNode"; +export { ConditionalSwitchNode } from "./ConditionalSwitchNode"; export { GroupNode } from "./GroupNode"; diff --git a/src/lib/quickstart/validation.ts b/src/lib/quickstart/validation.ts index f303a63d..2e4f01ef 100644 --- a/src/lib/quickstart/validation.ts +++ b/src/lib/quickstart/validation.ts @@ -33,6 +33,7 @@ const VALID_NODE_TYPES: NodeType[] = [ "videoFrameGrab", "router", "switch", + "conditionalSwitch", "glbViewer", ]; @@ -61,6 +62,7 @@ const DEFAULT_DIMENSIONS: Record = videoFrameGrab: { width: 320, height: 320 }, router: { width: 200, height: 80 }, switch: { width: 220, height: 120 }, + conditionalSwitch: { width: 260, height: 180 }, glbViewer: { width: 360, height: 380 }, }; diff --git a/src/store/execution/index.ts b/src/store/execution/index.ts index 5e5776e2..3f47465a 100644 --- a/src/store/execution/index.ts +++ b/src/store/execution/index.ts @@ -19,6 +19,7 @@ export { executeGlbViewer, executeRouter, executeSwitch, + executeConditionalSwitch, } from "./simpleNodeExecutors"; export { executeNanoBanana } from "./nanoBananaExecutor"; diff --git a/src/store/execution/simpleNodeExecutors.ts b/src/store/execution/simpleNodeExecutors.ts index 3ff55ee6..ccb1f33c 100644 --- a/src/store/execution/simpleNodeExecutors.ts +++ b/src/store/execution/simpleNodeExecutors.ts @@ -337,6 +337,17 @@ export async function executeSwitch(ctx: NodeExecutionContext): Promise { ctx.updateNodeData(ctx.node.id, { status: "complete" }); } +/** + * ConditionalSwitch node: pure passthrough with text-based rule matching. + */ +export async function executeConditionalSwitch(ctx: NodeExecutionContext): Promise { + // ConditionalSwitch is pure passthrough — actual text matching happens during connectedInputs traversal. + // Brief status flash to show execution occurred. + ctx.updateNodeData(ctx.node.id, { status: "loading" }); + await new Promise(resolve => setTimeout(resolve, 50)); + ctx.updateNodeData(ctx.node.id, { status: "complete" }); +} + /** * GLB Viewer node: receives 3D model URL from upstream, fetches and loads it. */ diff --git a/src/store/utils/nodeDefaults.ts b/src/store/utils/nodeDefaults.ts index 6fbc6da6..4cd75f1e 100644 --- a/src/store/utils/nodeDefaults.ts +++ b/src/store/utils/nodeDefaults.ts @@ -20,6 +20,7 @@ import { VideoFrameGrabNodeData, RouterNodeData, SwitchNodeData, + ConditionalSwitchNodeData, GLBViewerNodeData, WorkflowNodeData, GroupColor, @@ -53,6 +54,7 @@ export const defaultNodeDimensions: Record { { id: Math.random().toString(36).slice(2, 9), name: "Output 1", enabled: true } ] } as SwitchNodeData; + case "conditionalSwitch": + return { + customTitle: null, + comment: null, + incomingText: null, + rules: [ + { + id: "rule-" + Math.random().toString(36).slice(2, 9), + value: "", + mode: "contains", + label: "Rule 1", + isMatched: false, + } + ] + } as ConditionalSwitchNodeData; case "glbViewer": return { glbUrl: null, diff --git a/src/types/nodes.ts b/src/types/nodes.ts index 5fb5bf95..ae980e70 100644 --- a/src/types/nodes.ts +++ b/src/types/nodes.ts @@ -43,6 +43,7 @@ export type NodeType = | "videoFrameGrab" | "router" | "switch" + | "conditionalSwitch" | "generate3d" | "glbViewer"; @@ -370,6 +371,30 @@ export interface SwitchNodeData extends BaseNodeData { }>; } +/** + * Match mode for conditional switch rules + */ +export type MatchMode = "exact" | "contains" | "starts-with" | "ends-with"; + +/** + * Conditional switch rule for text-based routing + */ +export interface ConditionalSwitchRule { + id: string; // Unique handle ID, prefixed with "rule-" to avoid collision with reserved "default" keyword + value: string; // Comma-separated match values + mode: MatchMode; // Match strategy + label: string; // User-editable display name + isMatched: boolean; // Computed match state +} + +/** + * Conditional Switch node - text-based routing with multi-mode matching + */ +export interface ConditionalSwitchNodeData extends BaseNodeData { + incomingText: string | null; // Upstream text for evaluation and display + rules: ConditionalSwitchRule[]; // User-defined rules +} + /** * Split Grid node - splits image into grid cells for parallel processing */ @@ -430,6 +455,7 @@ export type WorkflowNodeData = | VideoFrameGrabNodeData | RouterNodeData | SwitchNodeData + | ConditionalSwitchNodeData | GLBViewerNodeData; /**