From 01de5267ded01b87149a85ccb5d73ce8f0a254a3 Mon Sep 17 00:00:00 2001 From: Krishna Mohan Date: Fri, 30 Jan 2026 15:31:27 +0530 Subject: [PATCH 1/2] fix: Fixed configuring the entry point component Signed-off-by: Krishna Mohan --- .../src/components/workflow/ConfigPanel.tsx | 159 ++++++++++++++++++ .../workflow-builder/WorkflowBuilder.tsx | 28 +++ .../hooks/useWorkflowRunner.tsx | 7 +- 3 files changed, 193 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/workflow/ConfigPanel.tsx b/frontend/src/components/workflow/ConfigPanel.tsx index dbdc174f..4ace1e04 100644 --- a/frontend/src/components/workflow/ConfigPanel.tsx +++ b/frontend/src/components/workflow/ConfigPanel.tsx @@ -785,6 +785,165 @@ export function ConfigPanel({
{componentInputs.map((input, index) => { + // Special handling for Entry Point's __runtimeData input + // Render individual fields for each runtime input definition + if (isEntryPointComponent && input.id === '__runtimeData') { + const runtimeDataValue = + typeof inputOverrides.__runtimeData === 'object' && + inputOverrides.__runtimeData !== null + ? (inputOverrides.__runtimeData as Record) + : {}; + + const handleRuntimeDataFieldChange = (fieldId: string, value: unknown) => { + const updatedRuntimeData = { ...runtimeDataValue }; + if (value === undefined || value === '' || value === null) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete updatedRuntimeData[fieldId]; + } else { + updatedRuntimeData[fieldId] = value; + } + // Store as empty object to avoid validation issues, or the full object + const finalValue = + Object.keys(updatedRuntimeData).length > 0 ? updatedRuntimeData : undefined; + handleInputOverrideChange('__runtimeData', finalValue); + }; + + // If no runtime inputs defined, show a helpful message + if (runtimeInputDefinitions.length === 0) { + return ( +
0 && 'border-t border-border')} + > +
+
+ {input.label} +
+ + json + +
+

+ Add runtime inputs in the Parameters section below to define default + values here. +

+
+ ); + } + + // Render a field for each runtime input definition + return ( +
0 && 'border-t border-border')} + > +
+
+ {input.label} +
+ + json + +
+

+ Set default values for runtime inputs. These will be used when the + workflow is triggered without providing values. +

+
+ {runtimeInputDefinitions.map((runtimeInput: any) => { + const fieldId = runtimeInput.id; + const fieldLabel = runtimeInput.label || fieldId; + const fieldType = runtimeInput.type || 'text'; + const fieldDescription = runtimeInput.description; + const fieldRequired = runtimeInput.required ?? true; + const currentValue = runtimeDataValue[fieldId]; + const hasValue = + currentValue !== undefined && + currentValue !== null && + currentValue !== ''; + + return ( +
+
+
+ {fieldLabel} + {fieldRequired && ( + + * + + )} +
+ + {fieldType} + +
+ {fieldDescription && ( +

+ {fieldDescription} +

+ )} + { + const nextValue = e.target.value; + if (nextValue === '') { + handleRuntimeDataFieldChange(fieldId, undefined); + return; + } + if (fieldType === 'number') { + const parsed = Number(nextValue); + if (!Number.isNaN(parsed)) { + handleRuntimeDataFieldChange(fieldId, parsed); + } + } else if (fieldType === 'json') { + // Store as string, will be parsed at runtime + try { + const parsed = JSON.parse(nextValue); + handleRuntimeDataFieldChange(fieldId, parsed); + } catch { + // Keep as string if not valid JSON + handleRuntimeDataFieldChange(fieldId, nextValue); + } + } else { + handleRuntimeDataFieldChange(fieldId, nextValue); + } + }} + placeholder={`Enter default ${fieldType} value`} + className="text-sm" + /> +
+ {hasValue ? ( +
+ + Value set +
+ ) : ( + + Leave blank to require a port connection. + + )} +
+
+ ); + })} +
+
+ ); + } + + // Standard input handling for non-Entry Point components const connection = nodeData.inputs?.[input.id]; const hasConnection = Boolean(connection); const manualValue = inputOverrides[input.id]; diff --git a/frontend/src/features/workflow-builder/WorkflowBuilder.tsx b/frontend/src/features/workflow-builder/WorkflowBuilder.tsx index 238db5cf..c22be2fc 100644 --- a/frontend/src/features/workflow-builder/WorkflowBuilder.tsx +++ b/frontend/src/features/workflow-builder/WorkflowBuilder.tsx @@ -764,6 +764,33 @@ function WorkflowBuilderContent() { return []; }, [getComponent, nodes]); + // Resolve default values from Entry Point's __runtimeData input override + const resolveRuntimeInputDefaults = useCallback((): Record => { + const triggerNode = nodes.find((node) => { + const nodeData = node.data as any; + const componentRef = nodeData.componentId ?? nodeData.componentSlug; + const component = getComponent(componentRef); + return component?.id === 'core.workflow.entrypoint'; + }); + + if (!triggerNode) { + return {}; + } + + const nodeData = triggerNode.data as any; + const runtimeDataOverride = nodeData.config?.inputOverrides?.__runtimeData; + + if ( + runtimeDataOverride && + typeof runtimeDataOverride === 'object' && + !Array.isArray(runtimeDataOverride) + ) { + return runtimeDataOverride as Record; + } + + return {}; + }, [getComponent, nodes]); + const { runDialogOpen, setRunDialogOpen, @@ -782,6 +809,7 @@ function WorkflowBuilderContent() { setNodes, toast, resolveRuntimeInputDefinitions, + resolveRuntimeInputDefaults, fetchRuns, markClean, navigate, diff --git a/frontend/src/features/workflow-builder/hooks/useWorkflowRunner.tsx b/frontend/src/features/workflow-builder/hooks/useWorkflowRunner.tsx index 01d4e0b3..8a6fd36d 100644 --- a/frontend/src/features/workflow-builder/hooks/useWorkflowRunner.tsx +++ b/frontend/src/features/workflow-builder/hooks/useWorkflowRunner.tsx @@ -31,6 +31,7 @@ interface UseWorkflowRunnerOptions { setNodes: Dispatch[]>>; toast: ToastFn; resolveRuntimeInputDefinitions: () => any[]; + resolveRuntimeInputDefaults: () => Record; fetchRuns: (params: { workflowId: string; force?: boolean }) => Promise; markClean: () => void; navigate: (path: string, options?: { replace?: boolean }) => void; @@ -63,6 +64,7 @@ export function useWorkflowRunner({ setNodes, toast, resolveRuntimeInputDefinitions, + resolveRuntimeInputDefaults, fetchRuns, markClean, navigate, @@ -199,7 +201,9 @@ export function useWorkflowRunner({ const runtimeDefinitions = resolveRuntimeInputDefinitions(); if (runtimeDefinitions.length > 0) { setRuntimeInputs(runtimeDefinitions); - setPrefilledRuntimeValues({}); + // Use default values from Entry Point's __runtimeData input override + const defaultValues = resolveRuntimeInputDefaults(); + setPrefilledRuntimeValues(defaultValues); setPendingVersionId(null); setRunDialogOpen(true); return; @@ -213,6 +217,7 @@ export function useWorkflowRunner({ metadata.id, nodes.length, resolveRuntimeInputDefinitions, + resolveRuntimeInputDefaults, toast, ]); From c464c74fb25ee8fe1aa13d40641f7775a6610529 Mon Sep 17 00:00:00 2001 From: Krishna Mohan Date: Mon, 9 Feb 2026 18:29:36 +0530 Subject: [PATCH 2/2] refactor: entry point node , removed the input field and added default input value in parameter Signed-off-by: Krishna Mohan --- .../src/components/workflow/ConfigPanel.tsx | 633 +++++++----------- .../workflow/RuntimeInputsEditor.tsx | 56 ++ .../workflow-builder/WorkflowBuilder.tsx | 21 +- 3 files changed, 311 insertions(+), 399 deletions(-) diff --git a/frontend/src/components/workflow/ConfigPanel.tsx b/frontend/src/components/workflow/ConfigPanel.tsx index a9b54f48..adad6197 100644 --- a/frontend/src/components/workflow/ConfigPanel.tsx +++ b/frontend/src/components/workflow/ConfigPanel.tsx @@ -962,66 +962,96 @@ export function ConfigPanel({ )} - {/* Inputs Section */} - {componentInputs.length > 0 && ( - -
- {componentInputs.map((input, index) => { - // Special handling for Entry Point's __runtimeData input - // Render individual fields for each runtime input definition - if (isEntryPointComponent && input.id === '__runtimeData') { - const runtimeDataValue = - typeof inputOverrides.__runtimeData === 'object' && - inputOverrides.__runtimeData !== null - ? (inputOverrides.__runtimeData as Record) - : {}; - - const handleRuntimeDataFieldChange = (fieldId: string, value: unknown) => { - const updatedRuntimeData = { ...runtimeDataValue }; - if (value === undefined || value === '' || value === null) { - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete updatedRuntimeData[fieldId]; - } else { - updatedRuntimeData[fieldId] = value; - } - // Store as empty object to avoid validation issues, or the full object - const finalValue = - Object.keys(updatedRuntimeData).length > 0 ? updatedRuntimeData : undefined; - handleInputOverrideChange('__runtimeData', finalValue); - }; - - // If no runtime inputs defined, show a helpful message - if (runtimeInputDefinitions.length === 0) { - return ( -
0 && 'border-t border-border')} - > -
-
- {input.label} -
- - json - -
-

- Add runtime inputs in the Parameters section below to define default - values here. -

-
- ); + {/* Inputs Section - hide for Entry Point if only __runtimeData exists */} + {componentInputs.length > 0 && + !(isEntryPointComponent && componentInputs.every((i) => i.id === '__runtimeData')) && ( + i.id !== '__runtimeData').length + : componentInputs.length + } + defaultOpen={true} + > +
+ {componentInputs.map((input, index) => { + // Skip __runtimeData input for Entry Point - default values are now set in the RuntimeInputsEditor + if (isEntryPointComponent && input.id === '__runtimeData') { + return null; + } + + // Filter out non-credential inputs in tool mode + if (isToolMode && !isCredentialInput(input)) { + return null; } - // Render a field for each runtime input definition + // Handle tools port with multiple connections + const isToolsPort = input.id === 'tools'; + const toolEdges = isToolsPort + ? getEdges().filter( + (edge) => + edge.target === selectedNode.id && edge.targetHandle === 'tools', + ) + : []; + const connection = isToolsPort ? undefined : nodeData.inputs?.[input.id]; + const hasConnection = isToolsPort ? toolEdges.length > 0 : Boolean(connection); + const manualValue = inputOverrides[input.id]; + const manualOverridesPort = input.valuePriority === 'manual-first'; + const allowsManualInput = + inputSupportsManualValue(input) || manualOverridesPort; + const manualValueProvided = + allowsManualInput && + (!hasConnection || manualOverridesPort) && + manualValue !== undefined && + manualValue !== null && + (typeof manualValue === 'string' ? manualValue.trim().length > 0 : true); + const manualLocked = hasConnection && !manualOverridesPort; + const connectedSourceLabels = isToolsPort + ? toolEdges + .map((edge) => { + const sourceNode = getNodes().find((n) => n.id === edge.source); + return (sourceNode?.data as any)?.label || edge.source; + }) + .filter(Boolean) + : connection + ? [connection.source] + : []; + const connectedSummary = (() => { + if (connectedSourceLabels.length === 0) return ''; + if (connectedSourceLabels.length <= 2) { + return connectedSourceLabels.join(', '); + } + return `${connectedSourceLabels.slice(0, 2).join(', ')} +${ + connectedSourceLabels.length - 2 + }`; + })(); + const portType = resolvePortType(input); + const primitiveName = portType?.kind === 'primitive' ? portType.name : null; + const isNumberInput = primitiveName === 'number'; + const isBooleanInput = primitiveName === 'boolean'; + const isListOfTextInput = isListOfTextPort(portType); + const manualInputValue = + manualValue === undefined || manualValue === null + ? '' + : typeof manualValue === 'string' + ? manualValue + : String(manualValue); + const isSecretInput = input.editor === 'secret' || primitiveName === 'secret'; + const useSecretSelect = isSecretInput; + const manualPlaceholder = useSecretSelect + ? 'Select a secret...' + : input.id === 'supabaseUrl' + ? 'https://.supabase.co or ' + : isNumberInput + ? 'Enter a number to use without a connection' + : isListOfTextInput + ? 'Add entries or press Add to provide a list' + : 'Enter text to use without a connection'; + const typeLabel = describePortType(portType); + return (
{input.label} + {input.required && ( + * + )}
- json + {typeLabel}
-

- Set default values for runtime inputs. These will be used when the - workflow is triggered without providing values. -

-
- {runtimeInputDefinitions.map((runtimeInput: any) => { - const fieldId = runtimeInput.id; - const fieldLabel = runtimeInput.label || fieldId; - const fieldType = runtimeInput.type || 'text'; - const fieldDescription = runtimeInput.description; - const fieldRequired = runtimeInput.required ?? true; - const currentValue = runtimeDataValue[fieldId]; - const hasValue = - currentValue !== undefined && - currentValue !== null && - currentValue !== ''; - - return ( -
-
-
- {fieldLabel} - {fieldRequired && ( - - * - - )} -
- - {fieldType} - -
- {fieldDescription && ( -

- {fieldDescription} -

- )} - + {input.description} +

+ )} + + {allowsManualInput && ( +
+ + {useSecretSelect ? ( + { + // Handle both undefined (from clear button) and empty string + if (value === undefined || value === '' || value === null) { + handleInputOverrideChange(input.id, undefined); + } else { + handleInputOverrideChange(input.id, value); + } + }} + placeholder={manualPlaceholder} + className="text-sm" + disabled={manualLocked} + /> + ) : isBooleanInput ? ( +
+ + {!manualLocked && typeof manualValue === 'boolean' && ( + + )}
- ); - })} -
-
- ); - } - - // Filter out non-credential inputs in tool mode - if (isToolMode && !isCredentialInput(input)) { - return null; - } - - // Handle tools port with multiple connections - const isToolsPort = input.id === 'tools'; - const toolEdges = isToolsPort - ? getEdges().filter( - (edge) => edge.target === selectedNode.id && edge.targetHandle === 'tools', - ) - : []; - const connection = isToolsPort ? undefined : nodeData.inputs?.[input.id]; - const hasConnection = isToolsPort ? toolEdges.length > 0 : Boolean(connection); - const manualValue = inputOverrides[input.id]; - const manualOverridesPort = input.valuePriority === 'manual-first'; - const allowsManualInput = inputSupportsManualValue(input) || manualOverridesPort; - const manualValueProvided = - allowsManualInput && - (!hasConnection || manualOverridesPort) && - manualValue !== undefined && - manualValue !== null && - (typeof manualValue === 'string' ? manualValue.trim().length > 0 : true); - const manualLocked = hasConnection && !manualOverridesPort; - const connectedSourceLabels = isToolsPort - ? toolEdges - .map((edge) => { - const sourceNode = getNodes().find((n) => n.id === edge.source); - return (sourceNode?.data as any)?.label || edge.source; - }) - .filter(Boolean) - : connection - ? [connection.source] - : []; - const connectedSummary = (() => { - if (connectedSourceLabels.length === 0) return ''; - if (connectedSourceLabels.length <= 2) { - return connectedSourceLabels.join(', '); - } - return `${connectedSourceLabels.slice(0, 2).join(', ')} +${ - connectedSourceLabels.length - 2 - }`; - })(); - const portType = resolvePortType(input); - const primitiveName = portType?.kind === 'primitive' ? portType.name : null; - const isNumberInput = primitiveName === 'number'; - const isBooleanInput = primitiveName === 'boolean'; - const isListOfTextInput = isListOfTextPort(portType); - const manualInputValue = - manualValue === undefined || manualValue === null - ? '' - : typeof manualValue === 'string' - ? manualValue - : String(manualValue); - const isSecretInput = input.editor === 'secret' || primitiveName === 'secret'; - const useSecretSelect = isSecretInput; - const manualPlaceholder = useSecretSelect - ? 'Select a secret...' - : input.id === 'supabaseUrl' - ? 'https://.supabase.co or ' - : isNumberInput - ? 'Enter a number to use without a connection' - : isListOfTextInput - ? 'Add entries or press Add to provide a list' - : 'Enter text to use without a connection'; - const typeLabel = describePortType(portType); - - return ( -
0 && 'border-t border-border')} - > -
-
- {input.label} - {input.required && ( - * - )} -
- - {typeLabel} - -
- {input.description && ( -

- {input.description} -

- )} - - {allowsManualInput && ( -
- - {useSecretSelect ? ( - { - // Handle both undefined (from clear button) and empty string - if (value === undefined || value === '' || value === null) { - handleInputOverrideChange(input.id, undefined); - } else { - handleInputOverrideChange(input.id, value); - } - }} - placeholder={manualPlaceholder} - className="text-sm" - disabled={manualLocked} - /> - ) : isBooleanInput ? ( -
- - {!manualLocked && typeof manualValue === 'boolean' && ( - - )} -
- ) : isListOfTextInput ? ( - handleInputOverrideChange(input.id, value)} - /> - ) : component?.id === 'core.artifact.writer' && - input.id === 'artifactName' ? ( - { - if (!value || value === '') { - handleInputOverrideChange(input.id, undefined); - } else { - handleInputOverrideChange(input.id, value); - } - }} - disabled={manualLocked} - placeholder="{{run_id}}-{{timestamp}}" - /> - ) : ( - { - const nextValue = e.target.value; - if (nextValue === '') { - handleInputOverrideChange(input.id, undefined); - return; - } - if (isNumberInput) { - const parsed = Number(nextValue); - if (Number.isNaN(parsed)) { + placeholder="{{run_id}}-{{timestamp}}" + /> + ) : ( + { + const nextValue = e.target.value; + if (nextValue === '') { + handleInputOverrideChange(input.id, undefined); return; } - handleInputOverrideChange(input.id, parsed); - } else { - handleInputOverrideChange(input.id, nextValue); - } - }} - placeholder={manualPlaceholder} - className="text-sm" - disabled={manualLocked} - /> - )} - {/* Skip helper text for DynamicArtifactNameInput as it has its own */} - {!( - component?.id === 'core.artifact.writer' && input.id === 'artifactName' - ) && - (manualLocked ? ( -

- Disconnect the port to edit manual input. -

- ) : ( -

- {isBooleanInput - ? 'Select a value or clear manual input to require a port connection.' - : isListOfTextInput - ? 'Add entries or clear manual input to require a port connection.' - : 'Leave blank to require a port connection.'} -

- ))} -
- )} - - {/* Connection status - compact */} -
- {manualValueProvided ? ( -
- - Value set -
- ) : hasConnection ? ( -
- - Connected from {connectedSummary || connection?.source} -
- ) : input.required ? ( -
- - Required + if (isNumberInput) { + const parsed = Number(nextValue); + if (Number.isNaN(parsed)) { + return; + } + handleInputOverrideChange(input.id, parsed); + } else { + handleInputOverrideChange(input.id, nextValue); + } + }} + placeholder={manualPlaceholder} + className="text-sm" + disabled={manualLocked} + /> + )} + {/* Skip helper text for DynamicArtifactNameInput as it has its own */} + {!( + component?.id === 'core.artifact.writer' && + input.id === 'artifactName' + ) && + (manualLocked ? ( +

+ Disconnect the port to edit manual input. +

+ ) : ( +

+ {isBooleanInput + ? 'Select a value or clear manual input to require a port connection.' + : isListOfTextInput + ? 'Add entries or clear manual input to require a port connection.' + : 'Leave blank to require a port connection.'} +

+ ))}
- ) : ( - Optional )} + + {/* Connection status - compact */} +
+ {manualValueProvided ? ( +
+ + Value set +
+ ) : hasConnection ? ( +
+ + Connected from {connectedSummary || connection?.source} +
+ ) : input.required ? ( +
+ + Required +
+ ) : ( + Optional + )} +
-
- ); - })} -
- - )} + ); + })} +
+
+ )} {!isToolMode && component.agentTool?.enabled && diff --git a/frontend/src/components/workflow/RuntimeInputsEditor.tsx b/frontend/src/components/workflow/RuntimeInputsEditor.tsx index f4ebfaad..af7a5032 100644 --- a/frontend/src/components/workflow/RuntimeInputsEditor.tsx +++ b/frontend/src/components/workflow/RuntimeInputsEditor.tsx @@ -24,6 +24,7 @@ interface RuntimeInput { type: RuntimeInputType; required: boolean; description?: string; + defaultValue?: string | number | boolean | object | null; } interface RuntimeInputsEditorProps { @@ -214,6 +215,61 @@ export function RuntimeInputsEditor({ value, onChange }: RuntimeInputsEditorProp
+ {/* Default Value Field */} +
+ + { + const nextValue = e.target.value; + if (nextValue === '') { + updateInput(index, 'defaultValue', undefined); + return; + } + if (input.type === 'number') { + const parsed = Number(nextValue); + if (!Number.isNaN(parsed)) { + updateInput(index, 'defaultValue', parsed); + } + } else if (input.type === 'json' || input.type === 'array') { + // Try to parse as JSON, otherwise store as string + try { + const parsed = JSON.parse(nextValue); + updateInput(index, 'defaultValue', parsed); + } catch { + updateInput(index, 'defaultValue', nextValue); + } + } else { + updateInput(index, 'defaultValue', nextValue); + } + }} + placeholder={ + input.type === 'array' + ? '["item1", "item2"]' + : input.type === 'json' + ? '{"key": "value"}' + : input.type === 'number' + ? '0' + : 'Enter default value' + } + className="h-8 text-xs" + /> +

+ Value used when workflow is triggered without this input +

+
+ {/* Output Port Preview */}
diff --git a/frontend/src/features/workflow-builder/WorkflowBuilder.tsx b/frontend/src/features/workflow-builder/WorkflowBuilder.tsx index 052c12c4..6c7a0656 100644 --- a/frontend/src/features/workflow-builder/WorkflowBuilder.tsx +++ b/frontend/src/features/workflow-builder/WorkflowBuilder.tsx @@ -768,7 +768,7 @@ function WorkflowBuilderContent() { return []; }, [getComponent, nodes]); - // Resolve default values from Entry Point's __runtimeData input override + // Resolve default values from Entry Point's runtimeInputs parameter (defaultValue field) const resolveRuntimeInputDefaults = useCallback((): Record => { const triggerNode = nodes.find((node) => { const nodeData = node.data as any; @@ -782,14 +782,17 @@ function WorkflowBuilderContent() { } const nodeData = triggerNode.data as any; - const runtimeDataOverride = nodeData.config?.inputOverrides?.__runtimeData; - - if ( - runtimeDataOverride && - typeof runtimeDataOverride === 'object' && - !Array.isArray(runtimeDataOverride) - ) { - return runtimeDataOverride as Record; + const runtimeInputsParam = nodeData.config?.params?.runtimeInputs; + + // Extract default values from each runtime input definition + if (Array.isArray(runtimeInputsParam)) { + const defaults: Record = {}; + for (const input of runtimeInputsParam) { + if (input?.id && input.defaultValue !== undefined && input.defaultValue !== null) { + defaults[input.id] = input.defaultValue; + } + } + return defaults; } return {};