From 3d69baed4032bce98c8f81294211f9b357b13fc9 Mon Sep 17 00:00:00 2001 From: j-sanaa Date: Mon, 27 Apr 2026 09:15:16 -0700 Subject: [PATCH] Fix/agentflow Fixing agentflow UI style and make it similar to v2 (#6280) * Fix output node not selectable * Fix highlight color * Fix previous node displaying label and not ID * Fix tool arguements not being called * Fix dropdown values to not be bold * Add standard sizing for all input text * FIx syntax highlighting styling * Use predefined tokens for font weight and size * Fix dialog size of Source * Fix color of syntax highlight in dark mode * Fix the input fileds color in dark and regular mode * Fix gemini comments * FIx inconsistent spacing * Standardise gap between label and field * Remove export for unused function and standardize dialog size accrosss code, rich text, and source * Fix test error --- .../agentflow/src/atoms/ArrayInput.test.tsx | 171 +++++++++++++++++- packages/agentflow/src/atoms/ArrayInput.tsx | 41 ++++- .../agentflow/src/atoms/ConditionBuilder.tsx | 4 +- .../agentflow/src/atoms/ExpandTextDialog.tsx | 6 +- .../agentflow/src/atoms/MessagesInput.tsx | 4 +- .../agentflow/src/atoms/NodeInputHandler.tsx | 18 +- .../agentflow/src/atoms/RichTextEditor.tsx | 3 +- .../src/atoms/StateKeyValueInput.tsx | 5 +- .../src/atoms/StructuredOutputBuilder.tsx | 4 +- .../src/atoms/SuggestionDropdown.tsx | 4 +- .../agentflow/src/atoms/VariableInput.tsx | 16 +- .../core/theme/createAgentflowTheme.test.ts | 4 +- .../src/core/theme/createAgentflowTheme.ts | 46 ++++- packages/agentflow/src/core/theme/tokens.ts | 24 ++- packages/agentflow/src/core/types/node.ts | 1 + .../canvas/components/ValidationFeedback.tsx | 12 +- .../features/generator/GenerateFlowDialog.tsx | 3 +- .../node-editor/CreateCredentialDialog.tsx | 5 +- .../useAvailableVariables.test.tsx | 2 +- .../node-editor/useAvailableVariables.ts | 1 - 20 files changed, 320 insertions(+), 54 deletions(-) diff --git a/packages/agentflow/src/atoms/ArrayInput.test.tsx b/packages/agentflow/src/atoms/ArrayInput.test.tsx index e3a8d41f2..d9f2f9a45 100644 --- a/packages/agentflow/src/atoms/ArrayInput.test.tsx +++ b/packages/agentflow/src/atoms/ArrayInput.test.tsx @@ -3,6 +3,7 @@ import { fireEvent, render, screen } from '@testing-library/react' import type { InputParam, NodeData } from '@/core/types' import { ArrayInput } from './ArrayInput' +import type { VariableItem } from './VariablePicker' // --- Mocks --- const mockOnDataChange = jest.fn() @@ -10,13 +11,20 @@ const mockOnDataChange = jest.fn() jest.mock('./NodeInputHandler', () => ({ NodeInputHandler: ({ inputParam, - onDataChange + data, + onDataChange, + variableItems }: { inputParam: InputParam data: NodeData onDataChange: (args: { inputParam: InputParam; newValue: unknown }) => void + variableItems?: VariableItem[] }) => ( -
+
v.value)) : undefined} + > onDataChange({ inputParam, newValue: e.target.value })} />
@@ -485,4 +493,163 @@ describe('ArrayInput', () => { const deleteButton = screen.getByTitle('Delete') expect(deleteButton).toBeDisabled() }) + + // Parent inputs are merged into itemData so async load methods inside array items + // (e.g. listToolInputArgs) can access parent-level fields like toolAgentflowSelectedTool. + it('should merge parent node inputs into itemData so async options inside array items can access parent fields', () => { + const dataWithParentField: NodeData = { + ...mockNodeData, + inputs: { + toolAgentflowSelectedTool: 'calculator', + toolInputArgs: [{ inputArgName: '', inputArgValue: '' }] + } + } as NodeData + + const toolInputArgsParam: InputParam = { + id: 'toolInputArgs', + name: 'toolInputArgs', + label: 'Tool Input Arguments', + type: 'array', + array: [ + { id: 'inputArgName', name: 'inputArgName', label: 'Input Argument Name', type: 'asyncOptions' } as InputParam, + { id: 'inputArgValue', name: 'inputArgValue', label: 'Input Argument Value', type: 'string' } as InputParam + ] + } + + render() + + const argNameHandler = screen.getByTestId('input-handler-inputArgName') + const inputs = JSON.parse(argNameHandler.getAttribute('data-inputs') ?? '{}') + + // The parent-level field must be visible to the array item's async input + expect(inputs.toolAgentflowSelectedTool).toBe('calculator') + // The item's own fields are also present + expect(inputs.inputArgName).toBe('') + }) +}) + +// ── acceptNodeOutputAsVariable in ArrayInput ──────────────────────────────── + +describe('ArrayInput – acceptNodeOutputAsVariable', () => { + const baseVariableItems: VariableItem[] = [ + { label: 'question', description: "User's question", category: 'Chat Context', value: '{{question}}' } + ] + + it('prepends {{output}} to variableItems when nested param has acceptNodeOutputAsVariable', () => { + const inputParam: InputParam = { + id: 'updateState', + name: 'agentUpdateState', + label: 'Update State', + type: 'array', + acceptVariable: true, + array: [ + { id: 'key', name: 'key', label: 'Key', type: 'string' } as InputParam, + { + id: 'value', + name: 'value', + label: 'Value', + type: 'string', + acceptVariable: true, + acceptNodeOutputAsVariable: true + } as InputParam + ] + } + + const data: NodeData = { + id: 'node-1', + name: 'agentNode', + label: 'Agent', + inputs: { agentUpdateState: [{ key: 'myKey', value: '' }] } + } as NodeData + + render() + + const valueHandler = screen.getByTestId('input-handler-value') + const passedItems: string[] = JSON.parse(valueHandler.getAttribute('data-variable-items') ?? '[]') + expect(passedItems[0]).toBe('{{output}}') + expect(passedItems).toContain('{{question}}') + }) + + it('does NOT prepend {{output}} when nested param lacks acceptNodeOutputAsVariable', () => { + const inputParam: InputParam = { + id: 'updateState', + name: 'agentUpdateState', + label: 'Update State', + type: 'array', + acceptVariable: true, + array: [{ id: 'value', name: 'value', label: 'Value', type: 'string', acceptVariable: true } as InputParam] + } + + const data: NodeData = { + id: 'node-1', + name: 'agentNode', + label: 'Agent', + inputs: { agentUpdateState: [{ value: '' }] } + } as NodeData + + render() + + const valueHandler = screen.getByTestId('input-handler-value') + const passedItems: string[] = JSON.parse(valueHandler.getAttribute('data-variable-items') ?? '[]') + expect(passedItems).not.toContain('{{output}}') + expect(passedItems).toContain('{{question}}') + }) + + it('includes structured output fields in prepended variables', () => { + const inputParam: InputParam = { + id: 'updateState', + name: 'agentUpdateState', + label: 'Update State', + type: 'array', + array: [ + { + id: 'value', + name: 'value', + label: 'Value', + type: 'string', + acceptVariable: true, + acceptNodeOutputAsVariable: true + } as InputParam + ] + } + + const data: NodeData = { + id: 'node-1', + name: 'agentNode', + label: 'Agent', + inputs: { + agentUpdateState: [{ value: '' }], + agentStructuredOutput: [{ key: 'sentiment', description: 'Sentiment score' }] + } + } as NodeData + + render() + + const valueHandler = screen.getByTestId('input-handler-value') + const passedItems: string[] = JSON.parse(valueHandler.getAttribute('data-variable-items') ?? '[]') + expect(passedItems).toContain('{{output}}') + expect(passedItems).toContain('{{output.sentiment}}') + }) + + it('does not pass variableItems when nested param has no acceptVariable', () => { + const inputParam: InputParam = { + id: 'updateState', + name: 'agentUpdateState', + label: 'Update State', + type: 'array', + array: [{ id: 'key', name: 'key', label: 'Key', type: 'string', acceptNodeOutputAsVariable: true } as InputParam] + } + + const data: NodeData = { + id: 'node-1', + name: 'agentNode', + label: 'Agent', + inputs: { agentUpdateState: [{ key: '' }] } + } as NodeData + + render() + + const keyHandler = screen.getByTestId('input-handler-key') + expect(keyHandler.getAttribute('data-variable-items')).toBeNull() + }) }) diff --git a/packages/agentflow/src/atoms/ArrayInput.tsx b/packages/agentflow/src/atoms/ArrayInput.tsx index a94d05303..89c78c9e7 100644 --- a/packages/agentflow/src/atoms/ArrayInput.tsx +++ b/packages/agentflow/src/atoms/ArrayInput.tsx @@ -11,6 +11,30 @@ import { type AsyncInputProps, type ConfigInputComponentProps, NodeInputHandler import { useStableKeys } from './useStableKeys' import type { VariableItem } from './VariablePicker' +/** + * Builds VariableItems representing the current node's own output. + * Added when a nested sub-field declares `acceptNodeOutputAsVariable: true`, + * matching the v2 suggestionOption behaviour where `{{output}}` and + * `{{output.}}` appear for structured-output fields. + */ +function buildNodeOutputVariables(data: NodeData): VariableItem[] { + const vars: VariableItem[] = [ + { label: 'output', description: 'Output from the current node', category: 'Node Outputs', value: '{{output}}' } + ] + const structured = ( + (data.inputs?.agentStructuredOutput ?? data.inputs?.llmStructuredOutput ?? []) as Array<{ key: string; description?: string }> + ).filter((item) => item?.key) + for (const item of structured) { + vars.push({ + label: `output.${item.key}`, + description: item.description ?? `Structured output field: ${item.key}`, + category: 'Node Outputs', + value: `{{output.${item.key}}}` + }) + } + return vars +} + export interface ArrayInputProps { inputParam: InputParam data: NodeData @@ -118,10 +142,13 @@ export function ArrayInput({ <> {/* Render each array item */} {arrayItems.map((itemValues, index) => { - // Create item-specific data context for nested NodeInputHandler + // Create item-specific data context for nested NodeInputHandler. + // Merge parent inputs first so async load methods (e.g. listToolInputArgs) + // can read parent-level fields like toolAgentflowSelectedTool, while + // item-level fields take precedence via the spread order. const itemData: NodeData = { ...data, - inputs: itemValues + inputs: { ...data.inputs, ...itemValues } } return ( @@ -178,7 +205,13 @@ export function ArrayInput({ onConfigChange={onConfigChange} arrayIndex={index} parentArrayParam={inputParam} - variableItems={param.acceptVariable ? variableItems : undefined} + variableItems={ + param.acceptVariable + ? param.acceptNodeOutputAsVariable + ? [...buildNodeOutputVariables(data), ...(variableItems ?? [])] + : variableItems + : undefined + } /> ))} @@ -191,7 +224,7 @@ export function ArrayInput({ size='small' variant='outlined' disabled={disabled} - sx={{ borderRadius: '16px', mt: 2 }} + sx={{ borderRadius: '16px', mt: 1 }} startIcon={} onClick={handleAddItem} > diff --git a/packages/agentflow/src/atoms/ConditionBuilder.tsx b/packages/agentflow/src/atoms/ConditionBuilder.tsx index d0258ea8c..c621dcd4c 100644 --- a/packages/agentflow/src/atoms/ConditionBuilder.tsx +++ b/packages/agentflow/src/atoms/ConditionBuilder.tsx @@ -171,13 +171,13 @@ export function ConditionBuilder({ - +