{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "flow-parallelization",
	"type": "registry:block",
	"description": "Agentic parallelization workflow.",
	"dependencies": [
		"@xyflow/react",
		"zustand",
		"zod",
		"ai",
		"nanoid",
		"@ai-sdk/openai",
		"@ai-sdk/groq",
		"@ai-sdk/deepseek"
	],
	"registryDependencies": [
		"button",
		"card",
		"dialog",
		"input",
		"textarea",
		"sonner",
		"@simple-ai/generate-text-node",
		"@simple-ai/prompt-crafter-node",
		"@simple-ai/text-input-node",
		"@simple-ai/visualize-text-node"
	],
	"files": [
		{
			"path": "./src/registry/blocks/flow-parallelization/page.tsx",
			"content": "\"use client\";\n\nimport {\n\tBackground,\n\tControls,\n\ttype EdgeTypes,\n\tMiniMap,\n\ttype NodeTypes,\n\tPanel,\n\tReactFlow,\n\tReactFlowProvider,\n\tuseReactFlow,\n} from \"@xyflow/react\";\nimport { type DragEvent, useEffect } from \"react\";\nimport { shallow } from \"zustand/shallow\";\nimport \"@xyflow/react/dist/style.css\";\nimport { Button } from \"@/components/ui/button\";\nimport { ErrorIndicator } from \"@/registry/blocks/flow-parallelization/components/error-indicator\";\nimport { NodesPanel } from \"@/registry/blocks/flow-parallelization/components/nodes-panel\";\nimport { EXAM_CREATOR_PARALLELIZATION_WORKFLOW } from \"@/registry/blocks/flow-parallelization/lib/exam-creator-parallelization\";\nimport { useWorkflow } from \"@/registry/hooks/flow/use-workflow\";\nimport type { FlowNode } from \"@/registry/lib/flow/workflow\";\nimport { GenerateTextNodeController } from \"@/registry/ui/flow/generate-text-node-controller\";\nimport { PromptCrafterNodeController } from \"@/registry/ui/flow/prompt-crafter-node-controller\";\nimport { StatusEdgeController } from \"@/registry/ui/flow/status-edge-controller\";\nimport { TextInputNodeController } from \"@/registry/ui/flow/text-input-node-controller\";\nimport { VisualizeTextNodeController } from \"@/registry/ui/flow/visualize-text-node-controller\";\n\nconst nodeTypes: NodeTypes = {\n\t\"generate-text\": GenerateTextNodeController,\n\t\"visualize-text\": VisualizeTextNodeController,\n\t\"text-input\": TextInputNodeController,\n\t\"prompt-crafter\": PromptCrafterNodeController,\n};\n\nconst edgeTypes: EdgeTypes = {\n\tstatus: StatusEdgeController,\n};\n\nexport function Flow() {\n\tconst store = useWorkflow(\n\t\t(store) => ({\n\t\t\tnodes: store.nodes,\n\t\t\tedges: store.edges,\n\t\t\tonNodesChange: store.onNodesChange,\n\t\t\tonEdgesChange: store.onEdgesChange,\n\t\t\tonConnect: store.onConnect,\n\t\t\tstartExecution: store.startExecution,\n\t\t\tcreateNode: store.createNode,\n\t\t\tworkflowExecutionState: store.workflowExecutionState,\n\t\t\tinitializeWorkflow: store.initializeWorkflow,\n\t\t}),\n\t\tshallow,\n\t);\n\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: We want to initialize the workflow only once\n\tuseEffect(() => {\n\t\tstore.initializeWorkflow(\n\t\t\tEXAM_CREATOR_PARALLELIZATION_WORKFLOW.nodes,\n\t\t\tEXAM_CREATOR_PARALLELIZATION_WORKFLOW.edges,\n\t\t);\n\t}, []);\n\n\tconst { screenToFlowPosition } = useReactFlow();\n\n\tconst onDragOver = (event: DragEvent) => {\n\t\tevent.preventDefault();\n\t\tevent.dataTransfer.dropEffect = \"move\";\n\t};\n\n\tconst onDrop = (event: DragEvent) => {\n\t\tevent.preventDefault();\n\n\t\tconst type = event.dataTransfer.getData(\n\t\t\t\"application/reactflow\",\n\t\t) as FlowNode[\"type\"];\n\n\t\tif (!type) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst position = screenToFlowPosition({\n\t\t\tx: event.clientX,\n\t\t\ty: event.clientY,\n\t\t});\n\n\t\tstore.createNode(type, position);\n\t};\n\n\tconst onStartExecution = async () => {\n\t\tconst result = await store.startExecution();\n\t\tif (result.status === \"error\") {\n\t\t\tconsole.error(result.error);\n\t\t}\n\t};\n\n\treturn (\n\t\t<ReactFlow\n\t\t\tnodes={store.nodes}\n\t\t\tedges={store.edges}\n\t\t\tonNodesChange={store.onNodesChange}\n\t\t\tonEdgesChange={store.onEdgesChange}\n\t\t\tonConnect={store.onConnect}\n\t\t\tnodeTypes={nodeTypes}\n\t\t\tedgeTypes={edgeTypes}\n\t\t\tonDragOver={onDragOver}\n\t\t\tonDrop={onDrop}\n\t\t\tfitView\n\t\t>\n\t\t\t<Background />\n\t\t\t<Controls />\n\t\t\t<MiniMap />\n\t\t\t<NodesPanel />\n\t\t\t<Panel position=\"top-right\" className=\"flex gap-2 items-center\">\n\t\t\t\t<ErrorIndicator errors={store.workflowExecutionState.errors} />\n\t\t\t\t<Button\n\t\t\t\t\tonClick={onStartExecution}\n\t\t\t\t\ttitle={\n\t\t\t\t\t\tstore.workflowExecutionState.timesRun > 1\n\t\t\t\t\t\t\t? \"Disabled for now\"\n\t\t\t\t\t\t\t: \"Run the workflow\"\n\t\t\t\t\t}\n\t\t\t\t\tdisabled={\n\t\t\t\t\t\tstore.workflowExecutionState.errors.length > 0 ||\n\t\t\t\t\t\tstore.workflowExecutionState.isRunning ||\n\t\t\t\t\t\tstore.workflowExecutionState.timesRun > 1\n\t\t\t\t\t}\n\t\t\t\t>\n\t\t\t\t\t{store.workflowExecutionState.isRunning\n\t\t\t\t\t\t? \"Running...\"\n\t\t\t\t\t\t: \"Run Flow\"}\n\t\t\t\t</Button>\n\t\t\t</Panel>\n\t\t</ReactFlow>\n\t);\n}\n\nexport default function Page() {\n\treturn (\n\t\t<div className=\"w-screen h-screen\">\n\t\t\t<ReactFlowProvider>\n\t\t\t\t<Flow />\n\t\t\t</ReactFlowProvider>\n\t\t</div>\n\t);\n}\n",
			"type": "registry:page",
			"target": "app/workflow/page.tsx"
		},
		{
			"path": "./src/registry/blocks/flow-parallelization/route.ts",
			"content": "import { NextResponse } from \"next/server\";\nimport { serverNodeProcessors } from \"@/registry/lib/flow/server-node-processors\";\nimport { executeServerWorkflow } from \"@/registry/lib/flow/sse-workflow-execution-engine\";\nimport type { WorkflowDefinition } from \"@/registry/lib/flow/workflow\";\n\nexport const maxDuration = 60;\n\nexport async function POST(req: Request) {\n\ttry {\n\t\tconst { workflow } = await req.json();\n\n\t\tif (!workflow) {\n\t\t\treturn NextResponse.json(\n\t\t\t\t{ error: \"No workflow data provided\" },\n\t\t\t\t{ status: 400 },\n\t\t\t);\n\t\t}\n\n\t\tconst workflowDefinition: WorkflowDefinition = workflow;\n\n\t\t// Create a stream for SSE\n\t\tconst stream = new ReadableStream({\n\t\t\tasync start(controller) {\n\t\t\t\tawait executeServerWorkflow(\n\t\t\t\t\tworkflowDefinition,\n\t\t\t\t\tserverNodeProcessors,\n\t\t\t\t\tcontroller,\n\t\t\t\t);\n\t\t\t},\n\t\t});\n\n\t\treturn new Response(stream, {\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"text/event-stream\",\n\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\tConnection: \"keep-alive\",\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\treturn NextResponse.json(\n\t\t\t{ error: error instanceof Error ? error.message : \"Unknown error\" },\n\t\t\t{ status: 500 },\n\t\t);\n\t}\n}\n",
			"type": "registry:page",
			"target": "app/api/workflow/execute/route.ts"
		},
		{
			"path": "./src/registry/blocks/flow-parallelization/components/nodes-panel.tsx",
			"content": "import { Panel } from \"@xyflow/react\";\nimport { Eye, PenLine } from \"lucide-react\";\nimport type React from \"react\";\nimport { Button } from \"@/components/ui/button\";\n\nconst nodeTypes = [\n\t{\n\t\ttype: \"visualize-text\",\n\t\tlabel: \"Visualize Text\",\n\t\ticon: Eye,\n\t},\n\t{\n\t\ttype: \"text-input\",\n\t\tlabel: \"Text Input\",\n\t\ticon: PenLine,\n\t},\n];\n\nexport function NodesPanel() {\n\tconst onDragStart = (event: React.DragEvent, nodeType: string) => {\n\t\tevent.dataTransfer.setData(\"application/reactflow\", nodeType);\n\t\tevent.dataTransfer.effectAllowed = \"move\";\n\t};\n\n\treturn (\n\t\t<Panel position=\"top-center\" className=\"flex gap-2\">\n\t\t\t{nodeTypes.map((nodeType) => (\n\t\t\t\t<Button\n\t\t\t\t\tkey={nodeType.type}\n\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\tclassName=\"cursor-grab\"\n\t\t\t\t\tdraggable\n\t\t\t\t\tonDragStart={(e) => onDragStart(e, nodeType.type)}\n\t\t\t\t>\n\t\t\t\t\t<nodeType.icon className=\"mr-2 h-4 w-4\" />\n\t\t\t\t\t{nodeType.label}\n\t\t\t\t</Button>\n\t\t\t))}\n\t\t</Panel>\n\t);\n}\n",
			"type": "registry:component",
			"target": "components/flow/nodes-panel.tsx"
		},
		{
			"path": "./src/registry/blocks/flow-parallelization/components/error-indicator.tsx",
			"content": "import { AlertCircle } from \"lucide-react\";\nimport type React from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tPopover,\n\tPopoverContent,\n\tPopoverTrigger,\n} from \"@/components/ui/popover\";\nimport type { WorkflowError } from \"@/registry/lib/flow/workflow\";\n\ninterface ErrorIndicatorProps {\n\terrors: WorkflowError[];\n}\n\nexport function ErrorIndicator({\n\terrors,\n}: ErrorIndicatorProps): React.ReactElement | null {\n\tif (errors.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<Popover>\n\t\t\t<PopoverTrigger asChild>\n\t\t\t\t<Button variant=\"ghost\" size=\"icon\" className=\"text-red-500\">\n\t\t\t\t\t<AlertCircle className=\"h-5 w-5\" />\n\t\t\t\t</Button>\n\t\t\t</PopoverTrigger>\n\t\t\t<PopoverContent align=\"center\" className=\"w-80 mt-4 mr-4\">\n\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t<h4 className=\"font-medium\">Workflow Errors</h4>\n\t\t\t\t\t<div className=\"space-y-1\">\n\t\t\t\t\t\t{errors.map((error) => (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tkey={`${error.type}-${error.message}`}\n\t\t\t\t\t\t\t\tclassName=\"text-sm text-red-500\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{error.message}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</PopoverContent>\n\t\t</Popover>\n\t);\n}\n",
			"type": "registry:component",
			"target": "components/error-indicator.tsx"
		},
		{
			"path": "./src/registry/blocks/flow-parallelization/lib/exam-creator-parallelization.ts",
			"content": "import type { FlowEdge, FlowNode } from \"@/registry/lib/flow/workflow\";\n\nexport const EXAM_CREATOR_PARALLELIZATION_WORKFLOW: {\n\tnodes: FlowNode[];\n\tedges: FlowEdge[];\n} = {\n\tnodes: [\n\t\t{\n\t\t\ttype: \"generate-text\",\n\t\t\tid: \"validateLLM\",\n\t\t\tdata: {\n\t\t\t\tconfig: {\n\t\t\t\t\tmodel: \"llama-3.1-8b-instant\",\n\t\t\t\t},\n\t\t\t\tdynamicHandles: {\n\t\t\t\t\ttools: [],\n\t\t\t\t},\n\t\t\t},\n\t\t\tposition: {\n\t\t\t\tx: 251.46219588875414,\n\t\t\t\ty: -157.42640737415334,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttype: \"generate-text\",\n\t\t\tid: \"Nr22stf-aM3K9KZ7fHREZ\",\n\t\t\tdata: {\n\t\t\t\tconfig: {\n\t\t\t\t\tmodel: \"llama-3.1-8b-instant\",\n\t\t\t\t},\n\t\t\t\tdynamicHandles: {\n\t\t\t\t\ttools: [],\n\t\t\t\t},\n\t\t\t},\n\t\t\tposition: {\n\t\t\t\tx: 245.3399584365339,\n\t\t\t\ty: 489.49722280589094,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttype: \"visualize-text\",\n\t\t\tid: \"eYRTRKwrUcn_fmuMKuUEl\",\n\t\t\tdata: {},\n\t\t\tposition: {\n\t\t\t\tx: 648.6394983132599,\n\t\t\t\ty: -252.7402610767247,\n\t\t\t},\n\t\t\twidth: 379,\n\t\t\theight: 300,\n\t\t},\n\t\t{\n\t\t\ttype: \"generate-text\",\n\t\t\tid: \"ZnL2SgGAMwaZSLNH-bOX3\",\n\t\t\tdata: {\n\t\t\t\tconfig: {\n\t\t\t\t\tmodel: \"llama-3.1-8b-instant\",\n\t\t\t\t},\n\t\t\t\tdynamicHandles: {\n\t\t\t\t\ttools: [],\n\t\t\t\t},\n\t\t\t},\n\t\t\tposition: {\n\t\t\t\tx: 241.32228205169054,\n\t\t\t\ty: 158.08000713832058,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttype: \"generate-text\",\n\t\t\tid: \"lu-X2l3QTJj8RBk4fDwGL\",\n\t\t\tdata: {\n\t\t\t\tconfig: {\n\t\t\t\t\tmodel: \"llama-3.1-8b-instant\",\n\t\t\t\t},\n\t\t\t\tdynamicHandles: {\n\t\t\t\t\ttools: [],\n\t\t\t\t},\n\t\t\t},\n\t\t\tposition: {\n\t\t\t\tx: 1687.7585469555088,\n\t\t\t\ty: 418.93136565541863,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttype: \"text-input\",\n\t\t\tid: \"_4RcYkPOEDKn-hmGOAvy9\",\n\t\t\tdata: {\n\t\t\t\tconfig: {\n\t\t\t\t\tvalue: \"<assistant_info>\\n    You are an expert in combining and organizing educational content.  \\n    Your task is to combine the outputs from three different agents to create a cohesive exam paper.  \\n\\n    You will receive:  \\n    - A set of multiple-choice questions.  \\n    - A set of open-answer questions.  \\n    - A set of essay prompts.  \\n\\n    Your task is to:  \\n    - Compile these into a single, well-structured exam paper.  \\n    - Organize the paper with the following structure:  \\n      1. Multiple-choice questions.  \\n      2. Short-answer questions.  \\n      3. Essay prompts.  \\n    - Ensure the content flows logically, using clear section headers.  \\n\\n    Output should:  \\n    - Be formatted for readability.  \\n    - Include proper numbering for each question.  \\n</assistant_info>\\n\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tposition: {\n\t\t\t\tx: 1250.9578657920588,\n\t\t\t\ty: -224.79053213173495,\n\t\t\t},\n\t\t\twidth: 350,\n\t\t\theight: 417,\n\t\t},\n\t\t{\n\t\t\ttype: \"visualize-text\",\n\t\t\tid: \"kaTYJV52ljshMg0uClQl1\",\n\t\t\tdata: {},\n\t\t\tposition: {\n\t\t\t\tx: 649.7252565724999,\n\t\t\t\ty: 107.18165405549195,\n\t\t\t},\n\t\t\twidth: 377,\n\t\t\theight: 300,\n\t\t},\n\t\t{\n\t\t\ttype: \"visualize-text\",\n\t\t\tid: \"s5NSuCUuEByh_BTCSSMDU\",\n\t\t\tdata: {},\n\t\t\tposition: {\n\t\t\t\tx: 646.3721167044031,\n\t\t\t\ty: 456.0475192259633,\n\t\t\t},\n\t\t\twidth: 377,\n\t\t\theight: 300,\n\t\t},\n\t\t{\n\t\t\ttype: \"visualize-text\",\n\t\t\tid: \"9cLCaECGGL5t21iQ3TDc9\",\n\t\t\tdata: {},\n\t\t\tposition: {\n\t\t\t\tx: 1695.1459958597898,\n\t\t\t\ty: -234.91904513607875,\n\t\t\t},\n\t\t\twidth: 518,\n\t\t\theight: 614,\n\t\t},\n\t\t{\n\t\t\ttype: \"text-input\",\n\t\t\tid: \"VGFbBVUjlwdQ2cGhrCv72\",\n\t\t\tdata: {\n\t\t\t\tconfig: {\n\t\t\t\t\tvalue: \"I want to create a exam on React Flow programming\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tposition: {\n\t\t\t\tx: -722.022355638326,\n\t\t\t\ty: 62.42404642145064,\n\t\t\t},\n\t\t\twidth: 350,\n\t\t\theight: 300,\n\t\t},\n\t\t{\n\t\t\ttype: \"text-input\",\n\t\t\tid: \"FpL4edqHCqaXqhGrD2xEJ\",\n\t\t\tdata: {\n\t\t\t\tconfig: {\n\t\t\t\t\tvalue: \"<assistant_info>\\n    You are an expert in creating multiple-choice questions for educational purposes. \\n    Your task is to create multiple-choice questions on the given topic. Each question should:\\n    - Be clear and concise.\\n    - Include a single correct answer and three plausible distractors (incorrect answers).\\n    - Test the student's understanding of key concepts from the topic.\\n\\n    The output should:\\n    - Contain the question, four answer options, and the correct answer.\\n    - Ensure distractors are not obviously incorrect but based on common misconceptions or related ideas.\\n   - ALWAYS Only output 2 questions\\n</assistant_info>\\n<examples>\\n    Example 1:  \\n    Question: What is the primary cause of climate change?  \\n    Options:  \\n    A. Solar flares  \\n    B. Volcanic eruptions  \\n    C. Greenhouse gas emissions  \\n    D. Changes in Earth's orbit  \\n    Correct Answer: C  \\n\\n    Example 2:  \\n    Question: Which of the following gases is considered a greenhouse gas?  \\n    Options:  \\n    A. Oxygen  \\n    B. Nitrogen  \\n    C. Carbon dioxide  \\n    D. Argon  \\n    Correct Answer: C  \\n</examples>\\n\\n\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tposition: {\n\t\t\t\tx: -157.73052506593396,\n\t\t\t\ty: -200.36676668546224,\n\t\t\t},\n\t\t\twidth: 350,\n\t\t\theight: 300,\n\t\t},\n\t\t{\n\t\t\ttype: \"text-input\",\n\t\t\tid: \"mcXEqjj4TY8HBof7E6pdl\",\n\t\t\tdata: {\n\t\t\t\tconfig: {\n\t\t\t\t\tvalue: \"<assistant_info>\\n    You are an expert in creating short-answer questions for educational purposes.  \\n    Your task is to create short-answer questions that:  \\n    - Require students to demonstrate understanding of the topic in 1-3 sentences.  \\n    - Are open-ended but specific enough to test key concepts.  \\n\\n    The output should:  \\n    - Include the question as a standalone sentence or prompt.  \\n    - Provide a sample ideal answer for reference.  \\n  - ALWAYS Only output 2 questions\\n</assistant_info>\\n<example>\\n    Example 1:  \\n    Question: Explain how greenhouse gases contribute to global warming.  \\n    Sample Answer: Greenhouse gases trap heat in the Earth's atmosphere, preventing it from escaping into space. This leads to an increase in global temperatures over time.  \\n\\n    Example 2:  \\n    Question: What is the significance of the Paris Agreement in addressing climate change?  \\n    Sample Answer: The Paris Agreement is a global treaty that aims to limit global warming to below 2 degrees Celsius compared to pre-industrial levels by reducing greenhouse gas emissions.  \\n</example>\\n\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tposition: {\n\t\t\t\tx: -159.63323331453773,\n\t\t\t\ty: 122.18288195856604,\n\t\t\t},\n\t\t\twidth: 350,\n\t\t\theight: 300,\n\t\t},\n\t\t{\n\t\t\ttype: \"text-input\",\n\t\t\tid: \"eVfOwR2k_3HG4sBFeFZcg\",\n\t\t\tdata: {\n\t\t\t\tconfig: {\n\t\t\t\t\tvalue: \"<assistant_info>\\n   You are an expert in creating essay prompts for educational purposes.  \\n    Your task is to create essay prompts that:  \\n    - Encourage critical thinking and analysis of the topic.  \\n    - Allow students to explore different perspectives or arguments.  \\n    - Require detailed explanations or evidence-based reasoning.  \\n\\n    The output should:  \\n    - Include a clearly worded essay question or statement.  \\n    - Optionally provide guidance on how to approach the essay.  \\n  - ALWAYS Only output 1 essay prompt\\n</assistant_info>\\n<example>\\n    Example 1:  \\n    Prompt: Discuss the social, economic, and environmental impacts of climate change. How can governments and individuals work together to address these challenges?  \\n    Guidance: In your essay, provide examples of specific impacts, such as rising sea levels or economic costs. Discuss at least one solution involving government policies and one involving individual actions.  \\n\\n    Example 2:  \\n    Prompt: Analyze the role of renewable energy in mitigating climate change. What are the challenges and benefits of transitioning to renewable energy sources?  \\n    Guidance: Consider different types of renewable energy, such as solar and wind, and evaluate their feasibility in various regions. Address both technological and economic factors.  \\n</example>\\n\\n\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tposition: {\n\t\t\t\tx: -160.93480457629244,\n\t\t\t\ty: 461.9358073446226,\n\t\t\t},\n\t\t\twidth: 350,\n\t\t\theight: 300,\n\t\t},\n\t\t{\n\t\t\ttype: \"prompt-crafter\",\n\t\t\tid: \"7-uZXwIU-n7fEMCoLZsMt\",\n\t\t\tdata: {\n\t\t\t\tconfig: {\n\t\t\t\t\ttemplate:\n\t\t\t\t\t\t\"<multiple-choice-content>\\n  {{multiple-choice-content}}\\n</multiple-choice-content>\\n  {{open-answer-questions-content}}\\n<open-questions-content>\\n</open-questions-content>\\n<essay-content>\\n  {{essay-content}}\\n</essay-content>\",\n\t\t\t\t},\n\t\t\t\tdynamicHandles: {\n\t\t\t\t\t\"template-tags\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"multiple-choice-content\",\n\t\t\t\t\t\t\tid: \"GDFxwCjnyoesYWdUKZtGq\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"open-answer-questions-content\",\n\t\t\t\t\t\t\tid: \"llV7g536-dmly98vvpFak\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"essay-content\",\n\t\t\t\t\t\t\tid: \"Zla3PfCwXBMnW32gB_MiF\",\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t},\n\t\t\tposition: {\n\t\t\t\tx: 1251.6723866635305,\n\t\t\t\ty: 232.25256033640713,\n\t\t\t},\n\t\t},\n\t],\n\tedges: [\n\t\t{\n\t\t\tsource: \"_4RcYkPOEDKn-hmGOAvy9\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"lu-X2l3QTJj8RBk4fDwGL\",\n\t\t\ttargetHandle: \"system\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge___4RcYkPOEDKn-hmGOAvy9result-lu-X2l3QTJj8RBk4fDwGLsystem\",\n\t\t\tdata: {},\n\t\t},\n\t\t{\n\t\t\tsource: \"ZnL2SgGAMwaZSLNH-bOX3\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"kaTYJV52ljshMg0uClQl1\",\n\t\t\ttargetHandle: \"input\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__ZnL2SgGAMwaZSLNH-bOX3result-kaTYJV52ljshMg0uClQl1input\",\n\t\t\tdata: {},\n\t\t},\n\t\t{\n\t\t\tsource: \"Nr22stf-aM3K9KZ7fHREZ\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"s5NSuCUuEByh_BTCSSMDU\",\n\t\t\ttargetHandle: \"input\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__Nr22stf-aM3K9KZ7fHREZresult-s5NSuCUuEByh_BTCSSMDUinput\",\n\t\t\tdata: {},\n\t\t},\n\t\t{\n\t\t\tsource: \"lu-X2l3QTJj8RBk4fDwGL\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"9cLCaECGGL5t21iQ3TDc9\",\n\t\t\ttargetHandle: \"input\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__lu-X2l3QTJj8RBk4fDwGLresult-9cLCaECGGL5t21iQ3TDc9input\",\n\t\t\tdata: {},\n\t\t},\n\t\t{\n\t\t\tsource: \"VGFbBVUjlwdQ2cGhrCv72\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"validateLLM\",\n\t\t\ttargetHandle: \"prompt\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__VGFbBVUjlwdQ2cGhrCv72result-validateLLMprompt\",\n\t\t\tdata: {},\n\t\t\tselected: false,\n\t\t},\n\t\t{\n\t\t\tsource: \"mcXEqjj4TY8HBof7E6pdl\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"ZnL2SgGAMwaZSLNH-bOX3\",\n\t\t\ttargetHandle: \"system\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__mcXEqjj4TY8HBof7E6pdlresult-ZnL2SgGAMwaZSLNH-bOX3system\",\n\t\t\tdata: {},\n\t\t},\n\t\t{\n\t\t\tsource: \"FpL4edqHCqaXqhGrD2xEJ\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"validateLLM\",\n\t\t\ttargetHandle: \"system\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__FpL4edqHCqaXqhGrD2xEJresult-validateLLMsystem\",\n\t\t\tdata: {},\n\t\t},\n\t\t{\n\t\t\tsource: \"eVfOwR2k_3HG4sBFeFZcg\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"Nr22stf-aM3K9KZ7fHREZ\",\n\t\t\ttargetHandle: \"system\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__eVfOwR2k_3HG4sBFeFZcgresult-Nr22stf-aM3K9KZ7fHREZsystem\",\n\t\t\tdata: {},\n\t\t},\n\t\t{\n\t\t\tsource: \"validateLLM\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"eYRTRKwrUcn_fmuMKuUEl\",\n\t\t\ttargetHandle: \"input\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__validateLLMresult-eYRTRKwrUcn_fmuMKuUElinput\",\n\t\t\tdata: {},\n\t\t},\n\t\t{\n\t\t\tsource: \"VGFbBVUjlwdQ2cGhrCv72\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"ZnL2SgGAMwaZSLNH-bOX3\",\n\t\t\ttargetHandle: \"prompt\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__VGFbBVUjlwdQ2cGhrCv72result-ZnL2SgGAMwaZSLNH-bOX3prompt\",\n\t\t\tdata: {},\n\t\t},\n\t\t{\n\t\t\tsource: \"VGFbBVUjlwdQ2cGhrCv72\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"Nr22stf-aM3K9KZ7fHREZ\",\n\t\t\ttargetHandle: \"prompt\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__VGFbBVUjlwdQ2cGhrCv72result-Nr22stf-aM3K9KZ7fHREZprompt\",\n\t\t\tdata: {},\n\t\t},\n\t\t{\n\t\t\tsource: \"validateLLM\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"7-uZXwIU-n7fEMCoLZsMt\",\n\t\t\ttargetHandle: \"GDFxwCjnyoesYWdUKZtGq\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__validateLLMresult-7-uZXwIU-n7fEMCoLZsMtGDFxwCjnyoesYWdUKZtGq\",\n\t\t\tdata: {},\n\t\t},\n\t\t{\n\t\t\tsource: \"ZnL2SgGAMwaZSLNH-bOX3\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"7-uZXwIU-n7fEMCoLZsMt\",\n\t\t\ttargetHandle: \"llV7g536-dmly98vvpFak\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__ZnL2SgGAMwaZSLNH-bOX3result-7-uZXwIU-n7fEMCoLZsMtllV7g536-dmly98vvpFak\",\n\t\t\tdata: {},\n\t\t},\n\t\t{\n\t\t\tsource: \"Nr22stf-aM3K9KZ7fHREZ\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"7-uZXwIU-n7fEMCoLZsMt\",\n\t\t\ttargetHandle: \"Zla3PfCwXBMnW32gB_MiF\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__Nr22stf-aM3K9KZ7fHREZresult-7-uZXwIU-n7fEMCoLZsMtZla3PfCwXBMnW32gB_MiF\",\n\t\t\tdata: {},\n\t\t},\n\t\t{\n\t\t\tsource: \"7-uZXwIU-n7fEMCoLZsMt\",\n\t\t\tsourceHandle: \"result\",\n\t\t\ttarget: \"lu-X2l3QTJj8RBk4fDwGL\",\n\t\t\ttargetHandle: \"prompt\",\n\t\t\ttype: \"status\",\n\t\t\tid: \"xy-edge__7-uZXwIU-n7fEMCoLZsMtresult-lu-X2l3QTJj8RBk4fDwGLprompt\",\n\t\t\tdata: {},\n\t\t},\n\t],\n};\n",
			"type": "registry:lib"
		},
		{
			"path": "./src/registry/ui/flow/status-edge-controller.tsx",
			"content": "\"use client\";\n\nimport type { EdgeProps } from \"@xyflow/react\";\nimport type { EdgeExecutionState } from \"@/registry/lib/flow/workflow-execution-engine\";\nimport { StatusEdge } from \"@/registry/ui/flow/status-edge\";\n\nexport type StatusEdgeController = Omit<StatusEdge, \"data\"> & {\n\ttype: \"status\";\n\tdata: {\n\t\texecutionState?: EdgeExecutionState;\n\t};\n};\n\nexport function StatusEdgeController({\n\tdata,\n\t...props\n}: EdgeProps<StatusEdgeController>) {\n\treturn (\n\t\t<StatusEdge\n\t\t\t{...props}\n\t\t\tdata={{\n\t\t\t\terror: !!data.executionState?.error,\n\t\t\t}}\n\t\t/>\n\t);\n}\n",
			"type": "registry:component",
			"target": "components/flow/status-edge-controller.tsx"
		},
		{
			"path": "./src/registry/ui/flow/status-edge.tsx",
			"content": "import {\n\ttype Edge,\n\ttype EdgeProps,\n\tBaseEdge as FlowBaseEdge,\n\tgetBezierPath,\n} from \"@xyflow/react\";\nimport type { CSSProperties } from \"react\";\n\nexport type StatusEdge = Edge<\n\t{\n\t\terror?: boolean;\n\t},\n\t\"status\"\n> & {\n\ttype: \"status\";\n\tsourceHandle: string;\n\ttargetHandle: string;\n};\n\nexport function StatusEdge({\n\tsourceX,\n\tsourceY,\n\ttargetX,\n\ttargetY,\n\tsourcePosition,\n\ttargetPosition,\n\tdata,\n\tselected,\n}: EdgeProps<StatusEdge>) {\n\tconst [edgePath] = getBezierPath({\n\t\tsourceX,\n\t\tsourceY,\n\t\tsourcePosition,\n\t\ttargetX,\n\t\ttargetY,\n\t\ttargetPosition,\n\t});\n\n\tconst edgeStyle: CSSProperties = {\n\t\tstroke: data?.error ? \"#ef4444\" : selected ? \"#3b82f6\" : \"#b1b1b7\",\n\t\tstrokeWidth: selected ? 3 : 2,\n\t\ttransition: \"stroke 0.2s, stroke-width 0.2s\",\n\t};\n\n\treturn <FlowBaseEdge path={edgePath} style={edgeStyle} />;\n}\n",
			"type": "registry:component",
			"target": "components/flow/status-edge.tsx"
		},
		{
			"path": "./src/registry/ui/flow/visualize-text-node-controller.tsx",
			"content": "\"use client\";\n\nimport type { NodeProps } from \"@xyflow/react\";\nimport { useCallback } from \"react\";\nimport { useWorkflow } from \"@/registry/hooks/flow/use-workflow\";\nimport type { NodeExecutionState } from \"@/registry/lib/flow/workflow-execution-engine\";\nimport { VisualizeTextNode } from \"@/registry/ui/flow/visualize-text-node\";\n\nexport type VisualizeTextNodeController = Omit<VisualizeTextNode, \"data\"> & {\n\ttype: \"visualize-text\";\n\tdata: {\n\t\texecutionState?: NodeExecutionState;\n\t};\n};\n\nexport function VisualizeTextNodeController({\n\tid,\n\tdata,\n\t...props\n}: NodeProps<VisualizeTextNodeController>) {\n\tconst deleteNode = useWorkflow((state) => state.deleteNode);\n\n\tconst handleDeleteNode = useCallback(() => {\n\t\tdeleteNode(id);\n\t}, [id, deleteNode]);\n\n\treturn (\n\t\t<VisualizeTextNode\n\t\t\tid={id}\n\t\t\tdata={{\n\t\t\t\tinput: data.executionState?.targets?.input,\n\t\t\t\tstatus: data.executionState?.status,\n\t\t\t}}\n\t\t\tonDeleteNode={handleDeleteNode}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n",
			"type": "registry:component",
			"target": "components/flow/visualize-text-node-controller.tsx"
		},
		{
			"path": "./src/registry/ui/flow/text-input-node-controller.tsx",
			"content": "\"use client\";\n\nimport type { NodeProps } from \"@xyflow/react\";\nimport { useCallback } from \"react\";\nimport { useWorkflow } from \"@/registry/hooks/flow/use-workflow\";\nimport type { NodeExecutionState } from \"@/registry/lib/flow/workflow-execution-engine\";\nimport { TextInputNode } from \"@/registry/ui/flow/text-input-node\";\n\nexport type TextInputNodeController = Omit<TextInputNode, \"data\"> & {\n\ttype: \"text-input\";\n\tdata: Omit<TextInputNode[\"data\"], \"status\"> & {\n\t\texecutionState?: NodeExecutionState;\n\t};\n};\n\nexport function TextInputNodeController({\n\tid,\n\tdata,\n\t...props\n}: NodeProps<TextInputNodeController>) {\n\tconst updateNode = useWorkflow((state) => state.updateNode);\n\tconst deleteNode = useWorkflow((state) => state.deleteNode);\n\n\tconst handleTextChange = useCallback(\n\t\t(value: string) => {\n\t\t\tupdateNode(id, \"text-input\", { config: { value } });\n\t\t},\n\t\t[id, updateNode],\n\t);\n\n\tconst handleDeleteNode = useCallback(() => {\n\t\tdeleteNode(id);\n\t}, [id, deleteNode]);\n\n\treturn (\n\t\t<TextInputNode\n\t\t\tid={id}\n\t\t\tdata={{\n\t\t\t\tstatus: data.executionState?.status,\n\t\t\t\tconfig: data.config,\n\t\t\t}}\n\t\t\t{...props}\n\t\t\tonTextChange={handleTextChange}\n\t\t\tonDeleteNode={handleDeleteNode}\n\t\t/>\n\t);\n}\n",
			"type": "registry:component",
			"target": "components/flow/text-input-node-controller.tsx"
		},
		{
			"path": "./src/registry/ui/flow/prompt-crafter-node-controller.tsx",
			"content": "\"use client\";\n\nimport type { NodeProps } from \"@xyflow/react\";\nimport { useCallback } from \"react\";\nimport { toast } from \"sonner\";\nimport { useWorkflow } from \"@/registry/hooks/flow/use-workflow\";\nimport type { NodeExecutionState } from \"@/registry/lib/flow/workflow-execution-engine\";\nimport { PromptCrafterNode } from \"@/registry/ui/flow/prompt-crafter-node\";\n\nexport type PromptCrafterNodeController = Omit<PromptCrafterNode, \"data\"> & {\n\ttype: \"prompt-crafter\";\n\tdata: Omit<PromptCrafterNode[\"data\"], \"status\"> & {\n\t\texecutionState?: NodeExecutionState;\n\t};\n};\n\nexport function PromptCrafterNodeController({\n\tid,\n\tdata,\n\t...props\n}: NodeProps<PromptCrafterNodeController>) {\n\tconst updateNode = useWorkflow((state) => state.updateNode);\n\tconst addDynamicHandle = useWorkflow((state) => state.addDynamicHandle);\n\tconst removeDynamicHandle = useWorkflow(\n\t\t(state) => state.removeDynamicHandle,\n\t);\n\tconst deleteNode = useWorkflow((state) => state.deleteNode);\n\n\tconst handlePromptTextChange = useCallback(\n\t\t(value: string) => {\n\t\t\tupdateNode(id, \"prompt-crafter\", { config: { template: value } });\n\t\t},\n\t\t[id, updateNode],\n\t);\n\n\tconst handleCreateInput = useCallback(\n\t\t(name: string) => {\n\t\t\tif (!name) {\n\t\t\t\ttoast.error(\"Input name cannot be empty\");\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst existingInput = data.dynamicHandles[\"template-tags\"]?.find(\n\t\t\t\t(input) => input.name === name,\n\t\t\t);\n\t\t\tif (existingInput) {\n\t\t\t\ttoast.error(\"Input name already exists\");\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\taddDynamicHandle(id, \"prompt-crafter\", \"template-tags\", {\n\t\t\t\tname,\n\t\t\t});\n\t\t\treturn true;\n\t\t},\n\t\t[id, data.dynamicHandles, addDynamicHandle],\n\t);\n\n\tconst handleRemoveInput = useCallback(\n\t\t(handleId: string) => {\n\t\t\tremoveDynamicHandle(\n\t\t\t\tid,\n\t\t\t\t\"prompt-crafter\",\n\t\t\t\t\"template-tags\",\n\t\t\t\thandleId,\n\t\t\t);\n\t\t},\n\t\t[id, removeDynamicHandle],\n\t);\n\n\tconst handleUpdateInputName = useCallback(\n\t\t(handleId: string, newLabel: string): boolean => {\n\t\t\tif (!newLabel) {\n\t\t\t\ttoast.error(\"Input name cannot be empty\");\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst existingInput = data.dynamicHandles[\"template-tags\"]?.find(\n\t\t\t\t(input) => input.name === newLabel,\n\t\t\t);\n\t\t\tif (existingInput && existingInput.id !== handleId) {\n\t\t\t\ttoast.error(\"Input name already exists\");\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst oldInput = data.dynamicHandles[\"template-tags\"]?.find(\n\t\t\t\t(input) => input.id === handleId,\n\t\t\t);\n\t\t\tif (!oldInput) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tupdateNode(id, \"prompt-crafter\", {\n\t\t\t\tconfig: {\n\t\t\t\t\t...data.config,\n\t\t\t\t\ttemplate: (data.config.template || \"\").replace(\n\t\t\t\t\t\tnew RegExp(`{{${oldInput.name}}}`, \"g\"),\n\t\t\t\t\t\t`{{${newLabel}}}`,\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\tdynamicHandles: {\n\t\t\t\t\t...data.dynamicHandles,\n\t\t\t\t\t\"template-tags\": (\n\t\t\t\t\t\tdata.dynamicHandles[\"template-tags\"] || []\n\t\t\t\t\t).map((input) =>\n\t\t\t\t\t\tinput.id === handleId\n\t\t\t\t\t\t\t? { ...input, name: newLabel }\n\t\t\t\t\t\t\t: input,\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn true;\n\t\t},\n\t\t[id, data.dynamicHandles, data.config, updateNode],\n\t);\n\n\tconst handleDeleteNode = useCallback(() => {\n\t\tdeleteNode(id);\n\t}, [id, deleteNode]);\n\n\treturn (\n\t\t<PromptCrafterNode\n\t\t\tid={id}\n\t\t\tdata={{ ...data, status: data.executionState?.status }}\n\t\t\t{...props}\n\t\t\tonPromptTextChange={handlePromptTextChange}\n\t\t\tonCreateInput={handleCreateInput}\n\t\t\tonRemoveInput={handleRemoveInput}\n\t\t\tonUpdateInputName={handleUpdateInputName}\n\t\t\tonDeleteNode={handleDeleteNode}\n\t\t/>\n\t);\n}\n",
			"type": "registry:component",
			"target": "components/flow/prompt-crafter-node-controller.tsx"
		},
		{
			"path": "./src/registry/ui/flow/generate-text-node-controller.tsx",
			"content": "\"use client\";\n\nimport type { NodeProps } from \"@xyflow/react\";\nimport { useCallback } from \"react\";\nimport { toast } from \"sonner\";\nimport { useWorkflow } from \"@/registry/hooks/flow/use-workflow\";\nimport type { NodeExecutionState } from \"@/registry/lib/flow/workflow-execution-engine\";\nimport { GenerateTextNode } from \"@/registry/ui/flow/generate-text-node\";\nimport type { Model } from \"@/registry/ui/model-selector\";\n\nexport type GenerateTextNodeController = Omit<GenerateTextNode, \"data\"> & {\n\ttype: \"generate-text\";\n\tdata: Omit<GenerateTextNode[\"data\"], \"status\"> & {\n\t\texecutionState?: NodeExecutionState;\n\t};\n};\n\nexport function GenerateTextNodeController({\n\tid,\n\tdata,\n\t...props\n}: NodeProps<GenerateTextNodeController>) {\n\tconst updateNode = useWorkflow((state) => state.updateNode);\n\tconst addDynamicHandle = useWorkflow((state) => state.addDynamicHandle);\n\tconst removeDynamicHandle = useWorkflow(\n\t\t(state) => state.removeDynamicHandle,\n\t);\n\tconst deleteNode = useWorkflow((state) => state.deleteNode);\n\n\tconst handleModelChange = useCallback(\n\t\t(model: Model) => {\n\t\t\tupdateNode(id, \"generate-text\", {\n\t\t\t\tconfig: {\n\t\t\t\t\t...data.config,\n\t\t\t\t\tmodel,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t\t[id, data.config, updateNode],\n\t);\n\n\tconst handleCreateTool = useCallback(\n\t\t(name: string, description?: string) => {\n\t\t\tif (!name) {\n\t\t\t\ttoast.error(\"Tool name cannot be empty\");\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst existingTool = data.dynamicHandles.tools.find(\n\t\t\t\t(tool) => tool.name === name,\n\t\t\t);\n\t\t\tif (existingTool) {\n\t\t\t\ttoast.error(\"Tool name already exists\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\taddDynamicHandle(id, \"generate-text\", \"tools\", {\n\t\t\t\tname,\n\t\t\t\tdescription,\n\t\t\t});\n\t\t\treturn true;\n\t\t},\n\t\t[id, data.dynamicHandles.tools, addDynamicHandle],\n\t);\n\n\tconst handleRemoveTool = useCallback(\n\t\t(handleId: string) => {\n\t\t\tremoveDynamicHandle(id, \"generate-text\", \"tools\", handleId);\n\t\t},\n\t\t[id, removeDynamicHandle],\n\t);\n\n\tconst handleUpdateTool = useCallback(\n\t\t(toolId: string, newName: string, newDescription?: string) => {\n\t\t\tif (!newName) {\n\t\t\t\ttoast.error(\"Tool name cannot be empty\");\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst existingTool = data.dynamicHandles.tools.find(\n\t\t\t\t(tool) => tool.name === newName && tool.id !== toolId,\n\t\t\t);\n\t\t\tif (existingTool) {\n\t\t\t\ttoast.error(\"Tool name already exists\");\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tupdateNode(id, \"generate-text\", {\n\t\t\t\tdynamicHandles: {\n\t\t\t\t\t...data.dynamicHandles,\n\t\t\t\t\ttools: data.dynamicHandles.tools.map((tool) =>\n\t\t\t\t\t\ttool.id === toolId\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t...tool,\n\t\t\t\t\t\t\t\t\tname: newName,\n\t\t\t\t\t\t\t\t\tdescription: newDescription,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: tool,\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn true;\n\t\t},\n\t\t[id, data.dynamicHandles, updateNode],\n\t);\n\n\tconst handleDeleteNode = useCallback(() => {\n\t\tdeleteNode(id);\n\t}, [id, deleteNode]);\n\n\treturn (\n\t\t<GenerateTextNode\n\t\t\tid={id}\n\t\t\tdata={{\n\t\t\t\tstatus: data.executionState?.status,\n\t\t\t\tconfig: data.config,\n\t\t\t\tdynamicHandles: data.dynamicHandles,\n\t\t\t}}\n\t\t\t{...props}\n\t\t\tdisableModelSelector\n\t\t\tonModelChange={handleModelChange}\n\t\t\tonCreateTool={handleCreateTool}\n\t\t\tonRemoveTool={handleRemoveTool}\n\t\t\tonUpdateTool={handleUpdateTool}\n\t\t\tonDeleteNode={handleDeleteNode}\n\t\t/>\n\t);\n}\n",
			"type": "registry:component",
			"target": "components/flow/generate-text-node-controller.tsx"
		},
		{
			"path": "./src/registry/hooks/flow/use-workflow.ts",
			"content": "import type { Connection, EdgeChange, NodeChange } from \"@xyflow/react\";\nimport { addEdge, applyEdgeChanges, applyNodeChanges } from \"@xyflow/react\";\nimport { nanoid } from \"nanoid\";\nimport { createWithEqualityFn } from \"zustand/traditional\";\nimport { createNode } from \"@/registry/lib/flow/node-factory\";\nimport { SSEWorkflowExecutionClient } from \"@/registry/lib/flow/sse-workflow-execution-client\";\nimport {\n\ttype DynamicHandle,\n\ttype FlowEdge,\n\ttype FlowNode,\n\tisNodeOfType,\n\tisNodeWithDynamicHandles,\n\tprepareWorkflow,\n\ttype WorkflowDefinition,\n\ttype WorkflowError,\n} from \"@/registry/lib/flow/workflow\";\nimport type {\n\tEdgeExecutionState,\n\tNodeExecutionState,\n} from \"@/registry/lib/flow/workflow-execution-engine\";\n\nexport interface WorkflowState {\n\tnodes: FlowNode[];\n\tedges: FlowEdge[];\n\tonNodesChange: (changes: NodeChange<FlowNode>[]) => void;\n\tonEdgesChange: (changes: EdgeChange<FlowEdge>[]) => void;\n\tonConnect: (connection: Connection) => void;\n\tgetNodeById: (nodeId: string) => FlowNode;\n\tcreateNode: (\n\t\tnodeType: FlowNode[\"type\"],\n\t\tposition: { x: number; y: number },\n\t) => FlowNode;\n\tupdateNode: <T extends FlowNode[\"type\"]>(\n\t\tid: string,\n\t\tnodeType: T,\n\t\tdata: Partial<FlowNode[\"data\"]>,\n\t) => void;\n\tupdateNodeExecutionState: (\n\t\tnodeId: string,\n\t\tstate: Partial<NodeExecutionState> | undefined,\n\t) => void;\n\tupdateEdgeExecutionState: (\n\t\tedgeId: string,\n\t\tstate: Partial<EdgeExecutionState> | undefined,\n\t) => void;\n\tdeleteNode: (id: string) => void;\n\taddDynamicHandle: <T extends FlowNode[\"type\"]>(\n\t\tnodeId: string,\n\t\tnodeType: T,\n\t\thandleCategory: string,\n\t\thandle: Omit<DynamicHandle, \"id\">,\n\t) => string;\n\tremoveDynamicHandle: <T extends FlowNode[\"type\"]>(\n\t\tnodeId: string,\n\t\tnodeType: T,\n\t\thandleCategory: string,\n\t\thandleId: string,\n\t) => void;\n\t// Workflow validation and execution state\n\tvalidateWorkflow: () => WorkflowDefinition;\n\tworkflowExecutionState: {\n\t\tisRunning: boolean;\n\t\tfinishedAt: string | null;\n\t\terrors: WorkflowError[];\n\t\ttimesRun: number;\n\t};\n\t// execution\n\tstartExecution: () => Promise<{\n\t\tstatus: \"success\" | \"error\";\n\t\tmessage: string;\n\t\terror?: Error;\n\t\tvalidationErrors?: WorkflowError[];\n\t}>;\n\t// Initialize workflow with nodes and edges\n\tinitializeWorkflow: (nodes: FlowNode[], edges: FlowEdge[]) => void;\n}\n\nconst useWorkflow = createWithEqualityFn<WorkflowState>((set, get) => ({\n\tnodes: [],\n\tedges: [],\n\tworkflowExecutionState: {\n\t\tisRunning: false,\n\t\tfinishedAt: null,\n\t\terrors: [],\n\t\ttimesRun: 0,\n\t},\n\tinitializeWorkflow: (nodes: FlowNode[], edges: FlowEdge[]) => {\n\t\tset({ nodes, edges });\n\t\tget().validateWorkflow();\n\t},\n\tvalidateWorkflow: () => {\n\t\tconst { nodes, edges } = get();\n\t\tconst workflow = prepareWorkflow(nodes, edges);\n\n\t\t// Reset edge execution states\n\t\tfor (const edge of workflow.edges) {\n\t\t\tget().updateEdgeExecutionState(edge.id, {\n\t\t\t\terror: undefined,\n\t\t\t});\n\t\t}\n\n\t\t// Update states for errors if any\n\t\tif (workflow.errors.length > 0) {\n\t\t\tfor (const error of workflow.errors) {\n\t\t\t\tswitch (error.type) {\n\t\t\t\t\tcase \"multiple-sources-for-target-handle\":\n\t\t\t\t\tcase \"cycle\":\n\t\t\t\t\t\tfor (const edge of error.edges) {\n\t\t\t\t\t\t\tget().updateEdgeExecutionState(edge.id, {\n\t\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"missing-required-connection\":\n\t\t\t\t\t\tget().updateNodeExecutionState(error.node.id, {\n\t\t\t\t\t\t\tstatus: \"idle\",\n\t\t\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tset((state) => ({\n\t\t\tworkflowExecutionState: {\n\t\t\t\t...state.workflowExecutionState,\n\t\t\t\terrors: workflow.errors,\n\t\t\t},\n\t\t}));\n\t\treturn workflow;\n\t},\n\tonNodesChange: (changes) => {\n\t\tset({\n\t\t\tnodes: applyNodeChanges<FlowNode>(changes, get().nodes),\n\t\t});\n\t\tget().validateWorkflow();\n\t},\n\tonEdgesChange: (changes) => {\n\t\tset({\n\t\t\tedges: applyEdgeChanges(changes, get().edges),\n\t\t});\n\t\tget().validateWorkflow();\n\t},\n\tonConnect: (connection) => {\n\t\tconst newEdge = addEdge({ ...connection, type: \"status\" }, get().edges);\n\t\tconst sourceNode = get().getNodeById(connection.source);\n\n\t\tif (!connection.sourceHandle) {\n\t\t\tthrow new Error(\"Source handle not found\");\n\t\t}\n\n\t\tconst sourceExecutionState = sourceNode.data.executionState;\n\n\t\tif (sourceExecutionState?.sources) {\n\t\t\tconst sourceHandleData =\n\t\t\t\tsourceExecutionState.sources[connection.sourceHandle];\n\t\t\tconst nodes = get().nodes.map((node) => {\n\t\t\t\tif (node.id === connection.target && connection.targetHandle) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...node,\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t...node.data,\n\t\t\t\t\t\t\texecutionState: node.data.executionState\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t...node.data.executionState,\n\t\t\t\t\t\t\t\t\t\ttargets: {\n\t\t\t\t\t\t\t\t\t\t\t...node.data.executionState.targets,\n\t\t\t\t\t\t\t\t\t\t\t[connection.targetHandle]:\n\t\t\t\t\t\t\t\t\t\t\t\tsourceHandleData,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\tstatus: \"success\",\n\t\t\t\t\t\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\t\t\t\t\t\ttargets: {\n\t\t\t\t\t\t\t\t\t\t\t[connection.targetHandle]:\n\t\t\t\t\t\t\t\t\t\t\t\tsourceHandleData,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn node;\n\t\t\t});\n\n\t\t\tset({\n\t\t\t\tnodes: nodes as FlowNode[],\n\t\t\t});\n\t\t}\n\n\t\tset({\n\t\t\tedges: newEdge,\n\t\t});\n\t\tget().validateWorkflow();\n\t},\n\tgetNodeById: (nodeId) => {\n\t\tconst node = get().nodes.find((node) => node.id === nodeId);\n\t\tif (!node) {\n\t\t\tthrow new Error(`Node with id ${nodeId} not found`);\n\t\t}\n\t\treturn node;\n\t},\n\tcreateNode(nodeType, position) {\n\t\tconst newNode = createNode(nodeType, position);\n\t\tset((state) => ({\n\t\t\tnodes: [...state.nodes, newNode],\n\t\t}));\n\t\treturn newNode;\n\t},\n\tupdateNode(id, type, data) {\n\t\tset((state) => ({\n\t\t\tnodes: state.nodes.map((node) => {\n\t\t\t\tif (node.id === id && isNodeOfType(node, type)) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...node,\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t...node.data,\n\t\t\t\t\t\t\t...data,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn node;\n\t\t\t}),\n\t\t}));\n\t},\n\tupdateNodeExecutionState: (nodeId, state) => {\n\t\tset((currentState) => ({\n\t\t\tnodes: currentState.nodes.map((node) => {\n\t\t\t\tif (node.id === nodeId) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...node,\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t...node.data,\n\t\t\t\t\t\t\texecutionState: {\n\t\t\t\t\t\t\t\t...node.data?.executionState,\n\t\t\t\t\t\t\t\t...state,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t} as FlowNode;\n\t\t\t\t}\n\t\t\t\treturn node;\n\t\t\t}),\n\t\t}));\n\t},\n\tupdateEdgeExecutionState: (edgeId, state) => {\n\t\tset((currentState) => ({\n\t\t\tedges: currentState.edges.map((edge) => {\n\t\t\t\tif (edge.id === edgeId) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...edge,\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t...edge.data,\n\t\t\t\t\t\t\texecutionState: {\n\t\t\t\t\t\t\t\t...edge.data?.executionState,\n\t\t\t\t\t\t\t\t...state,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn edge;\n\t\t\t}),\n\t\t}));\n\t},\n\tdeleteNode(id) {\n\t\tset({\n\t\t\tnodes: get().nodes.filter((node) => node.id !== id),\n\t\t\tedges: get().edges.filter(\n\t\t\t\t(edge) => edge.source !== id && edge.target !== id,\n\t\t\t),\n\t\t});\n\t},\n\taddDynamicHandle(nodeId, type, handleCategory, handle) {\n\t\tconst newId = nanoid();\n\t\tset({\n\t\t\tnodes: get().nodes.map((node) => {\n\t\t\t\tif (\n\t\t\t\t\tnode.id === nodeId &&\n\t\t\t\t\tisNodeWithDynamicHandles(node) &&\n\t\t\t\t\tisNodeOfType(node, type)\n\t\t\t\t) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...node,\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t...node.data,\n\t\t\t\t\t\t\tdynamicHandles: {\n\t\t\t\t\t\t\t\t...node.data.dynamicHandles,\n\t\t\t\t\t\t\t\t[handleCategory]: [\n\t\t\t\t\t\t\t\t\t...(node.data.dynamicHandles[\n\t\t\t\t\t\t\t\t\t\thandleCategory as keyof typeof node.data.dynamicHandles\n\t\t\t\t\t\t\t\t\t] || []),\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t...handle,\n\t\t\t\t\t\t\t\t\t\tid: newId,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\treturn node;\n\t\t\t}),\n\t\t});\n\t\treturn newId;\n\t},\n\tremoveDynamicHandle(nodeId, type, handleCategory, handleId) {\n\t\tset({\n\t\t\tnodes: get().nodes.map((node) => {\n\t\t\t\tif (\n\t\t\t\t\tnode.id === nodeId &&\n\t\t\t\t\tisNodeWithDynamicHandles(node) &&\n\t\t\t\t\tisNodeOfType(node, type)\n\t\t\t\t) {\n\t\t\t\t\tconst dynamicHandles = node.data.dynamicHandles;\n\t\t\t\t\tconst handles = dynamicHandles[\n\t\t\t\t\t\thandleCategory as keyof typeof dynamicHandles\n\t\t\t\t\t] as DynamicHandle[];\n\t\t\t\t\tconst newHandles = handles.filter(\n\t\t\t\t\t\t(handle) => handle.id !== handleId,\n\t\t\t\t\t);\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...node,\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t...node.data,\n\t\t\t\t\t\t\tdynamicHandles: {\n\t\t\t\t\t\t\t\t...node.data.dynamicHandles,\n\t\t\t\t\t\t\t\t[handleCategory]: newHandles,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn node;\n\t\t\t}),\n\t\t\tedges: get().edges.filter((edge) => {\n\t\t\t\tif (edge.source === nodeId && edge.sourceHandle === handleId) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif (edge.target === nodeId && edge.targetHandle === handleId) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}),\n\t\t});\n\t},\n\t// Runtime\n\n\tasync startExecution() {\n\t\t// Check if workflow has already run successfully\n\t\tif (get().workflowExecutionState.timesRun > 3) {\n\t\t\tconst message =\n\t\t\t\t\"Workflow has already run successfully and cannot be run again\";\n\t\t\tconsole.warn(message);\n\t\t\treturn {\n\t\t\t\tstatus: \"error\",\n\t\t\t\tmessage,\n\t\t\t\terror: new Error(message),\n\t\t\t};\n\t\t}\n\n\t\t// Reset execution state for all nodes\n\t\tset((state) => ({\n\t\t\tnodes: state.nodes.map((node) => ({\n\t\t\t\t...node,\n\t\t\t\tdata: {\n\t\t\t\t\t...node.data,\n\t\t\t\t\texecutionState: {\n\t\t\t\t\t\tstatus: \"idle\",\n\t\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})) as FlowNode[],\n\t\t}));\n\n\t\tconst workflow = get().validateWorkflow();\n\n\t\tif (workflow.errors.length > 0) {\n\t\t\tconst message = \"Workflow validation failed\";\n\t\t\treturn {\n\t\t\t\tstatus: \"error\",\n\t\t\t\tmessage,\n\t\t\t\terror: new Error(message),\n\t\t\t\tvalidationErrors: workflow.errors,\n\t\t\t};\n\t\t}\n\n\t\t// Set execution state to running\n\t\tset((state) => ({\n\t\t\tworkflowExecutionState: {\n\t\t\t\t...state.workflowExecutionState,\n\t\t\t\tisRunning: true,\n\t\t\t},\n\t\t}));\n\n\t\ttry {\n\t\t\tconst sseClient = new SSEWorkflowExecutionClient();\n\t\t\tconst { updateNodeExecutionState } = get();\n\n\t\t\tawait new Promise((resolve, reject) => {\n\t\t\t\tsseClient.connect(workflow, {\n\t\t\t\t\tonNodeUpdate: (nodeId, state) => {\n\t\t\t\t\t\tupdateNodeExecutionState(nodeId, state);\n\t\t\t\t\t},\n\t\t\t\t\tonError: (error) => {\n\t\t\t\t\t\tconsole.error(\"Error in execution:\", error);\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t},\n\t\t\t\t\tonComplete: ({ timestamp }) => {\n\t\t\t\t\t\tset((state) => ({\n\t\t\t\t\t\t\tworkflowExecutionState: {\n\t\t\t\t\t\t\t\t...state.workflowExecutionState,\n\t\t\t\t\t\t\t\tfinishedAt: timestamp,\n\t\t\t\t\t\t\t\ttimesRun:\n\t\t\t\t\t\t\t\t\tstate.workflowExecutionState.timesRun + 1,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}));\n\t\t\t\t\t\tresolve(undefined);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tstatus: \"success\",\n\t\t\t\tmessage: \"Workflow executed successfully\",\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Workflow execution failed:\", error);\n\t\t\treturn {\n\t\t\t\tstatus: \"error\",\n\t\t\t\tmessage: \"Workflow execution failed\",\n\t\t\t\terror:\n\t\t\t\t\terror instanceof Error ? error : new Error(String(error)),\n\t\t\t};\n\t\t} finally {\n\t\t\t// Reset execution state when done\n\t\t\tset((state) => ({\n\t\t\t\tworkflowExecutionState: {\n\t\t\t\t\t...state.workflowExecutionState,\n\t\t\t\t\tisRunning: false,\n\t\t\t\t},\n\t\t\t}));\n\t\t}\n\t},\n}));\n\nexport { useWorkflow };\n",
			"type": "registry:hook"
		},
		{
			"path": "./src/registry/lib/flow/workflow.ts",
			"content": "import { nanoid } from \"nanoid\";\nimport type { GenerateTextNodeController } from \"@/registry/ui/flow/generate-text-node-controller\";\nimport type { PromptCrafterNodeController } from \"@/registry/ui/flow/prompt-crafter-node-controller\";\nimport type { StatusEdgeController } from \"@/registry/ui/flow/status-edge-controller\";\nimport type { TextInputNodeController } from \"@/registry/ui/flow/text-input-node-controller\";\nimport type { VisualizeTextNodeController } from \"@/registry/ui/flow/visualize-text-node-controller\";\n\ntype Dependency = {\n\tnode: string;\n\tsourceHandle: string;\n};\n\ntype Dependencies = Record<string, Dependency[]>;\n\ntype Dependent = {\n\tnode: string;\n\ttargetHandle: string;\n};\n\ntype Dependents = Record<string, Dependent[]>;\n\nexport type DependencyGraph = {\n\tdependencies: Map<string, { node: string; sourceHandle: string }[]>;\n\tdependents: Map<string, { node: string; targetHandle: string }[]>;\n};\n\nexport type ConnectionMap = Map<string, FlowEdge[]>;\n\n// Error types\n\ntype EdgeErrorInfo = {\n\tid: string;\n\tsource: string;\n\ttarget: string;\n\tsourceHandle: string;\n\ttargetHandle: string;\n};\n\nexport type MultipleSourcesError = {\n\tmessage: string;\n\ttype: \"multiple-sources-for-target-handle\";\n\tedges: EdgeErrorInfo[];\n};\n\nexport type CycleError = {\n\tmessage: string;\n\ttype: \"cycle\";\n\tedges: EdgeErrorInfo[];\n};\n\ntype NodeErrorInfo = {\n\tid: string;\n\thandleId: string;\n};\n\nexport type MissingConnectionError = {\n\tmessage: string;\n\ttype: \"missing-required-connection\";\n\tnode: NodeErrorInfo;\n};\n\nexport type WorkflowError =\n\t| MultipleSourcesError\n\t| CycleError\n\t| MissingConnectionError;\n\nexport interface WorkflowDefinition {\n\tid: string;\n\tnodes: FlowNode[];\n\tedges: FlowEdge[];\n\texecutionOrder: string[];\n\tdependencies: Dependencies;\n\tdependents: Dependents;\n\terrors: WorkflowError[];\n}\n\n// Dynamic Handles\n\nexport type DynamicHandle = {\n\tid: string;\n\tname: string;\n\tdescription?: string;\n};\n\n// Node Configuration\n\nconst NODES_CONFIG: Partial<\n\tRecord<\n\t\tFlowNode[\"type\"],\n\t\t{\n\t\t\trequiredTargets: string[];\n\t\t}\n\t>\n> = {\n\t\"generate-text\": {\n\t\trequiredTargets: [\"prompt\"],\n\t},\n};\n\n// Nodes\n\nexport type FlowNode =\n\t| VisualizeTextNodeController\n\t| TextInputNodeController\n\t| PromptCrafterNodeController\n\t| GenerateTextNodeController;\n\n// Edges\n\nexport type FlowEdge = StatusEdgeController;\n\n// Type Guards\n\nexport function isNodeOfType<T extends FlowNode[\"type\"]>(\n\tnode: FlowNode,\n\ttype: T,\n): node is Extract<FlowNode, { type: T }> {\n\treturn node.type === type;\n}\n\nexport function isNodeWithDynamicHandles<T extends FlowNode>(\n\tnode: T,\n): node is Extract<\n\tT,\n\t{\n\t\tdata: {\n\t\t\tdynamicHandles: {\n\t\t\t\t[key in string]: DynamicHandle[];\n\t\t\t};\n\t\t};\n\t}\n> {\n\treturn \"dynamicHandles\" in node.data;\n}\n\nfunction buildDependencyGraph(edges: FlowEdge[]): {\n\tdependencies: DependencyGraph[\"dependencies\"];\n\tdependents: DependencyGraph[\"dependents\"];\n\tconnectionMap: ConnectionMap;\n} {\n\tconst dependencies = new Map<\n\t\tstring,\n\t\t{ node: string; sourceHandle: string }[]\n\t>();\n\tconst dependents = new Map<\n\t\tstring,\n\t\t{ node: string; targetHandle: string }[]\n\t>();\n\tconst connectionMap = new Map<string, FlowEdge[]>();\n\n\tfor (const edge of edges) {\n\t\t// Track connections per target handle\n\t\tconst targetKey = `${edge.target}-${edge.targetHandle}`;\n\t\tconst existingConnections = connectionMap.get(targetKey) || [];\n\t\tconnectionMap.set(targetKey, [...existingConnections, edge]);\n\n\t\t// Build dependency graph\n\t\tconst existingDependencies = dependencies.get(edge.target) || [];\n\t\tdependencies.set(edge.target, [\n\t\t\t...existingDependencies,\n\t\t\t{\n\t\t\t\tnode: edge.source,\n\t\t\t\tsourceHandle: edge.sourceHandle,\n\t\t\t},\n\t\t]);\n\n\t\tconst existingDependents = dependents.get(edge.source) || [];\n\t\tdependents.set(edge.source, [\n\t\t\t...existingDependents,\n\t\t\t{\n\t\t\t\tnode: edge.target,\n\t\t\t\ttargetHandle: edge.targetHandle,\n\t\t\t},\n\t\t]);\n\t}\n\n\treturn { dependencies, dependents, connectionMap };\n}\n\nfunction topologicalSort(\n\tnodes: FlowNode[],\n\tdependencies: DependencyGraph[\"dependencies\"],\n\tdependents: DependencyGraph[\"dependents\"],\n): string[] {\n\tconst indegree = new Map<string, number>();\n\tconst queue: string[] = [];\n\tconst executionOrder: string[] = [];\n\n\t// Initialize in-degree\n\tfor (const node of nodes) {\n\t\tconst degree = dependencies.get(node.id)?.length || 0;\n\t\tindegree.set(node.id, degree);\n\t\tif (degree === 0) {\n\t\t\tqueue.push(node.id);\n\t\t}\n\t}\n\n\t// Process nodes\n\twhile (queue.length > 0) {\n\t\tconst currentNode = queue.shift();\n\t\tif (!currentNode) {\n\t\t\tcontinue;\n\t\t}\n\n\t\texecutionOrder.push(currentNode);\n\n\t\tconst nodesDependentOnCurrent = dependents.get(currentNode) || [];\n\t\tfor (const dependent of nodesDependentOnCurrent) {\n\t\t\tconst currentDegree = indegree.get(dependent.node);\n\t\t\tif (typeof currentDegree === \"number\") {\n\t\t\t\tconst newDegree = currentDegree - 1;\n\t\t\t\tindegree.set(dependent.node, newDegree);\n\t\t\t\tif (newDegree === 0) {\n\t\t\t\t\tqueue.push(dependent.node);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn executionOrder;\n}\n\nfunction validateMultipleSources(\n\tconnectionMap: ConnectionMap,\n): MultipleSourcesError[] {\n\tconst errors: MultipleSourcesError[] = [];\n\n\tconnectionMap.forEach((edges, targetKey) => {\n\t\tif (edges.length > 1) {\n\t\t\tconst [targetNode, targetHandle] = targetKey.split(\"-\");\n\t\t\terrors.push({\n\t\t\t\ttype: \"multiple-sources-for-target-handle\",\n\t\t\t\tmessage: `Target handle \"${targetHandle}\" on node \"${targetNode}\" has ${edges.length} sources.`,\n\t\t\t\tedges: edges.map((edge) => ({\n\t\t\t\t\tid: edge.id,\n\t\t\t\t\tsource: edge.source,\n\t\t\t\t\ttarget: edge.target,\n\t\t\t\t\tsourceHandle: edge.sourceHandle,\n\t\t\t\t\ttargetHandle: edge.targetHandle,\n\t\t\t\t})),\n\t\t\t});\n\t\t}\n\t});\n\n\treturn errors;\n}\n\nfunction detectCycles(\n\tnodes: FlowNode[],\n\tdependencies: DependencyGraph[\"dependencies\"],\n\tdependents: DependencyGraph[\"dependents\"],\n\tedges: FlowEdge[],\n): CycleError[] {\n\tconst executionOrder = topologicalSort(nodes, dependencies, dependents);\n\tif (executionOrder.length === nodes.length) {\n\t\treturn [];\n\t}\n\n\t// Find cycle participants\n\tconst indegree = new Map<string, number>();\n\tconst queue: string[] = [];\n\n\tfor (const node of nodes) {\n\t\tconst degree = dependencies.get(node.id)?.length || 0;\n\t\tindegree.set(node.id, degree);\n\t\tif (degree === 0) {\n\t\t\tqueue.push(node.id);\n\t\t}\n\t}\n\n\t// Kahn's algorithm to find remaining nodes\n\twhile (queue.length > 0) {\n\t\tconst currentNode = queue.shift();\n\t\tif (!currentNode) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst nodesDependentOnCurrent = dependents.get(currentNode) || [];\n\t\tfor (const dependent of nodesDependentOnCurrent) {\n\t\t\tconst currentDegree = indegree.get(dependent.node);\n\t\t\tif (typeof currentDegree === \"number\") {\n\t\t\t\tconst newDegree = currentDegree - 1;\n\t\t\t\tindegree.set(dependent.node, newDegree);\n\t\t\t\tif (newDegree === 0) {\n\t\t\t\t\tqueue.push(dependent.node);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Identify cycle nodes and edges\n\tconst cycleNodes = Array.from(indegree.entries())\n\t\t.filter(([_, degree]) => degree > 0)\n\t\t.map(([nodeId]) => nodeId);\n\n\tconst cycleEdges = edges.filter(\n\t\t(edge) =>\n\t\t\tcycleNodes.includes(edge.source) &&\n\t\t\tcycleNodes.includes(edge.target),\n\t);\n\n\tif (cycleEdges.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst error: CycleError = {\n\t\ttype: \"cycle\",\n\t\tmessage: `Workflow contains cycles between nodes: ${cycleNodes.join(\", \")}`,\n\t\tedges: cycleEdges.map((edge) => ({\n\t\t\tid: edge.id,\n\t\t\tsource: edge.source,\n\t\t\ttarget: edge.target,\n\t\t\tsourceHandle: edge.sourceHandle,\n\t\t\ttargetHandle: edge.targetHandle,\n\t\t})),\n\t};\n\n\treturn [error];\n}\n\nfunction validateRequiredHandles(\n\tnodes: FlowNode[],\n\tedges: FlowEdge[],\n): MissingConnectionError[] {\n\tconst errors: MissingConnectionError[] = [];\n\tconst connectionsByTarget = new Map<string, FlowEdge[]>();\n\tconst connectionsBySource = new Map<string, FlowEdge[]>();\n\n\t// Build connection maps\n\tfor (const edge of edges) {\n\t\tconst targetKey = `${edge.target}-${edge.targetHandle}`;\n\t\tconst sourceKey = `${edge.source}-${edge.sourceHandle}`;\n\n\t\tconst targetConnections = connectionsByTarget.get(targetKey) || [];\n\t\tconnectionsByTarget.set(targetKey, [...targetConnections, edge]);\n\n\t\tconst sourceConnections = connectionsBySource.get(sourceKey) || [];\n\t\tconnectionsBySource.set(sourceKey, [...sourceConnections, edge]);\n\t}\n\n\t// Check each node against its type configuration\n\tfor (const node of nodes) {\n\t\tconst config = NODES_CONFIG[node.type];\n\n\t\tif (!config) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Check required target handles\n\t\tif (config.requiredTargets) {\n\t\t\tfor (const targetHandle of config.requiredTargets) {\n\t\t\t\tconst key = `${node.id}-${targetHandle}`;\n\t\t\t\tconst connections = connectionsByTarget.get(key);\n\n\t\t\t\tif (!connections || connections.length === 0) {\n\t\t\t\t\terrors.push({\n\t\t\t\t\t\ttype: \"missing-required-connection\",\n\t\t\t\t\t\tmessage: `Node \"${node.id}\" requires a connection to its \"${targetHandle}\" input.`,\n\t\t\t\t\t\tnode: {\n\t\t\t\t\t\t\tid: node.id,\n\t\t\t\t\t\t\thandleId: targetHandle,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn errors;\n}\n\nexport function prepareWorkflow(\n\tnodes: FlowNode[],\n\tedges: FlowEdge[],\n): WorkflowDefinition {\n\tconst errors: WorkflowError[] = [];\n\n\t// First pass: Build dependency graph and check connection validity\n\tconst { dependencies, dependents, connectionMap } =\n\t\tbuildDependencyGraph(edges);\n\n\t/* console.log(\"dependencies\", dependencies);\n\tconsole.log(\"dependents\", dependents);\n\tconsole.log(\"connectionMap\", connectionMap);\n */\n\t// Second pass: Validate multiple sources for single target handle\n\terrors.push(...validateMultipleSources(connectionMap));\n\n\t// Third pass: Detect cycles\n\tconst cycleErrors = detectCycles(nodes, dependencies, dependents, edges);\n\terrors.push(...cycleErrors);\n\n\t// Fourth pass: Validate required handles\n\terrors.push(...validateRequiredHandles(nodes, edges));\n\n\t// Get execution order if no cycles were detected\n\tconst executionOrder =\n\t\tcycleErrors.length === 0\n\t\t\t? topologicalSort(nodes, dependencies, dependents)\n\t\t\t: [];\n\n\treturn {\n\t\tid: nanoid(),\n\t\tnodes,\n\t\tedges,\n\t\texecutionOrder,\n\t\tdependencies: Object.fromEntries(dependencies),\n\t\tdependents: Object.fromEntries(dependents),\n\t\terrors,\n\t};\n}\n",
			"type": "registry:lib"
		},
		{
			"path": "./src/registry/lib/flow/workflow-execution-engine.ts",
			"content": "import type {\n\tCycleError,\n\tFlowNode,\n\tMissingConnectionError,\n\tMultipleSourcesError,\n\tWorkflowDefinition,\n} from \"@/registry/lib/flow/workflow\";\n\n// Processing\n\nexport type ProcessingNodeError = {\n\tmessage: string;\n\ttype: \"processing-node\";\n};\n\nexport type ProcessedData = Record<string, string> | undefined;\n\nexport type NodeProcessor = (\n\tnode: FlowNode,\n\ttargetsData: ProcessedData,\n) => Promise<ProcessedData>;\n\n// Node Execution State\n\nexport type NodeExecutionStatus = \"success\" | \"error\" | \"processing\" | \"idle\";\n\nexport type NodeExecutionState = {\n\ttimestamp: string;\n\ttargets?: Record<string, string>;\n\tsources?: Record<string, string>;\n\tstatus: NodeExecutionStatus;\n\terror?: MissingConnectionError | ProcessingNodeError;\n};\n\n// Edge Execution State\n\nexport type EdgeExecutionState = {\n\terror?: MultipleSourcesError | CycleError;\n};\n\n// Excution Engine\n\ninterface ExecutionContext {\n\tworkflow: WorkflowDefinition;\n\tprocessNode: (\n\t\tnodeId: string,\n\t\ttargetsData: ProcessedData,\n\t) => Promise<ProcessedData>;\n\tupdateNodeExecutionState: (\n\t\tnodeId: string,\n\t\tstate: Partial<NodeExecutionState>,\n\t) => void;\n}\n\nexport const createWorkflowExecutionEngine = (context: ExecutionContext) => {\n\tconst completedNodes = new Set<string>();\n\tconst failedNodes = new Set<string>();\n\tconst processingNodes = new Set<string>();\n\n\tconst getNodeTargetsData = (\n\t\tworkflow: WorkflowDefinition,\n\t\tnodeId: string,\n\t): ProcessedData => {\n\t\tconst node = workflow.nodes.find((n) => n.id === nodeId);\n\t\tif (!node) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst edgesConnectedToNode = workflow.edges.filter(\n\t\t\t(edge) => edge.target === nodeId,\n\t\t);\n\n\t\tconst targetsData: ProcessedData = {};\n\t\tfor (const edge of edgesConnectedToNode) {\n\t\t\tconst sourceNode = workflow.nodes.find((n) => n.id === edge.source);\n\t\t\tif (!sourceNode?.data.executionState?.sources) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst sourceNodeResult =\n\t\t\t\tsourceNode.data.executionState.sources[edge.sourceHandle];\n\t\t\ttargetsData[edge.targetHandle] = sourceNodeResult;\n\t\t}\n\n\t\treturn targetsData;\n\t};\n\n\tconst checkBranchNodeStatus = (nodeId: string): NodeExecutionStatus => {\n\t\tconst node = context.workflow.nodes.find((n) => n.id === nodeId);\n\t\tif (!node) {\n\t\t\treturn \"idle\";\n\t\t}\n\n\t\t// If this node is processing, the whole branch is processing\n\t\tif (processingNodes.has(nodeId)) {\n\t\t\treturn \"processing\";\n\t\t}\n\n\t\t// If this node has failed, the branch has failed\n\t\tif (failedNodes.has(nodeId)) {\n\t\t\treturn \"error\";\n\t\t}\n\n\t\t// Get all nodes that this node depends on\n\t\tconst dependencies = context.workflow.dependencies[nodeId] || [];\n\n\t\t// If this node has no dependencies, check its own status\n\t\tif (dependencies.length === 0) {\n\t\t\tif (\n\t\t\t\tcompletedNodes.has(nodeId) &&\n\t\t\t\tnode.data.executionState?.sources\n\t\t\t) {\n\t\t\t\treturn \"success\";\n\t\t\t}\n\t\t\treturn \"idle\";\n\t\t}\n\n\t\t// Check status of all dependencies recursively\n\t\tfor (const dep of dependencies) {\n\t\t\tconst depStatus = checkBranchNodeStatus(dep.node);\n\t\t\t// If any dependency is processing or has error, propagate that status\n\t\t\tif (depStatus === \"processing\" || depStatus === \"error\") {\n\t\t\t\treturn depStatus;\n\t\t\t}\n\t\t}\n\n\t\t// If we got here and the node is complete with data, the branch is successful\n\t\tif (completedNodes.has(nodeId) && node.data.executionState?.sources) {\n\t\t\treturn \"success\";\n\t\t}\n\n\t\treturn \"idle\";\n\t};\n\n\tconst getBranchStatus = (\n\t\tnodeId: string,\n\t\thandleId: string,\n\t): NodeExecutionStatus => {\n\t\tconst node = context.workflow.nodes.find((n) => n.id === nodeId);\n\t\tif (!node) {\n\t\t\treturn \"idle\";\n\t\t}\n\n\t\t// Get all edges that connect to this handle\n\t\tconst incomingEdges = context.workflow.edges.filter(\n\t\t\t(edge) => edge.target === nodeId && edge.targetHandle === handleId,\n\t\t);\n\n\t\t// For each incoming edge, check the status of its source node and all its descendants\n\t\tfor (const edge of incomingEdges) {\n\t\t\tconst branchStatus = checkBranchNodeStatus(edge.source);\n\t\t\tif (branchStatus !== \"idle\") {\n\t\t\t\treturn branchStatus;\n\t\t\t}\n\t\t}\n\n\t\treturn \"idle\";\n\t};\n\n\tconst canProcessNode = (nodeId: string) => {\n\t\tconst node = context.workflow.nodes.find((n) => n.id === nodeId);\n\t\tif (!node) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Special handling for prompt-crafter nodes\n\t\tif (node.type === \"prompt-crafter\") {\n\t\t\t// Get all target handles from dynamic handles\n\t\t\tconst targetHandles = (\n\t\t\t\tnode.data.dynamicHandles?.[\"template-tags\"] || []\n\t\t\t).map((handle) => handle.id);\n\n\t\t\t// Check each target handle's branch status\n\t\t\tconst branchStatuses = targetHandles.map((handleId) =>\n\t\t\t\tgetBranchStatus(nodeId, handleId),\n\t\t\t);\n\n\t\t\t// Node can process if ALL branches are complete and NONE are processing\n\t\t\tconst allBranchesComplete = branchStatuses.every(\n\t\t\t\t(status) => status === \"success\",\n\t\t\t);\n\t\t\tconst hasProcessingBranch = branchStatuses.some(\n\t\t\t\t(status) => status === \"processing\",\n\t\t\t);\n\n\t\t\treturn allBranchesComplete && !hasProcessingBranch;\n\t\t}\n\n\t\t// For regular nodes, check all dependencies\n\t\tconst nodeDependencies = context.workflow.dependencies[nodeId] || [];\n\t\treturn nodeDependencies.every((dep) => {\n\t\t\t// Check if any dependency has failed\n\t\t\tif (failedNodes.has(dep.node)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t// Check if the node is completed AND the specific source handle has data\n\t\t\tconst sourceNode = context.workflow.nodes.find(\n\t\t\t\t(n) => n.id === dep.node,\n\t\t\t);\n\t\t\tif (!sourceNode?.data.executionState?.sources) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst sourceHandleData =\n\t\t\t\tsourceNode.data.executionState.sources[dep.sourceHandle];\n\t\t\treturn (\n\t\t\t\tcompletedNodes.has(dep.node) && sourceHandleData !== undefined\n\t\t\t);\n\t\t});\n\t};\n\n\tconst processNode = async (nodeId: string) => {\n\t\ttry {\n\t\t\tconst targetsData = getNodeTargetsData(context.workflow, nodeId);\n\n\t\t\tcontext.updateNodeExecutionState(nodeId, {\n\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\tstatus: \"processing\",\n\t\t\t\ttargets: targetsData,\n\t\t\t});\n\n\t\t\tconst result = await context.processNode(nodeId, targetsData);\n\n\t\t\tcontext.updateNodeExecutionState(nodeId, {\n\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\ttargets: targetsData,\n\t\t\t\tsources: result,\n\t\t\t\tstatus: \"success\",\n\t\t\t});\n\n\t\t\tcompletedNodes.add(nodeId);\n\t\t\tprocessingNodes.delete(nodeId);\n\t\t} catch (error) {\n\t\t\tcontext.updateNodeExecutionState(nodeId, {\n\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\tstatus: \"error\",\n\t\t\t\terror: {\n\t\t\t\t\ttype: \"processing-node\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t? error.message\n\t\t\t\t\t\t\t: \"Unknown error\",\n\t\t\t\t},\n\t\t\t});\n\t\t\tconsole.error(error);\n\t\t\tprocessingNodes.delete(nodeId);\n\t\t\tfailedNodes.add(nodeId); // Track failed nodes separately\n\t\t}\n\t};\n\n\treturn {\n\t\tasync execute(executionOrder: string[]) {\n\t\t\t// Reset tracking sets\n\t\t\tcompletedNodes.clear();\n\t\t\tfailedNodes.clear();\n\t\t\tprocessingNodes.clear();\n\n\t\t\twhile (\n\t\t\t\tcompletedNodes.size + failedNodes.size <\n\t\t\t\texecutionOrder.length\n\t\t\t) {\n\t\t\t\tconst availableNodes = executionOrder.filter(\n\t\t\t\t\t(nodeId) =>\n\t\t\t\t\t\t!completedNodes.has(nodeId) &&\n\t\t\t\t\t\t!failedNodes.has(nodeId) &&\n\t\t\t\t\t\t!processingNodes.has(nodeId) &&\n\t\t\t\t\t\tcanProcessNode(nodeId),\n\t\t\t\t);\n\n\t\t\t\tif (availableNodes.length === 0) {\n\t\t\t\t\tif (processingNodes.size > 0) {\n\t\t\t\t\t\tawait new Promise((resolve) =>\n\t\t\t\t\t\t\tsetTimeout(resolve, 100),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\t// If there are no available nodes and nothing is processing,\n\t\t\t\t\t// but we haven't completed all nodes, it means some nodes\n\t\t\t\t\t// couldn't execute due to failed dependencies\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tconst processingPromises = availableNodes.map((nodeId) => {\n\t\t\t\t\tprocessingNodes.add(nodeId);\n\t\t\t\t\treturn processNode(nodeId);\n\t\t\t\t});\n\n\t\t\t\tawait Promise.race(processingPromises);\n\t\t\t}\n\t\t},\n\t};\n};\n",
			"type": "registry:lib"
		},
		{
			"path": "./src/registry/lib/flow/sse-workflow-execution-client.ts",
			"content": "import type { WorkflowDefinition } from \"@/registry/lib/flow/workflow\";\nimport type { NodeExecutionState } from \"@/registry/lib/flow/workflow-execution-engine\";\n\nexport interface SSEWorkflowExecutionEventHandlers {\n\tonNodeUpdate: (nodeId: string, state: NodeExecutionState) => void;\n\tonError: (error: Error, nodeId?: string) => void;\n\tonComplete: ({ timestamp }: { timestamp: string }) => void;\n}\n\nexport class SSEWorkflowExecutionClient {\n\tprivate abortController: AbortController | null = null;\n\tprivate reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n\n\tasync connect(\n\t\tworkflow: WorkflowDefinition,\n\t\thandlers: SSEWorkflowExecutionEventHandlers,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tthis.abortController = new AbortController();\n\n\t\t\tconst response = await fetch(\"/api/flow/execute\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\tAccept: \"text/event-stream\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({ workflow }),\n\t\t\t\tsignal: this.abortController.signal,\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`HTTP error! status: ${response.status}`);\n\t\t\t}\n\n\t\t\tif (!response.body) {\n\t\t\t\tthrow new Error(\"Response body is null\");\n\t\t\t}\n\n\t\t\tthis.reader = response.body.getReader();\n\t\t\tconst decoder = new TextDecoder();\n\t\t\tlet buffer = \"\";\n\n\t\t\t// eslint-disable-next-line no-constant-condition\n\t\t\twhile (true) {\n\t\t\t\tconst { done, value } = await this.reader.read();\n\t\t\t\tif (done) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\t\tconst lines = buffer.split(\"\\n\\n\");\n\t\t\t\tbuffer = lines.pop() || \"\";\n\n\t\t\t\tfor (const line of lines) {\n\t\t\t\t\tif (line.startsWith(\"data: \")) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst data = JSON.parse(line.slice(6));\n\n\t\t\t\t\t\t\tswitch (data.type) {\n\t\t\t\t\t\t\t\tcase \"nodeUpdate\": {\n\t\t\t\t\t\t\t\t\thandlers.onNodeUpdate(\n\t\t\t\t\t\t\t\t\t\tdata.nodeId,\n\t\t\t\t\t\t\t\t\t\tdata.executionState,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase \"error\": {\n\t\t\t\t\t\t\t\t\thandlers.onError(new Error(data.error));\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase \"complete\": {\n\t\t\t\t\t\t\t\t\thandlers.onComplete({\n\t\t\t\t\t\t\t\t\t\ttimestamp: data.timestamp,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tthis.disconnect();\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconsole.error(\"Error parsing SSE data:\", error);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error && error.name === \"AbortError\") {\n\t\t\t\t// Ignore abort errors as they are expected when disconnecting\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thandlers.onError(\n\t\t\t\terror instanceof Error\n\t\t\t\t\t? error\n\t\t\t\t\t: new Error(\"SSE connection failed\"),\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.disconnect();\n\t\t}\n\t}\n\n\tdisconnect(): void {\n\t\tif (this.reader) {\n\t\t\tthis.reader.cancel();\n\t\t\tthis.reader = null;\n\t\t}\n\t\tif (this.abortController) {\n\t\t\tthis.abortController.abort();\n\t\t\tthis.abortController = null;\n\t\t}\n\t}\n}\n",
			"type": "registry:lib"
		},
		{
			"path": "./src/registry/lib/flow/sse-workflow-execution-engine.ts",
			"content": "import type {\n\tFlowNode,\n\tWorkflowDefinition,\n} from \"@/registry/lib/flow/workflow\";\nimport {\n\tcreateWorkflowExecutionEngine,\n\ttype NodeExecutionState,\n\ttype NodeProcessor,\n} from \"@/registry/lib/flow/workflow-execution-engine\";\n\nfunction createEvent(type: string, data: Record<string, unknown>) {\n\treturn `data: ${JSON.stringify({ type, ...data })}\\n\\n`;\n}\n\nfunction createSSEWorkflowExecutionEngine(\n\tworkflow: WorkflowDefinition,\n\tnodeProcessor: Record<FlowNode[\"type\"], NodeProcessor>,\n\tcontroller: ReadableStreamDefaultController,\n) {\n\treturn createWorkflowExecutionEngine({\n\t\tworkflow,\n\t\tprocessNode: async (nodeId, targetsData) => {\n\t\t\tconst node = workflow.nodes.find((n) => n.id === nodeId);\n\t\t\tif (!node) {\n\t\t\t\tthrow new Error(`Node ${nodeId} not found`);\n\t\t\t}\n\n\t\t\tconst processor = nodeProcessor[node.type];\n\t\t\treturn await processor(node, targetsData);\n\t\t},\n\t\tupdateNodeExecutionState: (\n\t\t\tnodeId,\n\t\t\tstate: Partial<NodeExecutionState>,\n\t\t) => {\n\t\t\tconst node = workflow.nodes.find((n) => n.id === nodeId);\n\t\t\tif (!node) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tnode.data.executionState = {\n\t\t\t\t...node.data.executionState,\n\t\t\t\t...state,\n\t\t\t} as NodeExecutionState;\n\n\t\t\t// Send node update event\n\t\t\tcontroller.enqueue(\n\t\t\t\tcreateEvent(\"nodeUpdate\", {\n\t\t\t\t\tnodeId,\n\t\t\t\t\texecutionState: node.data.executionState,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tif (state.status === \"error\") {\n\t\t\t\tcontroller.enqueue(\n\t\t\t\t\tcreateEvent(\"error\", {\n\t\t\t\t\t\terror: state.error,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t});\n}\n\nexport async function executeServerWorkflow(\n\tworkflow: WorkflowDefinition,\n\tnodeProcessor: Record<FlowNode[\"type\"], NodeProcessor>,\n\tcontroller: ReadableStreamDefaultController,\n) {\n\tconst engine = createSSEWorkflowExecutionEngine(\n\t\tworkflow,\n\t\tnodeProcessor,\n\t\tcontroller,\n\t);\n\n\ttry {\n\t\tawait engine.execute(workflow.executionOrder);\n\t\tcontroller.enqueue(\n\t\t\tcreateEvent(\"complete\", { timestamp: new Date().toISOString() }),\n\t\t);\n\t} catch (error) {\n\t\tcontroller.enqueue(\n\t\t\tcreateEvent(\"error\", {\n\t\t\t\terror: error instanceof Error ? error.message : \"Unknown error\",\n\t\t\t}),\n\t\t);\n\t} finally {\n\t\tcontroller.close();\n\t}\n}\n",
			"type": "registry:lib"
		},
		{
			"path": "./src/registry/lib/flow/server-node-processors.ts",
			"content": "import { generateAIText } from \"@/registry/lib/flow/generate-ai-text\";\nimport type { FlowNode } from \"@/registry/lib/flow/workflow\";\nimport type { NodeProcessor } from \"@/registry/lib/flow/workflow-execution-engine\";\nimport type { GenerateTextNode } from \"@/registry/ui/flow/generate-text-node\";\nimport type { PromptCrafterNode } from \"@/registry/ui/flow/prompt-crafter-node\";\nimport type { TextInputNode } from \"@/registry/ui/flow/text-input-node\";\n\nexport const serverNodeProcessors: Record<FlowNode[\"type\"], NodeProcessor> = {\n\t\"text-input\": async (node) => {\n\t\tconst textNode = node as TextInputNode;\n\t\treturn {\n\t\t\tresult: textNode.data.config.value,\n\t\t};\n\t},\n\n\t\"prompt-crafter\": async (node, targetsData) => {\n\t\tconst promptNode = node as PromptCrafterNode;\n\t\tif (!targetsData) {\n\t\t\tthrow new Error(\"Targets data not found\");\n\t\t}\n\n\t\tlet parsedTemplate = promptNode.data.config.template;\n\t\tfor (const [targetId, targetValue] of Object.entries(targetsData)) {\n\t\t\tconst tag = promptNode.data.dynamicHandles[\"template-tags\"].find(\n\t\t\t\t(handle) => handle.id === targetId,\n\t\t\t);\n\t\t\tif (!tag) {\n\t\t\t\tthrow new Error(`Tag with id ${targetId} not found`);\n\t\t\t}\n\t\t\tparsedTemplate = parsedTemplate.replaceAll(\n\t\t\t\t`{{${tag.name}}}`,\n\t\t\t\ttargetValue,\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\tresult: parsedTemplate,\n\t\t};\n\t},\n\n\t\"generate-text\": async (node, targetsData) => {\n\t\tconst generateNode = node as GenerateTextNode;\n\t\tconst system = targetsData?.system;\n\t\tconst prompt = targetsData?.prompt;\n\t\tif (!prompt) {\n\t\t\tthrow new Error(\"Prompt not found\");\n\t\t}\n\n\t\tconst result = await generateAIText({\n\t\t\tprompt,\n\t\t\tsystem,\n\t\t\tmodel: generateNode.data.config.model,\n\t\t\ttools: generateNode.data.dynamicHandles.tools,\n\t\t});\n\n\t\treturn result.parsedResult;\n\t},\n\n\t\"visualize-text\": async () => {\n\t\treturn undefined;\n\t},\n};\n",
			"type": "registry:lib"
		},
		{
			"path": "./src/registry/lib/flow/node-factory.ts",
			"content": "import { nanoid } from \"nanoid\";\nimport type { FlowNode } from \"@/registry/lib/flow/workflow\";\nimport type { GenerateTextNodeController } from \"@/registry/ui/flow/generate-text-node-controller\";\nimport type { PromptCrafterNodeController } from \"@/registry/ui/flow/prompt-crafter-node-controller\";\nimport type { TextInputNodeController } from \"@/registry/ui/flow/text-input-node-controller\";\nimport type { VisualizeTextNodeController } from \"@/registry/ui/flow/visualize-text-node-controller\";\n\nexport type NodePosition = {\n\tx: number;\n\ty: number;\n};\n\nexport const nodeFactory = {\n\t\"generate-text\": (position: NodePosition): GenerateTextNodeController => ({\n\t\tid: nanoid(),\n\t\ttype: \"generate-text\",\n\t\tposition,\n\t\tdata: {\n\t\t\tconfig: {\n\t\t\t\tmodel: \"llama-3.1-8b-instant\",\n\t\t\t},\n\t\t\tdynamicHandles: {\n\t\t\t\ttools: [],\n\t\t\t},\n\t\t},\n\t}),\n\n\t\"prompt-crafter\": (\n\t\tposition: NodePosition,\n\t): PromptCrafterNodeController => ({\n\t\tid: nanoid(),\n\t\ttype: \"prompt-crafter\",\n\t\tposition,\n\t\tdata: {\n\t\t\tconfig: {\n\t\t\t\ttemplate: \"\",\n\t\t\t},\n\t\t\tdynamicHandles: {\n\t\t\t\t\"template-tags\": [],\n\t\t\t},\n\t\t},\n\t}),\n\n\t\"visualize-text\": (\n\t\tposition: NodePosition,\n\t): VisualizeTextNodeController => ({\n\t\tid: nanoid(),\n\t\ttype: \"visualize-text\",\n\t\tposition,\n\t\tdata: {},\n\t\twidth: 350,\n\t\theight: 300,\n\t}),\n\n\t\"text-input\": (position: NodePosition): TextInputNodeController => ({\n\t\tid: nanoid(),\n\t\ttype: \"text-input\",\n\t\tposition,\n\t\tdata: {\n\t\t\tconfig: {\n\t\t\t\tvalue: \"\",\n\t\t\t},\n\t\t},\n\t\twidth: 350,\n\t\theight: 300,\n\t}),\n};\n\nexport function createNode(\n\tnodeType: FlowNode[\"type\"],\n\tposition: NodePosition,\n): FlowNode {\n\tif (!nodeType) {\n\t\tthrow new Error(\"Node type is required\");\n\t}\n\tconst factory = nodeFactory[nodeType];\n\tif (!factory) {\n\t\tthrow new Error(`Unknown node type: ${nodeType}`);\n\t}\n\treturn factory(position);\n}\n",
			"type": "registry:lib"
		},
		{
			"path": "./src/registry/lib/flow/generate-ai-text.ts",
			"content": "import { deepseek } from \"@ai-sdk/deepseek\";\nimport { groq } from \"@ai-sdk/groq\";\nimport { openai } from \"@ai-sdk/openai\";\nimport { generateText } from \"ai\";\nimport { z } from \"zod\";\nimport type { GenerateTextNode } from \"@/registry/ui/flow/generate-text-node\";\nimport type { Model } from \"@/registry/ui/model-selector\";\n\ninterface ToolResult {\n\tid: string;\n\tname: string;\n\tresult: string;\n}\n\nfunction createAIClient(model: Model) {\n\tswitch (model) {\n\t\tcase \"deepseek-chat\":\n\t\t\treturn deepseek;\n\t\tcase \"llama-3.3-70b-versatile\":\n\t\tcase \"llama-3.1-8b-instant\":\n\t\tcase \"deepseek-r1-distill-llama-70b\":\n\t\t\treturn groq;\n\t\tcase \"gpt-4o\":\n\t\tcase \"gpt-4o-mini\":\n\t\t\treturn openai;\n\t\tdefault:\n\t\t\tthrow new Error(`Unsupported model: ${model}`);\n\t}\n}\n\nfunction mapToolsForAI(\n\ttools: GenerateTextNode[\"data\"][\"dynamicHandles\"][\"tools\"],\n) {\n\treturn Object.fromEntries(\n\t\ttools.map((toolToMap) => [\n\t\t\ttoolToMap.name,\n\t\t\t{\n\t\t\t\tdescription: toolToMap.description,\n\t\t\t\tinputSchema: z.object({\n\t\t\t\t\ttoolValue: z.string(),\n\t\t\t\t}),\n\t\t\t\texecute: async ({ toolValue }: { toolValue: string }) =>\n\t\t\t\t\ttoolValue,\n\t\t\t},\n\t\t]),\n\t);\n}\n\nexport async function generateAIText({\n\tprompt,\n\tsystem,\n\tmodel,\n\ttools,\n}: {\n\tprompt: string;\n\tsystem?: string;\n\tmodel: Model;\n\ttools: GenerateTextNode[\"data\"][\"dynamicHandles\"][\"tools\"];\n}) {\n\tconst client = createAIClient(model);\n\tconst mappedTools = mapToolsForAI(tools);\n\n\tconst result = await generateText({\n\t\tmodel: client(model),\n\t\tsystem,\n\t\tprompt,\n\t\t...(tools.length > 0 && {\n\t\t\ttools: mappedTools,\n\t\t\tmaxSteps: 1,\n\t\t\ttoolChoice: \"required\",\n\t\t}),\n\t});\n\n\tlet toolResults: ToolResult[] = [];\n\tif (tools.length > 0 && result.toolResults) {\n\t\ttoolResults = result.toolResults.map((step) => {\n\t\t\tconst originalTool = tools.find(\n\t\t\t\t(tool) => tool.name === step.toolName,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tid: originalTool?.id || \"\",\n\t\t\t\tname: step.toolName,\n\t\t\t\tdescription: originalTool?.description || \"\",\n\t\t\t\tresult: step.output as string,\n\t\t\t};\n\t\t});\n\t}\n\n\tconst parsedResult: Record<string, string> = {\n\t\tresult: result.text,\n\t};\n\n\tfor (const toolResult of toolResults) {\n\t\tparsedResult[toolResult.id] = toolResult.result;\n\t}\n\n\treturn {\n\t\ttext: result.text,\n\t\ttoolResults,\n\t\ttotalTokens: result.usage?.totalTokens,\n\t\tparsedResult,\n\t};\n}\n",
			"type": "registry:lib"
		}
	],
	"envVars": {
		"OPENAI_API_KEY": "",
		"GROQ_API_KEY": "",
		"DEEPSEEK_API_KEY": ""
	},
	"docs": "Get API keys from OpenAI (https://platform.openai.com/), Groq (https://console.groq.com/), and DeepSeek (https://platform.deepseek.com/) and add them to the environment variables file and run the dev server. \n\nLearn more about how to use the Vercel AI SDK https://ai-sdk.dev/docs/introduction.",
	"categories": ["flow"]
}
