@ -180,6 +180,7 @@ export function PromptConstructorNode({ id, data, selected }: NodeProps<PromptCo
< BaseNode
id = { id }
selected = { selected }
fullBleed
>
{ /* Text input handle */ }
< Handle
@ -187,76 +188,76 @@ export function PromptConstructorNode({ id, data, selected }: NodeProps<PromptCo
position = { Position . Left }
id = "text"
data - handletype = "text"
style = { { zIndex : 10 } }
/ >
< div className = "relative flex flex-col gap-2 flex-1" >
{ /* Warning badge for unresolved variables */ }
{ unresolvedVars . length > 0 && (
< div className = "px-2 py-1 bg-amber-900/30 border border-amber-700/50 rounded text-[10px] text-amber-400" >
< span className = "font-semibold" > Unresolved : < / span > { unresolvedVars . map ( v = > ` @ ${ v } ` ) . join ( ', ' ) }
< / div >
) }
{ /* Template textarea with autocomplete */ }
< div className = "relative flex-1 flex flex-col" >
< textarea
ref = { textareaRef }
value = { localTemplate }
onChange = { handleChange }
onFocus = { handleFocus }
onBlur = { handleBlur }
onKeyDown = { handleKeyDown }
placeholder = "Type @ to insert variables..."
className = "nodrag nopan nowheel w-full flex-1 min-h-[70px] p-2 text-xs leading-relaxed text-neutral-100 border border-neutral-700 rounded bg-neutral-900/50 resize-none focus:outline-none focus:ring-1 focus:ring-neutral-600 focus:border-neutral-600 placeholder:text-neutral-500"
title = { resolvedPreview ? ` Preview: ${ resolvedPreview } ` : undefined }
/ >
{ /* Autocomplete dropdown */ }
{ showAutocomplete && filteredAutocompleteVars . length > 0 && (
< div
className = "absolute z-10 bg-neutral-800 border border-neutral-600 rounded shadow-xl max-h-40 overflow-y-auto"
style = { {
top : autocompletePosition.top ,
left : autocompletePosition.left ,
} }
>
{ filteredAutocompleteVars . map ( ( variable , index ) = > (
< button
key = { variable . nodeId }
onMouseDown = { ( e ) = > {
e . preventDefault ( ) ;
handleAutocompleteSelect ( variable . name ) ;
} }
className = { ` w-full px-3 py-2 text-left text-[11px] flex flex-col gap-0.5 transition-colors ${
index === selectedAutocompleteIndex
? "bg-neutral-700 text-neutral-100"
: "text-neutral-300 hover:bg-neutral-700"
} ` }
>
< div className = "font-medium text-blue-400" > @ { variable . name } < / div >
< div className = "text-neutral-500 truncate max-w-[200px]" >
{ variable . value || "(empty)" }
< / div >
< / button >
) ) }
< / div >
) }
{ /* Warning badge for unresolved variables - overlay at top */ }
{ unresolvedVars . length > 0 && (
< div className = "absolute top-2 left-2 right-2 z-20 px-2 py-1 bg-amber-900/80 backdrop-blur-sm border border-amber-700/50 rounded text-[10px] text-amber-400 pointer-events-none" >
< span className = "font-semibold" > Unresolved : < / span > { unresolvedVars . map ( v = > ` @ ${ v } ` ) . join ( ', ' ) }
< / div >
) }
{ /* Available variables info */ }
{ availableVariables . length > 0 && (
< div className = "text-[10px] text-neutral-500 px-2" >
Available : { availableVariables . map ( v = > ` @ ${ v . name } ` ) . join ( ', ' ) }
{ /* Template textarea with autocomplete */ }
< div className = "relative w-full h-full" >
< textarea
ref = { textareaRef }
value = { localTemplate }
onChange = { handleChange }
onFocus = { handleFocus }
onBlur = { handleBlur }
onKeyDown = { handleKeyDown }
placeholder = "Type @ to insert variables..."
className = { ` nodrag nopan nowheel w-full h-full p-3 text-xs leading-relaxed text-neutral-100 bg-neutral-800 rounded-lg resize-none focus:outline-none placeholder:text-neutral-500 ${ availableVariables . length > 0 ? "pb-7" : "" } ` }
title = { resolvedPreview ? ` Preview: ${ resolvedPreview } ` : undefined }
/ >
{ /* Autocomplete dropdown */ }
{ showAutocomplete && filteredAutocompleteVars . length > 0 && (
< div
className = "absolute z-20 bg-neutral-800 border border-neutral-600 rounded shadow-xl max-h-40 overflow-y-auto"
style = { {
top : autocompletePosition.top ,
left : autocompletePosition.left ,
} }
>
{ filteredAutocompleteVars . map ( ( variable , index ) = > (
< button
key = { variable . nodeId }
onMouseDown = { ( e ) = > {
e . preventDefault ( ) ;
handleAutocompleteSelect ( variable . name ) ;
} }
className = { ` w-full px-3 py-2 text-left text-[11px] flex flex-col gap-0.5 transition-colors ${
index === selectedAutocompleteIndex
? "bg-neutral-700 text-neutral-100"
: "text-neutral-300 hover:bg-neutral-700"
} ` }
>
< div className = "font-medium text-blue-400" > @ { variable . name } < / div >
< div className = "text-neutral-500 truncate max-w-[200px]" >
{ variable . value || "(empty)" }
< / div >
< / button >
) ) }
< / div >
) }
< / div >
{ /* Available variables - fixed footer pinned at bottom */ }
{ availableVariables . length > 0 && (
< div className = "absolute bottom-0 left-0 right-0 z-10 px-3 py-1.5 bg-neutral-900/80 backdrop-blur-sm rounded-b-lg text-[10px] text-neutral-500 pointer-events-none" >
Available : { availableVariables . map ( v = > ` @ ${ v . name } ` ) . join ( ', ' ) }
< / div >
) }
{ /* Text output handle */ }
< Handle
type = "source"
position = { Position . Right }
id = "text"
data - handletype = "text"
style = { { zIndex : 10 } }
/ >
< / BaseNode >