import { useMemo, type ChangeEvent } from 'react' import type { WorkflowNodeDefinition, WorkflowNodeFieldDefinition, WorkflowParams } from '../../api/workflows' import { FAMILY_FILTER_LABELS, FAMILY_FILTER_STYLES, getDefinitionFamily, getDefinitionModuleLabel, groupDefinitionsForStepSelect, isDefinitionAllowedForGraphFamily, type WorkflowGraphFamily, } from './workflowNodeLibrary' import { WorkflowNodeContractCard } from './WorkflowNodeContractCard' function groupFieldsBySection(fields: WorkflowNodeFieldDefinition[]) { return fields.reduce>((sections, field) => { const section = field.section || 'General' sections[section] = [...(sections[section] ?? []), field] return sections }, {}) } function getContractValues(contract: Record | undefined, key: string): string[] { const value = contract?.[key] if (!Array.isArray(value)) return [] return value.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0) } function getContractContextLabel(contract: Record | undefined): string | null { const value = contract?.context if (value !== 'cad_file' && value !== 'order_line') return null return value === 'cad_file' ? 'CAD File' : 'Order Line' } type WorkflowNodeInspectorProps = { params: WorkflowParams onChange: (params: WorkflowParams) => void nodeDefinition?: WorkflowNodeDefinition step?: string onStepChange?: (step: string) => void nodeDefinitions: WorkflowNodeDefinition[] graphFamily: WorkflowGraphFamily } export function WorkflowNodeInspector({ params, onChange, nodeDefinition, step, onStepChange, nodeDefinitions, graphFamily, }: WorkflowNodeInspectorProps) { const customRenderSettingsEnabled = Boolean(params.use_custom_render_settings) const selectableNodeDefinitions = useMemo( () => nodeDefinitions.filter(definition => isDefinitionAllowedForGraphFamily(definition, graphFamily), ), [graphFamily, nodeDefinitions], ) const nodeSelectionGroups = groupDefinitionsForStepSelect(selectableNodeDefinitions) const updateField = (field: WorkflowNodeFieldDefinition, value: unknown) => { onChange({ ...params, [field.key]: value, }) } const handleNumberChange = (field: WorkflowNodeFieldDefinition, event: ChangeEvent) => { const rawValue = event.target.value if (rawValue === '') { const nextParams = { ...params } delete nextParams[field.key] onChange(nextParams) return } updateField(field, Number(rawValue)) } const fieldsBySection = groupFieldsBySection(nodeDefinition?.fields ?? []) const inputContextLabel = getContractContextLabel(nodeDefinition?.input_contract as Record | undefined) const outputContextLabel = getContractContextLabel(nodeDefinition?.output_contract as Record | undefined) const requiredInputs = getContractValues(nodeDefinition?.input_contract as Record | undefined, 'requires') const providedOutputs = getContractValues(nodeDefinition?.output_contract as Record | undefined, 'provides') const consumedArtifacts = nodeDefinition?.artifact_roles_consumed ?? [] const producedArtifacts = nodeDefinition?.artifact_roles_produced ?? [] return (

Node Configuration

{nodeDefinitions.length > 0 && onStepChange && (
{nodeDefinition && (

{nodeDefinition.description}

{graphFamily !== 'mixed' && (

Step selection is scoped to {FAMILY_FILTER_LABELS[graphFamily]} nodes for this workflow.

)} {nodeDefinition.execution_kind === 'bridge' ? 'Legacy Bridge' : 'Native Node'}
)}
)} {nodeDefinition && ( )} {Object.keys(fieldsBySection).length === 0 && (

This node currently has no configurable settings in the editor.

)} {Object.entries(fieldsBySection).map(([section, fields]) => (

{section}

{fields.map(field => { const rawValue = params[field.key] const value = rawValue ?? field.default const disableRenderOverrideField = (step === 'blender_still' || step === 'blender_turntable') && !customRenderSettingsEnabled && field.key !== 'use_custom_render_settings' && (field.section === 'Render' || field.section === 'Output') return (
{field.type === 'select' && ( )} {field.type === 'number' && ( handleNumberChange(field, event)} disabled={disableRenderOverrideField} className="w-full border border-border-default rounded-lg px-3 py-2 text-sm bg-surface text-content focus:outline-none focus:ring-2 focus:ring-accent" /> )} {field.type === 'boolean' && ( )} {field.type === 'text' && ( updateField(field, event.target.value)} disabled={disableRenderOverrideField} className="w-full border border-border-default rounded-lg px-3 py-2 text-sm bg-surface text-content focus:outline-none focus:ring-2 focus:ring-accent" /> )} {field.description && (

{field.description}

)} {disableRenderOverrideField && (

In Graph/Shadow mode this field inherits from Output Type and Template until Custom Render Settings is enabled.

)}
) })}
))}
) }