From 8efa33bbee635410540e34097336b68d0db95974 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Tue, 11 Feb 2025 21:28:38 +0000 Subject: [PATCH 01/20] fix: predecessor schema parsing in router node --- .../src/components/nodes/logic/RouterNode.tsx | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/nodes/logic/RouterNode.tsx b/frontend/src/components/nodes/logic/RouterNode.tsx index 42a6e917..37cafaa0 100644 --- a/frontend/src/components/nodes/logic/RouterNode.tsx +++ b/frontend/src/components/nodes/logic/RouterNode.tsx @@ -249,12 +249,21 @@ export const RouterNode: React.FC = ({ id, data, readOnly = fal const predNodeConfig = nodeConfigs[node.id] const nodeTitle = predNodeConfig?.title || node.id - const outputSchema = predNodeConfig?.output_schema || {} - - return Object.entries(outputSchema).map(([key, type]) => ({ - value: `${nodeTitle}.${key}`, - label: `${nodeTitle}.${key} (${type})`, - })) + const outputSchema = predNodeConfig?.output_json_schema || '{}' + + try { + const schema = JSON.parse(outputSchema) + // Extract properties from JSON schema + const properties = schema.properties || {} + + return Object.entries(properties).map(([key, value]: [string, any]) => ({ + value: `${nodeTitle}.${key}`, + label: `${nodeTitle}.${key} (${value.type || 'any'})`, + })) + } catch (error) { + console.error('Failed to parse output schema:', error) + return [] + } }) }, [predecessorNodes, nodeConfigs]) From 48ca7969b378d8aa646e6774f87b69e928a99aa6 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Tue, 11 Feb 2025 21:28:53 +0000 Subject: [PATCH 02/20] fix: Improve RouterNode select item styling and text wrapping --- frontend/src/components/nodes/logic/RouterNode.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/nodes/logic/RouterNode.tsx b/frontend/src/components/nodes/logic/RouterNode.tsx index 37cafaa0..fdecd2b4 100644 --- a/frontend/src/components/nodes/logic/RouterNode.tsx +++ b/frontend/src/components/nodes/logic/RouterNode.tsx @@ -520,8 +520,7 @@ export const RouterNode: React.FC = ({ id, data, readOnly = fal variant="flat" classNames={{ trigger: 'dark:bg-default-50/10', - base: 'dark:bg-default-50/10', - popoverContent: 'dark:bg-default-50/10', + popoverContent: 'dark:bg-default-50', }} renderValue={(items) => { return items.map((item) => ( @@ -541,6 +540,9 @@ export const RouterNode: React.FC = ({ id, data, readOnly = fal value={variable.value} textValue={variable.label} className="text-default-700 dark:text-default-300" + classNames={{ + title: 'w-full whitespace-normal break-words', + }} >
{variable.label} @@ -579,8 +581,7 @@ export const RouterNode: React.FC = ({ id, data, readOnly = fal variant="flat" classNames={{ trigger: 'dark:bg-default-50/10', - base: 'dark:bg-default-50/10', - popoverContent: 'dark:bg-default-50/10', + popoverContent: 'dark:bg-default-50', }} renderValue={(items) => { return items.map((item) => ( @@ -599,6 +600,9 @@ export const RouterNode: React.FC = ({ id, data, readOnly = fal value={op.value} textValue={op.label} className="text-default-700 dark:text-default-300" + classNames={{ + title: 'w-full whitespace-normal break-words', + }} > {op.label} From 68dea4e38617cb3a474090662ddc7b9626884924 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Tue, 11 Feb 2025 21:50:41 +0000 Subject: [PATCH 03/20] refactor: show empty fragment for non LLM node outputschema --- .../nodes/nodeSidebar/NodeSidebar.tsx | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx index e65c12cf..723d15c8 100644 --- a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx +++ b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx @@ -833,19 +833,7 @@ const NodeSidebar: React.FC = ({ nodeID }) => { ) : ( - -
- - - This model uses a fixed output schema:{' '} - - { - "{ type: 'object', properties: { output: { type: 'string' } }, required: ['output'] }" - } - - -
-
+ <> )} {!isLast &&
}
@@ -916,9 +904,10 @@ const NodeSidebar: React.FC = ({ nodeID }) => { } if (key.endsWith('_template')) { - const title = key.slice(0, -9) + const title = key + .slice(0, -9) .replace(/_/g, ' ') - .replace(/\b\w/g, (char) => char.toUpperCase()); + .replace(/\b\w/g, (char) => char.toUpperCase()) return (
@@ -936,13 +925,11 @@ const NodeSidebar: React.FC = ({ nodeID }) => { /> {!isLast &&
}
- ); + ) } if (key.endsWith('_prompt') || key.endsWith('_message')) { - const title = key - .replace(/_/g, ' ') - .replace(/\b\w/g, (char) => char.toUpperCase()); + const title = key.replace(/_/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase()) return (
@@ -959,7 +946,7 @@ const NodeSidebar: React.FC = ({ nodeID }) => { /> {!isLast &&
}
- ); + ) } if (key === 'code') { From 581876f90eed49db07ed21219db77bd19b7daaa8 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 03:10:15 +0000 Subject: [PATCH 04/20] fix: generate router node output schema using json schema --- frontend/src/store/flowSlice.ts | 37 +++++++++++++++++---------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/frontend/src/store/flowSlice.ts b/frontend/src/store/flowSlice.ts index c40ad106..c39c8928 100644 --- a/frontend/src/store/flowSlice.ts +++ b/frontend/src/store/flowSlice.ts @@ -45,33 +45,36 @@ function rebuildRouterNodeSchema(state: FlowState, routerNode: FlowWorkflowNode) const incomingEdges = state.edges.filter((edge) => edge.target === routerNode.id) // Build new output schema by combining all source nodes - const newOutputSchema = incomingEdges.reduce( - (schema, edge) => { + const newSchemaProperties = incomingEdges.reduce( + (properties, edge) => { const sourceNode = state.nodes.find((n) => n.id === edge.source) const sourceNodeConfig = sourceNode ? state.nodeConfigs[sourceNode.id] : undefined - if (sourceNodeConfig?.output_schema) { + if (sourceNodeConfig?.output_json_schema) { const nodeTitle = sourceNodeConfig.title || sourceNode?.id - const sourceSchema = sourceNodeConfig.output_schema + const sourceSchema = sourceNodeConfig.output_json_schema - // Add prefixed entries from the source schema - Object.entries(sourceSchema).forEach(([key, value]) => { - schema[`${nodeTitle}.${key}`] = value - }) + properties[nodeTitle] = sourceSchema } - return schema + return properties }, {} as Record ) + const newOutputSchema = JSON.stringify({ + type: 'object', + properties: newSchemaProperties, + required: Object.keys(newSchemaProperties), + additionalProperties: false, + }) const routerNodeConfig = state.nodeConfigs[routerNode.id] || {} - const currentSchema = routerNodeConfig.output_schema || {} + const currentSchema = routerNodeConfig.output_json_schema || '{}' const hasChanges = !isEqual(currentSchema, newOutputSchema) // Only update if there are actual changes if (hasChanges) { state.nodeConfigs[routerNode.id] = { ...routerNodeConfig, - output_schema: newOutputSchema, + output_json_schema: newOutputSchema, } } } @@ -107,10 +110,8 @@ function rebuildCoalesceNodeSchema(state: FlowState, coalesceNode: FlowWorkflowN const generateJsonSchema = (schema: Record): string => { const jsonSchema = { type: 'object', - properties: Object.fromEntries( - Object.entries(schema).map(([key, type]) => [key, { type }]) - ), - required: Object.keys(schema) + properties: Object.fromEntries(Object.entries(schema).map(([key, type]) => [key, { type }])), + required: Object.keys(schema), } return JSON.stringify(jsonSchema, null, 2) } @@ -422,7 +423,7 @@ const flowSlice = createSlice({ state.nodeConfigs[inputNode.id] = { ...currentConfig, output_schema: updatedSchema, - output_json_schema: generateJsonSchema(updatedSchema) + output_json_schema: generateJsonSchema(updatedSchema), } } @@ -467,7 +468,7 @@ const flowSlice = createSlice({ state.nodeConfigs[inputNode.id] = { ...currentConfig, output_schema: updatedSchema, - output_json_schema: generateJsonSchema(updatedSchema) + output_json_schema: generateJsonSchema(updatedSchema), } } // Remove from global workflowInputVariables @@ -528,7 +529,7 @@ const flowSlice = createSlice({ state.nodeConfigs[inputNode.id] = { ...currentConfig, output_schema: updatedSchema, - output_json_schema: generateJsonSchema(updatedSchema) + output_json_schema: generateJsonSchema(updatedSchema), } } From 72c38d79c7b11476b839ad23a0edf8050f8ce7d9 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 03:25:53 +0000 Subject: [PATCH 05/20] fix: Improve output schema tooltip text for different model constraints --- frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx index 723d15c8..6c3a7623 100644 --- a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx +++ b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx @@ -703,9 +703,13 @@ const NodeSidebar: React.FC = ({ nodeID }) => { content={ currentNodeConfig?.has_fixed_output === true ? "This node has a fixed output schema that cannot be modified. The JSON schema provides detailed validation rules for the node's output." - : currentNodeConfig?.llm_info?.model + : currentNodeConfig?.llm_info?.model && + currentModelConstraints?.supports_JSON_output ? "Define the structure of this node's output. You can use either the Simple Editor for basic types, or the JSON Schema Editor for more complex validation rules." - : "This model only supports a fixed output schema with a single 'output' field of type string. Schema editing is disabled." + : currentNodeConfig?.llm_info?.model && + !currentModelConstraints?.supports_JSON_output + ? "This model only supports a fixed output schema with a single 'output' field of type string. Schema editing is disabled." + : "The output schema defines the structure of this node's output." } placement="left-start" showArrow={true} From 3713bdaf6c9c8b9369de23c6175cebedf683daa5 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 03:33:48 +0000 Subject: [PATCH 06/20] refactor: Extract OutputSchemaEditor into separate component --- .../nodes/nodeSidebar/NodeSidebar.tsx | 167 ++-------------- .../nodes/nodeSidebar/OutputSchemaEditor.tsx | 180 ++++++++++++++++++ 2 files changed, 195 insertions(+), 152 deletions(-) create mode 100644 frontend/src/components/nodes/nodeSidebar/OutputSchemaEditor.tsx diff --git a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx index 6c3a7623..59a9e8c3 100644 --- a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx +++ b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx @@ -5,15 +5,12 @@ import { Alert, Button, Card, - CardBody, Input, Select, SelectItem, SelectSection, Slider, Switch, - Tab, - Tabs, Textarea, Tooltip, } from '@heroui/react' @@ -43,6 +40,7 @@ import NumberInput from '../../NumberInput' import FewShotEditor from '../../textEditor/FewShotEditor' import TextEditor from '../../textEditor/TextEditor' import NodeOutput from '../NodeOutputDisplay' +import OutputSchemaEditor from './OutputSchemaEditor' import SchemaEditor from './SchemaEditor' import { convertToPythonVariableName } from '@/utils/variableNameUtils' @@ -691,156 +689,21 @@ const NodeSidebar: React.FC = ({ nodeID }) => { } if (key === 'output_json_schema') { - const { schema: parsedSchema, error: parseError } = extractSchemaFromJsonSchema( - currentNodeConfig[key] || '' - ) - return ( -
-
-

Output Schema

- - - - {!currentNodeConfig?.has_fixed_output && currentNodeConfig?.llm_info?.model && ( - - )} -
- {currentNodeConfig?.has_fixed_output ? ( -
- {}} // No-op for fixed output nodes - readOnly={true} - /> -

- This node has a fixed output schema that cannot be modified. -

-
- ) : currentNodeConfig?.llm_info?.model ? ( - <> - {jsonSchemaError && ( - -
- {jsonSchemaError} -
-
- )} - - - {parsedSchema && ( - - - { - if (typeof newValue === 'object' && !('type' in newValue)) { - const jsonSchema = generateJsonSchemaFromSchema(newValue) - if (jsonSchema) { - const updates = { - output_json_schema: jsonSchema, - } - setCurrentNodeConfig((prev) => ({ - ...prev, - ...updates, - })) - dispatch( - updateNodeConfigOnly({ - id: nodeID, - data: { - ...currentNodeConfig, - ...updates, - }, - }) - ) - } - } else { - const updates = { - output_json_schema: JSON.stringify(newValue, null, 2), - } - setCurrentNodeConfig((prev) => ({ - ...prev, - ...updates, - })) - dispatch( - updateNodeConfigOnly({ - id: nodeID, - data: { - ...currentNodeConfig, - ...updates, - }, - }) - ) - } - }} - options={jsonOptions} - nodeId={nodeID} - /> - - - )} - - - - - { - handleInputChange('output_json_schema', value) - debouncedValidate(value) - }} - /> - - - - - - ) : ( - <> - )} - {!isLast &&
} -
+ ) } diff --git a/frontend/src/components/nodes/nodeSidebar/OutputSchemaEditor.tsx b/frontend/src/components/nodes/nodeSidebar/OutputSchemaEditor.tsx new file mode 100644 index 00000000..70ea1d1e --- /dev/null +++ b/frontend/src/components/nodes/nodeSidebar/OutputSchemaEditor.tsx @@ -0,0 +1,180 @@ +import { Alert, Button, Card, CardBody, Tab, Tabs, Tooltip } from '@heroui/react' +import { Icon } from '@iconify/react' +import React from 'react' +import { jsonOptions } from '../../../constants/jsonOptions' +import CodeEditor from '../../CodeEditor' +import SchemaEditor from './SchemaEditor' + +interface OutputSchemaEditorProps { + nodeID: string + currentNodeConfig: any + jsonSchemaError: string + handleInputChange: (key: string, value: any) => void + debouncedValidate: (value: string) => void + setCurrentNodeConfig: (updater: (prev: any) => any) => void + dispatch: any + updateNodeConfigOnly: any + generateJsonSchemaFromSchema: (schema: Record) => string | null + extractSchemaFromJsonSchema: (jsonSchema: string) => { schema: Record | null; error: string | null } + isLast?: boolean +} + +const OutputSchemaEditor: React.FC = ({ + nodeID, + currentNodeConfig, + jsonSchemaError, + handleInputChange, + debouncedValidate, + setCurrentNodeConfig, + dispatch, + updateNodeConfigOnly, + generateJsonSchemaFromSchema, + extractSchemaFromJsonSchema, + isLast = false, +}) => { + const { schema: parsedSchema } = extractSchemaFromJsonSchema(currentNodeConfig.output_json_schema || '') + + return ( +
+
+

Output Schema

+ + + + {!currentNodeConfig?.has_fixed_output && currentNodeConfig?.llm_info?.model && ( + + )} +
+ {currentNodeConfig?.has_fixed_output ? ( +
+ {}} // No-op for fixed output nodes + readOnly={true} + /> +

+ This node has a fixed output schema that cannot be modified. +

+
+ ) : currentNodeConfig?.llm_info?.model ? ( + <> + {jsonSchemaError && ( + +
+ {jsonSchemaError} +
+
+ )} + + + {parsedSchema && ( + + + { + if (typeof newValue === 'object' && !('type' in newValue)) { + const jsonSchema = generateJsonSchemaFromSchema(newValue) + if (jsonSchema) { + const updates = { + output_json_schema: jsonSchema, + } + setCurrentNodeConfig((prev) => ({ + ...prev, + ...updates, + })) + dispatch( + updateNodeConfigOnly({ + id: nodeID, + data: { + ...currentNodeConfig, + ...updates, + }, + }) + ) + } + } else { + const updates = { + output_json_schema: JSON.stringify(newValue, null, 2), + } + setCurrentNodeConfig((prev) => ({ + ...prev, + ...updates, + })) + dispatch( + updateNodeConfigOnly({ + id: nodeID, + data: { + ...currentNodeConfig, + ...updates, + }, + }) + ) + } + }} + options={jsonOptions} + nodeId={nodeID} + /> + + + )} + + + + + { + handleInputChange('output_json_schema', value) + debouncedValidate(value) + }} + /> + + + + + + ) : ( + <> + )} + {!isLast &&
} +
+ ) +} + +export default OutputSchemaEditor From 198c2980fb512292bb9eeac2b2936647a401dc39 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 03:36:01 +0000 Subject: [PATCH 07/20] refactor: Extract schema utility functions to separate module --- .../nodes/nodeSidebar/NodeSidebar.tsx | 71 +---------------- .../nodes/nodeSidebar/OutputSchemaEditor.tsx | 5 +- frontend/src/utils/schemaUtils.ts | 79 +++++++++++++++++++ 3 files changed, 81 insertions(+), 74 deletions(-) create mode 100644 frontend/src/utils/schemaUtils.ts diff --git a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx index 59a9e8c3..71d35bdc 100644 --- a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx +++ b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx @@ -43,6 +43,7 @@ import NodeOutput from '../NodeOutputDisplay' import OutputSchemaEditor from './OutputSchemaEditor' import SchemaEditor from './SchemaEditor' +import { extractSchemaFromJsonSchema, generateJsonSchemaFromSchema } from '@/utils/schemaUtils' import { convertToPythonVariableName } from '@/utils/variableNameUtils' import Ajv from 'ajv' @@ -78,75 +79,7 @@ const nodesComparator = (prevNodes: FlowWorkflowNode[], nextNodes: FlowWorkflowN return prevNodes.every((node, index) => nodeComparator(node, nextNodes[index])) } -// Update the extractSchemaFromJsonSchema function to return an error instead of calling setError -const extractSchemaFromJsonSchema = ( - jsonSchema: string -): { schema: Record | null; error: string | null } => { - if (!jsonSchema || !jsonSchema.trim()) { - return { schema: null, error: null } - } - try { - // Try to parse the schema - let parsed: Record - try { - parsed = JSON.parse(jsonSchema.trim()) - } catch (e: any) { - // If the schema has escaped characters, clean it up first - let cleaned = jsonSchema - .replace(/\"/g, '"') // Replace escaped quotes - .replace(/\\\[/g, '[') // Replace escaped brackets - .replace(/\\\]/g, ']') - .replace(/\\n/g, '') // Remove newlines - .replace(/\\t/g, '') // Remove tabs - .replace(/\\/g, '') // Remove remaining backslashes - .trim() - try { - parsed = JSON.parse(cleaned) - } catch (e: any) { - // Extract line and column info from the error message if available - const match = e.message.match(/at position (\d+)(?:\s*\(line (\d+) column (\d+)\))?/) - const errorMsg = match - ? `Invalid JSON: ${e.message.split('at position')[0].trim()} at line ${match[2] || '?'}, column ${match[3] || '?'}` - : `Invalid JSON: ${e.message}` - return { schema: null, error: errorMsg } - } - } - - // If the parsed schema has a properties field (i.e. full JSON Schema format), - // return the nested properties so that nested objects are preserved. - if (parsed.properties) { - return { schema: parsed.properties, error: null } - } - return { schema: parsed, error: null } - } catch (error: any) { - return { schema: null, error: error.message || 'Invalid JSON Schema' } - } -} - // Add this helper function near the top, after extractSchemaFromJsonSchema -const generateJsonSchemaFromSchema = (schema: Record): string | null => { - if (!schema || Object.keys(schema).length === 0) return null - - try { - const jsonSchema = { - type: 'object', - required: Object.keys(schema), - properties: {} as Record, - } - - for (const [key, type] of Object.entries(schema)) { - if (!key || !type) return null - jsonSchema.properties[key] = { type } - } - - return JSON.stringify(jsonSchema, null, 2) - } catch (error) { - console.error('Error generating JSON schema:', error) - return null - } -} - -// Add this function after the existing helper functions and before the NodeSidebar component const getModelConstraints = (nodeSchema: FlowWorkflowNodeType | null, modelId: string): ModelConstraints | null => { if (!nodeSchema || !nodeSchema.model_constraints || !nodeSchema.model_constraints[modelId]) { return null @@ -700,8 +633,6 @@ const NodeSidebar: React.FC = ({ nodeID }) => { setCurrentNodeConfig={setCurrentNodeConfig} dispatch={dispatch} updateNodeConfigOnly={updateNodeConfigOnly} - generateJsonSchemaFromSchema={generateJsonSchemaFromSchema} - extractSchemaFromJsonSchema={extractSchemaFromJsonSchema} isLast={isLast} /> ) diff --git a/frontend/src/components/nodes/nodeSidebar/OutputSchemaEditor.tsx b/frontend/src/components/nodes/nodeSidebar/OutputSchemaEditor.tsx index 70ea1d1e..23bdbb12 100644 --- a/frontend/src/components/nodes/nodeSidebar/OutputSchemaEditor.tsx +++ b/frontend/src/components/nodes/nodeSidebar/OutputSchemaEditor.tsx @@ -2,6 +2,7 @@ import { Alert, Button, Card, CardBody, Tab, Tabs, Tooltip } from '@heroui/react import { Icon } from '@iconify/react' import React from 'react' import { jsonOptions } from '../../../constants/jsonOptions' +import { extractSchemaFromJsonSchema, generateJsonSchemaFromSchema } from '../../../utils/schemaUtils' import CodeEditor from '../../CodeEditor' import SchemaEditor from './SchemaEditor' @@ -14,8 +15,6 @@ interface OutputSchemaEditorProps { setCurrentNodeConfig: (updater: (prev: any) => any) => void dispatch: any updateNodeConfigOnly: any - generateJsonSchemaFromSchema: (schema: Record) => string | null - extractSchemaFromJsonSchema: (jsonSchema: string) => { schema: Record | null; error: string | null } isLast?: boolean } @@ -28,8 +27,6 @@ const OutputSchemaEditor: React.FC = ({ setCurrentNodeConfig, dispatch, updateNodeConfigOnly, - generateJsonSchemaFromSchema, - extractSchemaFromJsonSchema, isLast = false, }) => { const { schema: parsedSchema } = extractSchemaFromJsonSchema(currentNodeConfig.output_json_schema || '') diff --git a/frontend/src/utils/schemaUtils.ts b/frontend/src/utils/schemaUtils.ts new file mode 100644 index 00000000..8d9bf86c --- /dev/null +++ b/frontend/src/utils/schemaUtils.ts @@ -0,0 +1,79 @@ +/** + * Utility functions for handling JSON Schema operations + */ + +/** + * Generates a JSON Schema from a simple schema object + * @param schema Record of field names to their types + * @returns JSON Schema string or null if invalid + */ +export const generateJsonSchemaFromSchema = (schema: Record): string | null => { + if (!schema || Object.keys(schema).length === 0) return null + + try { + const jsonSchema = { + type: 'object', + required: Object.keys(schema), + properties: {} as Record, + } + + for (const [key, type] of Object.entries(schema)) { + if (!key || !type) return null + jsonSchema.properties[key] = { type } + } + + return JSON.stringify(jsonSchema, null, 2) + } catch (error) { + console.error('Error generating JSON schema:', error) + return null + } +} + +/** + * Extracts a schema object from a JSON Schema string + * @param jsonSchema JSON Schema string to parse + * @returns Object containing the parsed schema and any error + */ +export const extractSchemaFromJsonSchema = ( + jsonSchema: string +): { schema: Record | null; error: string | null } => { + if (!jsonSchema || !jsonSchema.trim()) { + return { schema: null, error: null } + } + try { + // Try to parse the schema + let parsed: Record + try { + parsed = JSON.parse(jsonSchema.trim()) + } catch (e: any) { + // If the schema has escaped characters, clean it up first + let cleaned = jsonSchema + .replace(/\"/g, '"') // Replace escaped quotes + .replace(/\\\[/g, '[') // Replace escaped brackets + .replace(/\\\]/g, ']') + .replace(/\\n/g, '') // Remove newlines + .replace(/\\t/g, '') // Remove tabs + .replace(/\\/g, '') // Remove remaining backslashes + .trim() + try { + parsed = JSON.parse(cleaned) + } catch (e: any) { + // Extract line and column info from the error message if available + const match = e.message.match(/at position (\d+)(?:\s*\(line (\d+) column (\d+)\))?/) + const errorMsg = match + ? `Invalid JSON: ${e.message.split('at position')[0].trim()} at line ${match[2] || '?'}, column ${match[3] || '?'}` + : `Invalid JSON: ${e.message}` + return { schema: null, error: errorMsg } + } + } + + // If the parsed schema has a properties field (i.e. full JSON Schema format), + // return the nested properties so that nested objects are preserved. + if (parsed.properties) { + return { schema: parsed.properties, error: null } + } + return { schema: parsed, error: null } + } catch (error: any) { + return { schema: null, error: error.message || 'Invalid JSON Schema' } + } +} From 61646476844dd4b2b142046bf430c88f5db9d60d Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 03:44:21 +0000 Subject: [PATCH 08/20] refactor: Simplify OutputSchemaEditor component and improve prop handling --- .../nodes/nodeSidebar/NodeSidebar.tsx | 75 +++++- .../nodes/nodeSidebar/OutputSchemaEditor.tsx | 223 ++++++------------ 2 files changed, 130 insertions(+), 168 deletions(-) diff --git a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx index 71d35bdc..217955eb 100644 --- a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx +++ b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx @@ -623,18 +623,69 @@ const NodeSidebar: React.FC = ({ nodeID }) => { if (key === 'output_json_schema') { return ( - +
+
+

Output Schema

+ + + + {!currentNodeConfig?.has_fixed_output && currentNodeConfig?.llm_info?.model && ( + + )} +
+ {currentNodeConfig?.has_fixed_output && ( +

+ This node has a fixed output schema that cannot be modified. +

+ )} + { + handleInputChange('output_json_schema', newSchema) + debouncedValidate(newSchema) + }} + /> + {!isLast &&
} +
) } diff --git a/frontend/src/components/nodes/nodeSidebar/OutputSchemaEditor.tsx b/frontend/src/components/nodes/nodeSidebar/OutputSchemaEditor.tsx index 23bdbb12..14335807 100644 --- a/frontend/src/components/nodes/nodeSidebar/OutputSchemaEditor.tsx +++ b/frontend/src/components/nodes/nodeSidebar/OutputSchemaEditor.tsx @@ -1,5 +1,4 @@ -import { Alert, Button, Card, CardBody, Tab, Tabs, Tooltip } from '@heroui/react' -import { Icon } from '@iconify/react' +import { Alert, Card, CardBody, Tab, Tabs } from '@heroui/react' import React from 'react' import { jsonOptions } from '../../../constants/jsonOptions' import { extractSchemaFromJsonSchema, generateJsonSchemaFromSchema } from '../../../utils/schemaUtils' @@ -8,168 +7,80 @@ import SchemaEditor from './SchemaEditor' interface OutputSchemaEditorProps { nodeID: string - currentNodeConfig: any - jsonSchemaError: string - handleInputChange: (key: string, value: any) => void - debouncedValidate: (value: string) => void - setCurrentNodeConfig: (updater: (prev: any) => any) => void - dispatch: any - updateNodeConfigOnly: any - isLast?: boolean + schema: string + readOnly?: boolean + error?: string + onChange: (newSchema: string) => void } const OutputSchemaEditor: React.FC = ({ nodeID, - currentNodeConfig, - jsonSchemaError, - handleInputChange, - debouncedValidate, - setCurrentNodeConfig, - dispatch, - updateNodeConfigOnly, - isLast = false, + schema, + readOnly = false, + error = '', + onChange, }) => { - const { schema: parsedSchema } = extractSchemaFromJsonSchema(currentNodeConfig.output_json_schema || '') + const { schema: parsedSchema } = extractSchemaFromJsonSchema(schema || '') + + const handleSchemaEditorChange = (newValue: any) => { + if (!readOnly) { + if (typeof newValue === 'object' && !('type' in newValue)) { + const jsonSchema = generateJsonSchemaFromSchema(newValue) + if (jsonSchema) { + onChange(jsonSchema) + } + } else { + onChange(JSON.stringify(newValue, null, 2)) + } + } + } + + const handleJsonEditorChange = (value: string) => { + if (!readOnly) { + onChange(value) + } + } return ( -
-
-

Output Schema

- - - - {!currentNodeConfig?.has_fixed_output && currentNodeConfig?.llm_info?.model && ( - - )} -
- {currentNodeConfig?.has_fixed_output ? ( -
- {}} // No-op for fixed output nodes - readOnly={true} - /> -

- This node has a fixed output schema that cannot be modified. -

-
- ) : currentNodeConfig?.llm_info?.model ? ( - <> - {jsonSchemaError && ( - -
- {jsonSchemaError} -
-
- )} - - - {parsedSchema && ( - - - { - if (typeof newValue === 'object' && !('type' in newValue)) { - const jsonSchema = generateJsonSchemaFromSchema(newValue) - if (jsonSchema) { - const updates = { - output_json_schema: jsonSchema, - } - setCurrentNodeConfig((prev) => ({ - ...prev, - ...updates, - })) - dispatch( - updateNodeConfigOnly({ - id: nodeID, - data: { - ...currentNodeConfig, - ...updates, - }, - }) - ) - } - } else { - const updates = { - output_json_schema: JSON.stringify(newValue, null, 2), - } - setCurrentNodeConfig((prev) => ({ - ...prev, - ...updates, - })) - dispatch( - updateNodeConfigOnly({ - id: nodeID, - data: { - ...currentNodeConfig, - ...updates, - }, - }) - ) - } - }} - options={jsonOptions} - nodeId={nodeID} - /> - - - )} - - - - - { - handleInputChange('output_json_schema', value) - debouncedValidate(value) - }} - /> - - - - - - ) : ( - <> +
+ {error && ( + +
+ {error} +
+
)} - {!isLast &&
} + + + {parsedSchema && ( + + + + + + )} + + + + + + + + +
) } From 9d493659c19f424158b2a9ea2d3841567b0c9a67 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 03:50:51 +0000 Subject: [PATCH 09/20] fix: Enhance output schema read-only logic for different node types and model constraints --- .../src/components/nodes/nodeSidebar/NodeSidebar.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx index 217955eb..fbce1945 100644 --- a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx +++ b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx @@ -622,6 +622,11 @@ const NodeSidebar: React.FC = ({ nodeID }) => { } if (key === 'output_json_schema') { + const isReadOnly = + currentNodeConfig?.has_fixed_output || + (currentNodeConfig?.llm_info?.model && !currentModelConstraints?.supports_JSON_output) || + node.type === 'RouterNode' || + false return (
@@ -631,10 +636,10 @@ const NodeSidebar: React.FC = ({ nodeID }) => { currentNodeConfig?.has_fixed_output === true ? "This node has a fixed output schema that cannot be modified. The JSON schema provides detailed validation rules for the node's output." : currentNodeConfig?.llm_info?.model && - currentNodeConfig?.llm_info?.supports_JSON_output + currentModelConstraints?.supports_JSON_output ? "Define the structure of this node's output. You can use either the Simple Editor for basic types, or the JSON Schema Editor for more complex validation rules." : currentNodeConfig?.llm_info?.model && - !currentNodeConfig?.llm_info?.supports_JSON_output + !currentModelConstraints?.supports_JSON_output ? "This model only supports a fixed output schema with a single 'output' field of type string. Schema editing is disabled." : "The output schema defines the structure of this node's output." } @@ -677,7 +682,7 @@ const NodeSidebar: React.FC = ({ nodeID }) => { { handleInputChange('output_json_schema', newSchema) From 8cf0ee0e9f55a1da398e11c9b92055f11c827d16 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 03:53:49 +0000 Subject: [PATCH 10/20] feat: Add output JSON schema to FlowWorkflowNodeConfig type --- frontend/src/types/api_types/nodeTypeSchemas.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/types/api_types/nodeTypeSchemas.ts b/frontend/src/types/api_types/nodeTypeSchemas.ts index 6af28419..4eb07221 100644 --- a/frontend/src/types/api_types/nodeTypeSchemas.ts +++ b/frontend/src/types/api_types/nodeTypeSchemas.ts @@ -1,7 +1,7 @@ +import { RouteConditionGroup } from '@/types/api_types/routerSchemas' import { WorkflowNodeCoordinates } from '@/types/api_types/workflowSchemas' import { CoordinateExtent } from '@xyflow/react' import { ModelConstraints } from './modelMetadataSchemas' -import { RouteConditionGroup } from '@/types/api_types/routerSchemas' export interface NodeTypeSchema { node_type_name: string @@ -69,12 +69,15 @@ export interface FlowWorkflowNodeConfig { type?: string input_schema?: Record output_schema?: Record + output_json_schema?: string system_message?: string user_message?: string - few_shot_examples?: Array<{ - input: string - output: string - }> | Record[] + few_shot_examples?: + | Array<{ + input: string + output: string + }> + | Record[] llm_info?: { model?: string api_base?: string From 125e75debbee58e1ad14327da97baa51b221c1ea Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 03:53:56 +0000 Subject: [PATCH 11/20] fix: Parse source node output schema as JSON in router node generation --- frontend/src/store/flowSlice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/store/flowSlice.ts b/frontend/src/store/flowSlice.ts index c39c8928..4c52abda 100644 --- a/frontend/src/store/flowSlice.ts +++ b/frontend/src/store/flowSlice.ts @@ -53,7 +53,7 @@ function rebuildRouterNodeSchema(state: FlowState, routerNode: FlowWorkflowNode) const nodeTitle = sourceNodeConfig.title || sourceNode?.id const sourceSchema = sourceNodeConfig.output_json_schema - properties[nodeTitle] = sourceSchema + properties[nodeTitle] = JSON.parse(sourceSchema) } return properties }, From c6cd9f297ff31bb1eac1a703cfcf5820568bd744 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 04:03:13 +0000 Subject: [PATCH 12/20] fix: Update RouterNode schema handling to remove source node properties --- frontend/src/store/flowSlice.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/frontend/src/store/flowSlice.ts b/frontend/src/store/flowSlice.ts index 4c52abda..5c22e2b6 100644 --- a/frontend/src/store/flowSlice.ts +++ b/frontend/src/store/flowSlice.ts @@ -319,22 +319,20 @@ const flowSlice = createSlice({ const sourceNodeConfig = sourceNode ? state.nodeConfigs[sourceNode.id] : undefined // If target is a RouterNode and source has output schema, update target's schema - if (targetNode?.type === 'RouterNode' && sourceNodeConfig?.output_schema) { - const sourceTitle = sourceNodeConfig.title || sourceNode?.id - const currentSchema = { ...(targetNodeConfig?.output_schema || {}) } - - // Remove fields that start with this source's prefix - const prefix = `${sourceTitle}.` - Object.keys(currentSchema).forEach((key) => { - if (key.startsWith(prefix)) { - delete currentSchema[key] - } - }) + if (targetNode?.type === 'RouterNode') { + const sourceTitle = sourceNodeConfig.title + const currentSchema = targetNodeConfig.output_json_schema || '{}' + + // Remove the property that corresponds to the source node's title + let newSchema = JSON.parse(currentSchema) + delete newSchema.properties[sourceTitle] + newSchema['required'] = Object.keys(newSchema.properties) + const updatedSchema = JSON.stringify(newSchema) // Update the target node's schema state.nodeConfigs[targetNode.id] = { ...targetNodeConfig, - output_schema: currentSchema, + output_json_schema: updatedSchema, } } From c74666dfcb5efee4679dea69f03fc0ec61e2ad31 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 04:09:30 +0000 Subject: [PATCH 13/20] feat: Implement JSON schema intersection utility for node output schemas --- frontend/src/store/flowSlice.ts | 22 ++++------- frontend/src/utils/schemaUtils.ts | 64 +++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 15 deletions(-) diff --git a/frontend/src/store/flowSlice.ts b/frontend/src/store/flowSlice.ts index 5c22e2b6..5c21b1bb 100644 --- a/frontend/src/store/flowSlice.ts +++ b/frontend/src/store/flowSlice.ts @@ -8,6 +8,7 @@ import { } from '@/types/api_types/nodeTypeSchemas' import { TestInput, WorkflowDefinition } from '@/types/api_types/workflowSchemas' import { isTargetAncestorOfSource } from '@/utils/cyclicEdgeUtils' +import { computeJsonSchemaIntersection } from '@/utils/schemaUtils' import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { addEdge, applyEdgeChanges, applyNodeChanges, Connection, EdgeChange, NodeChange } from '@xyflow/react' import { isEqual } from 'lodash' @@ -341,30 +342,21 @@ const flowSlice = createSlice({ const incomingEdges = state.edges.filter((e) => e.target === targetNode.id && e.id !== edgeId) // Collect all source schemas from remaining edges - const schemas: Record[] = [] + const schemas: string[] = [] incomingEdges.forEach((ed) => { const sourceNode = state.nodes.find((n) => n.id === ed.source) const sourceNodeConfig = sourceNode ? state.nodeConfigs[sourceNode.id] : undefined - if (sourceNodeConfig?.output_schema) { - schemas.push(sourceNodeConfig.output_schema) + if (sourceNodeConfig?.output_json_schema) { + schemas.push(sourceNodeConfig.output_json_schema) } }) - // Compute intersection - let intersection: Record = {} - if (schemas.length > 0) { - const firstSchema = schemas[0] - const commonKeys = Object.keys(firstSchema).filter((key) => - schemas.every((sch) => sch.hasOwnProperty(key) && sch[key] === firstSchema[key]) - ) - commonKeys.forEach((key) => { - intersection[key] = firstSchema[key] - }) - } + // Compute intersection using the utility function + const intersectionSchema = computeJsonSchemaIntersection(schemas) state.nodeConfigs[targetNode.id] = { ...targetNodeConfig, - output_schema: intersection, + output_json_schema: intersectionSchema, } } diff --git a/frontend/src/utils/schemaUtils.ts b/frontend/src/utils/schemaUtils.ts index 8d9bf86c..930154f2 100644 --- a/frontend/src/utils/schemaUtils.ts +++ b/frontend/src/utils/schemaUtils.ts @@ -2,6 +2,70 @@ * Utility functions for handling JSON Schema operations */ +/** + * Checks if two JSON Schema property definitions are equal + */ +const arePropertiesEqual = (prop1: any, prop2: any): boolean => { + return JSON.stringify(prop1) === JSON.stringify(prop2) +} + +/** + * Computes the intersection of multiple JSON Schemas + * @param schemas Array of JSON Schema strings to intersect + * @returns A JSON Schema string representing the intersection + */ +export const computeJsonSchemaIntersection = (schemas: string[]): string => { + if (!schemas.length) { + return JSON.stringify({ + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }) + } + + try { + // Parse all schemas + const parsedSchemas = schemas.map((schema) => JSON.parse(schema)) + + // Get properties from first schema + const firstSchema = parsedSchemas[0] + const firstProperties = firstSchema.properties || {} + + // Find common properties that have the same definition across all schemas + const commonProperties: Record = {} + + Object.entries(firstProperties).forEach(([key, propDef]) => { + const isCommon = parsedSchemas.every((schema) => { + const properties = schema.properties || {} + return properties[key] && arePropertiesEqual(properties[key], propDef) + }) + + if (isCommon) { + commonProperties[key] = propDef + } + }) + + // Create intersection schema + const intersectionSchema = { + type: 'object', + properties: commonProperties, + required: Object.keys(commonProperties), + additionalProperties: false, + } + + return JSON.stringify(intersectionSchema) + } catch (error) { + console.error('Error computing schema intersection:', error) + return JSON.stringify({ + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }) + } +} + /** * Generates a JSON Schema from a simple schema object * @param schema Record of field names to their types From 9d6075082b13f958d1604a477a96939e707e0be5 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 04:40:30 +0000 Subject: [PATCH 14/20] refactor: Update coalesce node schema generation using JSON schema intersection --- frontend/src/store/flowSlice.ts | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/frontend/src/store/flowSlice.ts b/frontend/src/store/flowSlice.ts index 5c21b1bb..7f564b5b 100644 --- a/frontend/src/store/flowSlice.ts +++ b/frontend/src/store/flowSlice.ts @@ -84,27 +84,22 @@ function rebuildCoalesceNodeSchema(state: FlowState, coalesceNode: FlowWorkflowN const incomingEdges = state.edges.filter((edge) => edge.target === coalesceNode.id) // Collect all source schemas - const schemas: Record[] = incomingEdges.map((ed) => { + const schemas: string[] = [] + incomingEdges.forEach((ed) => { const sourceNode = state.nodes.find((n) => n.id === ed.source) - return sourceNode ? state.nodeConfigs[sourceNode.id]?.output_schema || {} : {} + const sourceNodeConfig = sourceNode ? state.nodeConfigs[sourceNode.id] : undefined + if (sourceNodeConfig?.output_json_schema) { + schemas.push(sourceNodeConfig.output_json_schema) + } }) - // Intersection - let intersection: Record = {} - if (schemas.length > 0) { - const firstSchema = schemas[0] - const commonKeys = Object.keys(firstSchema).filter((key) => - schemas.every((sch) => sch.hasOwnProperty(key) && sch[key] === firstSchema[key]) - ) - commonKeys.forEach((key) => { - intersection[key] = firstSchema[key] - }) - } + // Compute intersection using the utility function + const intersectionSchema = computeJsonSchemaIntersection(schemas) const coalesceNodeConfig = state.nodeConfigs[coalesceNode.id] || {} state.nodeConfigs[coalesceNode.id] = { ...coalesceNodeConfig, - output_schema: intersection, + output_json_schema: intersectionSchema, } } From 64cc4f2d7ad02d8f3db4b98704d73db05be243bf Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 04:41:54 +0000 Subject: [PATCH 15/20] fix: Update RouterNode schema generation to handle output JSON schema --- frontend/src/store/flowSlice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/store/flowSlice.ts b/frontend/src/store/flowSlice.ts index 7f564b5b..2ec37971 100644 --- a/frontend/src/store/flowSlice.ts +++ b/frontend/src/store/flowSlice.ts @@ -251,7 +251,7 @@ const flowSlice = createSlice({ } // If output_schema changed, rebuild connected RouterNode/CoalesceNode schemas - if (data?.output_schema) { + if (data?.output_schema || data?.output_json_schema) { const connectedRouterNodes = state.nodes.filter( (targetNode) => targetNode.type === 'RouterNode' && From 76741d3c804c9cec256343151fe0b4a0a8e60207 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 04:46:15 +0000 Subject: [PATCH 16/20] fix: Disable output schema editing for CoalesceNode --- frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx index fbce1945..e07f83da 100644 --- a/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx +++ b/frontend/src/components/nodes/nodeSidebar/NodeSidebar.tsx @@ -626,6 +626,7 @@ const NodeSidebar: React.FC = ({ nodeID }) => { currentNodeConfig?.has_fixed_output || (currentNodeConfig?.llm_info?.model && !currentModelConstraints?.supports_JSON_output) || node.type === 'RouterNode' || + node.type === 'CoalesceNode' || false return (
From 22a12c90d0b49a774cdb4db4f688f5da49fa12c1 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 04:47:23 +0000 Subject: [PATCH 17/20] fix: Improve variable selection display in CoalesceNode --- .../components/nodes/logic/CoalesceNode.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/nodes/logic/CoalesceNode.tsx b/frontend/src/components/nodes/logic/CoalesceNode.tsx index 11787395..5d081ca4 100644 --- a/frontend/src/components/nodes/logic/CoalesceNode.tsx +++ b/frontend/src/components/nodes/logic/CoalesceNode.tsx @@ -1,16 +1,15 @@ -import React, { useState, useRef, useEffect, useMemo } from 'react' +import { FlowWorkflowNode } from '@/types/api_types/nodeTypeSchemas' +import { Button, Card, Divider, Select, SelectItem } from '@heroui/react' +import { Icon } from '@iconify/react' import { Handle, Position, useConnection } from '@xyflow/react' -import BaseNode from '../BaseNode' -import { Input, Card, Divider, Button, Select, SelectItem } from '@heroui/react' +import isEqual from 'lodash/isEqual' +import React, { useEffect, useMemo, useRef, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { updateNodeConfigOnly } from '../../../store/flowSlice' -import styles from '../DynamicNode.module.css' -import { Icon } from '@iconify/react' import { RootState } from '../../../store/store' +import BaseNode from '../BaseNode' +import styles from '../DynamicNode.module.css' import NodeOutputDisplay from '../NodeOutputDisplay' -import isEqual from 'lodash/isEqual' -import { FlowWorkflowNodeConfig } from '@/types/api_types/nodeTypeSchemas' -import { FlowWorkflowNode } from '@/types/api_types/nodeTypeSchemas' interface CoalesceNodeProps { id: string @@ -357,6 +356,9 @@ export const CoalesceNode: React.FC = ({ id, data }) => { key={variable.value} value={variable.value} textValue={variable.label} + classNames={{ + title: 'w-full whitespace-normal break-words', + }} >
{variable.label} From 113a48e1b10e344346282dc5730e58df0ae3bf42 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 04:50:07 +0000 Subject: [PATCH 18/20] fix: Improve field selection display with full-width and word-wrapping --- .../nodes/nodeSidebar/SchemaEditor.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/nodes/nodeSidebar/SchemaEditor.tsx b/frontend/src/components/nodes/nodeSidebar/SchemaEditor.tsx index 51aa4d70..97545a7d 100644 --- a/frontend/src/components/nodes/nodeSidebar/SchemaEditor.tsx +++ b/frontend/src/components/nodes/nodeSidebar/SchemaEditor.tsx @@ -327,7 +327,13 @@ const SchemaField: React.FC = ({ path, value, onUpdate, onDelete, re )} > {availableFields.map((field) => ( - + {field} ))} @@ -977,7 +983,13 @@ const SchemaEditor: React.FC = ({ )} > {availableFields.map((field) => ( - + {field} ))} From 77273adfae710e21c31ed6739b6b1b1203c06a69 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 05:00:21 +0000 Subject: [PATCH 19/20] feat: Add error display for CoalesceNode --- frontend/src/components/nodes/logic/CoalesceNode.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/nodes/logic/CoalesceNode.tsx b/frontend/src/components/nodes/logic/CoalesceNode.tsx index 5d081ca4..213c6974 100644 --- a/frontend/src/components/nodes/logic/CoalesceNode.tsx +++ b/frontend/src/components/nodes/logic/CoalesceNode.tsx @@ -1,3 +1,4 @@ +import NodeErrorDisplay from '@/components/nodes/NodeErrorDisplay' import { FlowWorkflowNode } from '@/types/api_types/nodeTypeSchemas' import { Button, Card, Divider, Select, SelectItem } from '@heroui/react' import { Icon } from '@iconify/react' @@ -37,6 +38,7 @@ export const CoalesceNode: React.FC = ({ id, data }) => { // Node's output for display const nodeOutput = useSelector((state: RootState) => state.flow.nodes.find((node) => node.id === id)?.data?.run) + const nodeError = useSelector((state: RootState) => state.flow.nodes.find((node) => node.id === id)?.data?.error) // Node's config const nodeConfig = useSelector((state: RootState) => state.flow.nodeConfigs[id]) @@ -384,7 +386,7 @@ export const CoalesceNode: React.FC = ({ id, data }) => { )}
- + {nodeError && } {/* Display node's output if it exists */} From 7e2a27c9f0a6a0348dbd3313d89ec4c21c61ccd3 Mon Sep 17 00:00:00 2001 From: Srijan Patel Date: Wed, 12 Feb 2025 05:02:24 +0000 Subject: [PATCH 20/20] fix: Handle CoalesceNode with all None inputs in workflow execution --- backend/app/execution/workflow_executor.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/app/execution/workflow_executor.py b/backend/app/execution/workflow_executor.py index 538e2df2..f0a53078 100644 --- a/backend/app/execution/workflow_executor.py +++ b/backend/app/execution/workflow_executor.py @@ -234,6 +234,11 @@ async def _execute_node(self, node_id: str) -> Optional[BaseNodeOutput]: ): self._outputs[node_id] = None return None + elif node.node_type == "CoalesceNode" and all( + [v is None for v in node_input.values()] + ): + self._outputs[node_id] = None + return None # Remove None values from input node_input = {k: v for k, v in node_input.items() if v is not None}