{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "prompt-crafter-node",
	"type": "registry:ui",
	"title": "Prompt Crafter Node",
	"description": "A React Flow node component for building dynamic prompts by combining multiple inputs using a template-based approach.",
	"dependencies": [
		"@xyflow/react",
		"@uiw/react-codemirror",
		"@uiw/codemirror-themes",
		"@lezer/highlight",
		"@codemirror/language",
		"@codemirror/view"
	],
	"registryDependencies": [
		"button",
		"separator",
		"popover",
		"command",
		"@simple-ai/node-header-status",
		"@simple-ai/editable-handle",
		"@simple-ai/node-header",
		"@simple-ai/labeled-handle",
		"@simple-ai/base-node"
	],
	"files": [
		{
			"path": "./src/registry/ui/flow/prompt-crafter-node.tsx",
			"content": "\"use client\";\n\nimport { StreamLanguage } from \"@codemirror/language\";\nimport type { EditorView } from \"@codemirror/view\";\nimport { tags as t } from \"@lezer/highlight\";\nimport { createTheme } from \"@uiw/codemirror-themes\";\nimport CodeMirror from \"@uiw/react-codemirror\";\nimport type { Node } from \"@xyflow/react\";\nimport {\n\ttype NodeProps,\n\tPosition,\n\tuseUpdateNodeInternals,\n} from \"@xyflow/react\";\nimport { BetweenVerticalEnd, PencilRuler, Plus, Trash } from \"lucide-react\";\nimport { useCallback, useMemo, useRef, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tCommand,\n\tCommandEmpty,\n\tCommandGroup,\n\tCommandInput,\n\tCommandItem,\n\tCommandList,\n} from \"@/components/ui/command\";\nimport {\n\tPopover,\n\tPopoverContent,\n\tPopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\nimport { BaseNode } from \"@/registry/ui/flow/base-node\";\nimport {\n\tEditableHandle,\n\tEditableHandleDialog,\n} from \"@/registry/ui/flow/editable-handle\";\nimport { LabeledHandle } from \"@/registry/ui/flow/labeled-handle\";\nimport {\n\tNodeHeader,\n\tNodeHeaderAction,\n\tNodeHeaderActions,\n\tNodeHeaderIcon,\n\tNodeHeaderTitle,\n} from \"@/registry/ui/flow/node-header\";\nimport { NodeHeaderStatus } from \"@/registry/ui/flow/node-header-status\";\n\ntype PromptCrafterData = {\n\tstatus: \"processing\" | \"error\" | \"success\" | \"idle\" | undefined;\n\tconfig: {\n\t\ttemplate: string;\n\t};\n\tdynamicHandles: {\n\t\t\"template-tags\": {\n\t\t\tid: string;\n\t\t\tname: string;\n\t\t}[];\n\t};\n};\n\nexport type PromptCrafterNode = Node<PromptCrafterData, \"prompt-crafter\">;\n\ninterface PromptCrafterProps extends NodeProps<PromptCrafterNode> {\n\tonPromptTextChange: (value: string) => void;\n\tonCreateInput: (name: string) => boolean;\n\tonRemoveInput: (handleId: string) => void;\n\tonUpdateInputName: (handleId: string, newLabel: string) => boolean;\n\tonDeleteNode: () => void;\n}\n\n// Custom theme that matches your app's design\nconst promptTheme = createTheme({\n\ttheme: \"dark\",\n\tsettings: {\n\t\tbackground: \"transparent\",\n\t\tforeground: \"hsl(var(--foreground))\",\n\t\tcaret: \"black\",\n\t\tselection: \"#3B82F6\",\n\t\tlineHighlight: \"transparent\",\n\t},\n\tstyles: [\n\t\t{ tag: t.variableName, color: \"#10c43d\" },\n\t\t{ tag: t.string, color: \"hsl(var(--foreground))\" },\n\t\t{ tag: t.invalid, color: \"#DC2626\" },\n\t],\n});\n\n// Create a function to generate the language with the current inputs\nconst createPromptLanguage = (validInputs: string[] = []) =>\n\tStreamLanguage.define({\n\t\ttoken(stream) {\n\t\t\tif (stream.match(/{{[^}]*}}/)) {\n\t\t\t\tconst match = stream.current();\n\t\t\t\tconst inputName = match.slice(2, -2);\n\t\t\t\t// Check if the input name is valid\n\t\t\t\tif (validInputs.includes(inputName)) {\n\t\t\t\t\treturn \"variableName\";\n\t\t\t\t}\n\t\t\t\treturn \"invalid\";\n\t\t\t}\n\t\t\tstream.next();\n\t\t\treturn null;\n\t\t},\n\t});\n\nexport function PromptCrafterNode({\n\tid,\n\tselected,\n\tdeletable,\n\tdata,\n\tonPromptTextChange,\n\tonCreateInput,\n\tonRemoveInput,\n\tonUpdateInputName,\n\tonDeleteNode,\n}: PromptCrafterProps) {\n\tconst updateNodeInternals = useUpdateNodeInternals();\n\tconst [isPopoverOpen, setIsPopoverOpen] = useState(false);\n\tconst editorViewRef = useRef<EditorView | null>(null);\n\n\tconst handleCreateInput = useCallback(\n\t\t(name: string) => {\n\t\t\tconst result = onCreateInput(name);\n\t\t\tif (result) {\n\t\t\t\tupdateNodeInternals(id);\n\t\t\t}\n\t\t\treturn result;\n\t\t},\n\t\t[onCreateInput, id, updateNodeInternals],\n\t);\n\n\tconst handleRemoveInput = useCallback(\n\t\t(handleId: string) => {\n\t\t\tonRemoveInput(handleId);\n\t\t\tupdateNodeInternals(id);\n\t\t},\n\t\t[onRemoveInput, id, updateNodeInternals],\n\t);\n\n\tconst handleUpdateInputName = useCallback(\n\t\t(handleId: string, newLabel: string) => {\n\t\t\tconst result = onUpdateInputName(handleId, newLabel);\n\t\t\tif (result) {\n\t\t\t\tupdateNodeInternals(id);\n\t\t\t}\n\t\t\treturn result;\n\t\t},\n\t\t[onUpdateInputName, id, updateNodeInternals],\n\t);\n\n\tconst insertInputAtCursor = useCallback((inputName: string) => {\n\t\tconst view = editorViewRef.current;\n\t\tif (!view) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst inputTag = `{{${inputName}}}`;\n\t\tconst from = view.state.selection.main.from;\n\t\tview.dispatch({\n\t\t\tchanges: { from, insert: inputTag },\n\t\t\tselection: { anchor: from + inputTag.length },\n\t\t});\n\t\tsetIsPopoverOpen(false);\n\t}, []);\n\n\t// Create language with current inputs\n\tconst extensions = useMemo(() => {\n\t\tconst validLabels = (data.dynamicHandles[\"template-tags\"] || []).map(\n\t\t\t(input) => input.name,\n\t\t);\n\t\treturn [createPromptLanguage(validLabels)];\n\t}, [data.dynamicHandles[\"template-tags\"]]);\n\n\treturn (\n\t\t<BaseNode\n\t\t\tselected={selected}\n\t\t\tclassName={cn(\"w-[350px] p-0 hover:ring-orange-500\", {\n\t\t\t\t\"border-orange-500\": data.status === \"processing\",\n\t\t\t\t\"border-red-500\": data.status === \"error\",\n\t\t\t})}\n\t\t>\n\t\t\t<NodeHeader className=\"m-0\">\n\t\t\t\t<NodeHeaderIcon>\n\t\t\t\t\t<PencilRuler />\n\t\t\t\t</NodeHeaderIcon>\n\t\t\t\t<NodeHeaderTitle>Prompt Crafter</NodeHeaderTitle>\n\t\t\t\t<NodeHeaderActions>\n\t\t\t\t\t<NodeHeaderStatus status={data.status} />\n\t\t\t\t\t{deletable && (\n\t\t\t\t\t\t<NodeHeaderAction\n\t\t\t\t\t\t\tonClick={onDeleteNode}\n\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\tlabel=\"Delete node\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Trash />\n\t\t\t\t\t\t</NodeHeaderAction>\n\t\t\t\t\t)}\n\t\t\t\t</NodeHeaderActions>\n\t\t\t</NodeHeader>\n\t\t\t<Separator />\n\t\t\t<div className=\"p-2\">\n\t\t\t\t<div className=\"flex items-center gap-2 mb-1\">\n\t\t\t\t\t<Popover\n\t\t\t\t\t\topen={isPopoverOpen}\n\t\t\t\t\t\tonOpenChange={setIsPopoverOpen}\n\t\t\t\t\t>\n\t\t\t\t\t\t<PopoverTrigger asChild>\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\tclassName=\"h-7 px-2\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<BetweenVerticalEnd className=\"h-4 w-4 mr-1\" />\n\t\t\t\t\t\t\t\tInsert Input into Prompt\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</PopoverTrigger>\n\t\t\t\t\t\t<PopoverContent className=\"p-0\" align=\"start\">\n\t\t\t\t\t\t\t<Command>\n\t\t\t\t\t\t\t\t<CommandInput placeholder=\"Search inputs...\" />\n\t\t\t\t\t\t\t\t<CommandList>\n\t\t\t\t\t\t\t\t\t<CommandEmpty>\n\t\t\t\t\t\t\t\t\t\tNo inputs found.\n\t\t\t\t\t\t\t\t\t</CommandEmpty>\n\t\t\t\t\t\t\t\t\t<CommandGroup>\n\t\t\t\t\t\t\t\t\t\t{data.dynamicHandles[\n\t\t\t\t\t\t\t\t\t\t\t\"template-tags\"\n\t\t\t\t\t\t\t\t\t\t]?.map(\n\t\t\t\t\t\t\t\t\t\t\t(input) =>\n\t\t\t\t\t\t\t\t\t\t\t\tinput.name && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<CommandItem\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tkey={input.id}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tonSelect={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tinsertInputAtCursor(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tinput.name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-base\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{input.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</CommandItem>\n\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t</CommandGroup>\n\t\t\t\t\t\t\t\t</CommandList>\n\t\t\t\t\t\t\t</Command>\n\t\t\t\t\t\t</PopoverContent>\n\t\t\t\t\t</Popover>\n\t\t\t\t</div>\n\t\t\t\t<CodeMirror\n\t\t\t\t\tvalue={data.config.template || \"\"}\n\t\t\t\t\theight=\"150px\"\n\t\t\t\t\ttheme={promptTheme}\n\t\t\t\t\textensions={extensions}\n\t\t\t\t\tonChange={onPromptTextChange}\n\t\t\t\t\tonCreateEditor={(view) => {\n\t\t\t\t\t\teditorViewRef.current = view;\n\t\t\t\t\t}}\n\t\t\t\t\tclassName=\"nodrag border rounded-md overflow-hidden [&_.cm-content]:!cursor-text [&_.cm-line]:!cursor-text nodrag nopan nowheel\"\n\t\t\t\t\tplaceholder=\"Craft your prompt here... Use {{input-name}} to reference inputs\"\n\t\t\t\t\tbasicSetup={{\n\t\t\t\t\t\tlineNumbers: false,\n\t\t\t\t\t\tfoldGutter: false,\n\t\t\t\t\t\tdropCursor: false,\n\t\t\t\t\t\tallowMultipleSelections: false,\n\t\t\t\t\t\tindentOnInput: false,\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<div className=\"grid grid-cols-[2fr,1fr] pb-2 text-sm gap-4\">\n\t\t\t\t<div className=\"flex flex-col min-w-0\">\n\t\t\t\t\t<div className=\"flex items-center justify-between py-2 px-4 bg-muted rounded-r-xl\">\n\t\t\t\t\t\t<span className=\"text-sm font-medium\">Inputs</span>\n\t\t\t\t\t\t<EditableHandleDialog\n\t\t\t\t\t\t\tvariant=\"create\"\n\t\t\t\t\t\t\tlabel=\"\"\n\t\t\t\t\t\t\tonSave={handleCreateInput}\n\t\t\t\t\t\t\tonCancel={() => {}}\n\t\t\t\t\t\t\talign=\"end\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\tclassName=\"w-fit h-7 px-2 mx-1\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<Plus className=\"h-4 w-4 mr-1\" />\n\t\t\t\t\t\t\t\tNew Input\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</EditableHandleDialog>\n\t\t\t\t\t</div>\n\t\t\t\t\t{data.dynamicHandles[\"template-tags\"]?.map((input) => (\n\t\t\t\t\t\t<EditableHandle\n\t\t\t\t\t\t\tkey={input.id}\n\t\t\t\t\t\t\tnodeId={id}\n\t\t\t\t\t\t\thandleId={input.id}\n\t\t\t\t\t\t\tname={input.name}\n\t\t\t\t\t\t\ttype=\"target\"\n\t\t\t\t\t\t\tposition={Position.Left}\n\t\t\t\t\t\t\twrapperClassName=\"w-full\"\n\t\t\t\t\t\t\tonUpdateTool={handleUpdateInputName}\n\t\t\t\t\t\t\tonDelete={handleRemoveInput}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t\t<div className=\"self-stretch border-l border-border flex items-center justify-end\">\n\t\t\t\t\t<LabeledHandle\n\t\t\t\t\t\tid=\"result\"\n\t\t\t\t\t\ttitle=\"Result\"\n\t\t\t\t\t\ttype=\"source\"\n\t\t\t\t\t\tposition={Position.Right}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</BaseNode>\n\t);\n}\n",
			"type": "registry:ui",
			"target": "components/flow/prompt-crafter-node.tsx"
		}
	]
}
