diff --git a/src/components/admin/narrative-flow/FormatNode.tsx b/src/components/admin/narrative-flow/FormatNode.tsx
index 197e428..a193d2f 100644
--- a/src/components/admin/narrative-flow/FormatNode.tsx
+++ b/src/components/admin/narrative-flow/FormatNode.tsx
@@ -3,13 +3,30 @@
import { type NodeProps } from "@xyflow/react";
import { AlignLeftIcon } from "lucide-react";
import { NodeHandles } from "./NodeHandles";
+import { NODE_SOCKETS, type SocketDef } from "./types";
+
+function parseTemplateArgs(template: string): SocketDef[] {
+ const matches = template.match(/\{(\d+)\}/g);
+ if (!matches) return [];
+ const unique = [...new Set(matches)].sort();
+ return unique.map((m) => ({
+ id: `arg-${m.slice(1, -1)}`,
+ type: "any" as const,
+ label: m,
+ }));
+}
export function FormatNode({ data }: NodeProps) {
const d = data as { template?: string };
+ const template = d.template ?? "{0}";
+ const dynamicInputs: SocketDef[] = [
+ ...(NODE_SOCKETS.format?.inputs.filter((s) => s.id === "template") ?? []),
+ ...parseTemplateArgs(template),
+ ];
return (
-
+
@@ -17,7 +34,7 @@ export function FormatNode({ data }: NodeProps) {
- {d.template || "{0} {1}"}
+ {template}
);
diff --git a/src/components/admin/narrative-flow/NarrativeFlowEditor.tsx b/src/components/admin/narrative-flow/NarrativeFlowEditor.tsx
index fdbcf1a..e328ed8 100644
--- a/src/components/admin/narrative-flow/NarrativeFlowEditor.tsx
+++ b/src/components/admin/narrative-flow/NarrativeFlowEditor.tsx
@@ -119,10 +119,14 @@ export function NarrativeFlowEditor() {
const sourceSocket = sourceSockets.outputs.find((s) => s.id === connection.sourceHandle);
const targetSocket = targetSockets.inputs.find((s) => s.id === connection.targetHandle);
- if (!sourceSocket || !targetSocket) return false;
- if (sourceSocket.type === "any" || targetSocket.type === "any") return true;
- return sourceSocket.type === targetSocket.type;
+ // Dynamic handles (arg-*, option-*) are treated as "any"
+ const sourceType = sourceSocket?.type ?? (connection.sourceHandle.startsWith("arg-") || connection.sourceHandle.startsWith("option-") ? "any" : null);
+ const targetType = targetSocket?.type ?? (connection.targetHandle.startsWith("arg-") || connection.targetHandle.startsWith("option-") ? "any" : null);
+
+ if (sourceType === null || targetType === null) return false;
+ if (sourceType === "any" || targetType === "any") return true;
+ return sourceType === targetType;
},
[nodes],
);
diff --git a/src/components/admin/narrative-flow/NodeHandles.tsx b/src/components/admin/narrative-flow/NodeHandles.tsx
index afbc823..5cf33c6 100644
--- a/src/components/admin/narrative-flow/NodeHandles.tsx
+++ b/src/components/admin/narrative-flow/NodeHandles.tsx
@@ -1,21 +1,20 @@
"use client";
import { TypedHandle } from "./TypedHandle";
-import { NODE_SOCKETS } from "./types";
+import { NODE_SOCKETS, type SocketDef } from "./types";
interface NodeHandlesProps {
nodeType: string;
- dynamicOutputs?: Array<{ id: string }>;
+ dynamicInputs?: SocketDef[];
+ dynamicOutputs?: SocketDef[];
}
-export function NodeHandles({ nodeType, dynamicOutputs }: NodeHandlesProps) {
+export function NodeHandles({ nodeType, dynamicInputs, dynamicOutputs }: NodeHandlesProps) {
const config = NODE_SOCKETS[nodeType];
if (!config) return null;
- const inputs = config.inputs;
- const outputs = dynamicOutputs
- ? dynamicOutputs.map((o) => ({ ...o, type: "flow" as const }))
- : config.outputs;
+ const inputs = dynamicInputs ?? config.inputs;
+ const outputs = dynamicOutputs ?? config.outputs;
const maxSlots = Math.max(inputs.length, outputs.length, 1);
@@ -28,6 +27,7 @@ export function NodeHandles({ nodeType, dynamicOutputs }: NodeHandlesProps) {
key={socket.id}
id={socket.id}
socketType={socket.type}
+ label={socket.label}
isInput={true}
style={{ top: `${topPercent}%` }}
/>
@@ -40,6 +40,7 @@ export function NodeHandles({ nodeType, dynamicOutputs }: NodeHandlesProps) {
key={socket.id}
id={socket.id}
socketType={socket.type}
+ label={socket.label}
isInput={false}
style={{ top: `${topPercent}%` }}
/>
diff --git a/src/components/admin/narrative-flow/TypedHandle.tsx b/src/components/admin/narrative-flow/TypedHandle.tsx
index 1741081..0593e23 100644
--- a/src/components/admin/narrative-flow/TypedHandle.tsx
+++ b/src/components/admin/narrative-flow/TypedHandle.tsx
@@ -7,26 +7,48 @@ interface TypedHandleProps {
id: string;
socketType: SocketType;
isInput: boolean;
+ label?: string;
style?: React.CSSProperties;
}
-export function TypedHandle({ id, socketType, isInput, style }: TypedHandleProps) {
+export function TypedHandle({ id, socketType, isInput, label, style }: TypedHandleProps) {
const color = SOCKET_COLORS[socketType];
const isFlow = socketType === "flow";
return (
-
+ >
+
+ {label && (
+
+ {label}
+
+ )}
+
);
}