diff --git a/rnd/autogpt_builder/src/components/CustomNode.tsx b/rnd/autogpt_builder/src/components/CustomNode.tsx index 1c3f287f7..801f3d2cf 100644 --- a/rnd/autogpt_builder/src/components/CustomNode.tsx +++ b/rnd/autogpt_builder/src/components/CustomNode.tsx @@ -8,6 +8,12 @@ type Schema = { type: string; properties: { [key: string]: any }; required?: string[]; + enum?: string[]; + items?: Schema; + additionalProperties?: { type: string }; + allOf?: any[]; + anyOf?: any[]; + oneOf?: any[]; }; type CustomNodeData = { @@ -25,6 +31,9 @@ type CustomNodeData = { const CustomNode: FC> = ({ data, id }) => { const [isPropertiesOpen, setIsPropertiesOpen] = useState(data.isPropertiesOpen || false); + const [keyValuePairs, setKeyValuePairs] = useState<{ key: string, value: string }[]>([]); + const [newKey, setNewKey] = useState(''); + const [newValue, setNewValue] = useState(''); const [isModalOpen, setIsModalOpen] = useState(false); const [activeKey, setActiveKey] = useState(null); const [modalValue, setModalValue] = useState(''); @@ -76,41 +85,24 @@ const CustomNode: FC> = ({ data, id }) => { }; const handleInputChange = (key: string, value: any) => { - const newValues = { ...data.hardcodedValues, [key]: value }; + const keys = key.split('.'); + const newValues = JSON.parse(JSON.stringify(data.hardcodedValues)); + let current = newValues; + + for (let i = 0; i < keys.length - 1; i++) { + if (!current[keys[i]]) current[keys[i]] = {}; + current = current[keys[i]]; + } + current[keys[keys.length - 1]] = value; + console.log(`Updating hardcoded values for node ${id}:`, newValues); data.setHardcodedValues(newValues); setErrors((prevErrors) => ({ ...prevErrors, [key]: null })); }; - const validateInput = (key: string, value: any, schema: any) => { - switch (schema.type) { - case 'string': - if (schema.enum && !schema.enum.includes(value)) { - return `Invalid value for ${key}`; - } - break; - case 'boolean': - if (typeof value !== 'boolean') { - return `Invalid value for ${key}`; - } - break; - case 'number': - if (typeof value !== 'number') { - return `Invalid value for ${key}`; - } - break; - case 'array': - if (!Array.isArray(value) || value.some((item: any) => typeof item !== 'string')) { - return `Invalid value for ${key}`; - } - if (schema.minItems && value.length < schema.minItems) { - return `${key} requires at least ${schema.minItems} items`; - } - break; - default: - return null; - } - return null; + const getValue = (key: string) => { + const keys = key.split('.'); + return keys.reduce((acc, k) => (acc && acc[k] !== undefined) ? acc[k] : '', data.hardcodedValues); }; const isHandleConnected = (key: string) => { @@ -123,66 +115,180 @@ const CustomNode: FC> = ({ data, id }) => { }); }; + const handleAddProperty = () => { + if (newKey && newValue) { + const newPairs = [...keyValuePairs, { key: newKey, value: newValue }]; + setKeyValuePairs(newPairs); + setNewKey(''); + setNewValue(''); + const expectedFormat = newPairs.reduce((acc, pair) => ({ ...acc, [pair.key]: pair.value }), {}); + handleInputChange('expected_format', expectedFormat); + } + }; + const handleInputClick = (key: string) => { setActiveKey(key); - setModalValue(data.hardcodedValues[key] || ''); + const value = getValue(key); + setModalValue(typeof value === 'object' ? JSON.stringify(value, null, 2) : value); setIsModalOpen(true); }; const handleModalSave = (value: string) => { if (activeKey) { - handleInputChange(activeKey, value); + try { + const parsedValue = JSON.parse(value); + handleInputChange(activeKey, parsedValue); + } catch (error) { + handleInputChange(activeKey, value); + } } setIsModalOpen(false); setActiveKey(null); }; - const addArrayItem = (key: string) => { - const currentValues = data.hardcodedValues[key] || []; - handleInputChange(key, [...currentValues, '']); - }; + const renderInputField = (key: string, schema: any, parentKey: string = ''): JSX.Element => { + const fullKey = parentKey ? `${parentKey}.${key}` : key; + const error = errors[fullKey]; + const value = getValue(fullKey); - const removeArrayItem = (key: string, index: number) => { - const currentValues = data.hardcodedValues[key] || []; - currentValues.splice(index, 1); - handleInputChange(key, [...currentValues]); - }; + if (isHandleConnected(fullKey)) { + return
Connected
; + } - const handleArrayItemChange = (key: string, index: number, value: string) => { - const currentValues = data.hardcodedValues[key] || []; - currentValues[index] = value; - handleInputChange(key, [...currentValues]); - }; + const renderClickableInput = (displayValue: string) => ( +
handleInputClick(fullKey)}> + {displayValue} +
+ ); - const addDynamicTextInput = () => { - const dynamicKeyPrefix = 'texts_$_'; - const currentKeys = Object.keys(data.hardcodedValues).filter(key => key.startsWith(dynamicKeyPrefix)); - const nextIndex = currentKeys.length + 1; - const newKey = `${dynamicKeyPrefix}${nextIndex}`; - handleInputChange(newKey, ''); - }; + if (schema.type === 'object' && schema.properties) { + return ( +
+ {key}: + {Object.entries(schema.properties).map(([propKey, propSchema]: [string, any]) => ( +
+ {renderInputField(propKey, propSchema, fullKey)} +
+ ))} +
+ ); + } - const removeDynamicTextInput = (key: string) => { - const newValues = { ...data.hardcodedValues }; - delete newValues[key]; - data.setHardcodedValues(newValues); - }; + if (schema.type === 'object' && schema.additionalProperties) { + const objectValue = value || {}; + return ( +
+ {key}: + {Object.entries(objectValue).map(([propKey, propValue]: [string, any]) => ( +
+
handleInputClick(`${fullKey}.${propKey}`)}> + {propKey}: {typeof propValue === 'object' ? JSON.stringify(propValue, null, 2) : propValue} +
+ +
+ ))} + {key === 'expected_format' && ( +
+ {keyValuePairs.map((pair, index) => ( +
+ { + const newPairs = [...keyValuePairs]; + newPairs[index].key = e.target.value; + setKeyValuePairs(newPairs); + const expectedFormat = newPairs.reduce((acc, pair) => ({ ...acc, [pair.key]: pair.value }), {}); + handleInputChange('expected_format', expectedFormat); + }} + /> + { + const newPairs = [...keyValuePairs]; + newPairs[index].value = e.target.value; + setKeyValuePairs(newPairs); + const expectedFormat = newPairs.reduce((acc, pair) => ({ ...acc, [pair.key]: pair.value }), {}); + handleInputChange('expected_format', expectedFormat); + }} + /> +
+ ))} +
+ setNewKey(e.target.value)} + /> + setNewValue(e.target.value)} + /> +
+ +
+ )} + {error && {error}} +
+ ); + } - const handleDynamicTextInputChange = (key: string, value: string) => { - handleInputChange(key, value); - }; + if (schema.anyOf) { + const types = schema.anyOf.map((s: any) => s.type); + if (types.includes('string') && types.includes('null')) { + return ( +
+ {renderClickableInput(value || `Enter ${key} (optional)`)} + {error && {error}} +
+ ); + } + } + + if (schema.allOf) { + return ( +
+ {key}: + {schema.allOf[0].properties && Object.entries(schema.allOf[0].properties).map(([propKey, propSchema]: [string, any]) => ( +
+ {renderInputField(propKey, propSchema, fullKey)} +
+ ))} +
+ ); + } + + if (schema.oneOf) { + return ( +
+ {key}: + {schema.oneOf[0].properties && Object.entries(schema.oneOf[0].properties).map(([propKey, propSchema]: [string, any]) => ( +
+ {renderInputField(propKey, propSchema, fullKey)} +
+ ))} +
+ ); + } - const renderInputField = (key: string, schema: any) => { - const error = errors[key]; switch (schema.type) { case 'string': return schema.enum ? ( -
+
handleInputChange(key, true)} - /> - True - - +
+ {error && {error}}
); case 'number': + case 'integer': return ( -
+
handleInputChange(key, parseFloat(e.target.value))} + value={value || ''} + onChange={(e) => handleInputChange(fullKey, parseFloat(e.target.value))} className="number-input" /> {error && {error}} @@ -237,23 +333,23 @@ const CustomNode: FC> = ({ data, id }) => { ); case 'array': if (schema.items && schema.items.type === 'string') { - const arrayValues = data.hardcodedValues[key] || []; + const arrayValues = value || []; return ( -
+
{arrayValues.map((item: string, index: number) => ( -
+
handleArrayItemChange(key, index, e.target.value)} + onChange={(e) => handleInputChange(`${fullKey}.${index}`, e.target.value)} className="array-item-input" /> -
))} - {error && {error}} @@ -262,52 +358,33 @@ const CustomNode: FC> = ({ data, id }) => { } return null; default: - return null; + return ( +
+ {renderClickableInput(value ? `${key} (Complex)` : `Enter ${key} (Complex)`)} + {error && {error}} +
+ ); } }; - const renderDynamicTextFields = () => { - const dynamicKeyPrefix = 'texts_$_'; - const dynamicKeys = Object.keys(data.hardcodedValues).filter(key => key.startsWith(dynamicKeyPrefix)); - - return dynamicKeys.map((key, index) => ( -
-
- - {key} - {!isHandleConnected(key) && ( - <> - handleDynamicTextInputChange(key, e.target.value)} - className="dynamic-text-input" - /> - - - )} -
-
- )); - }; - const validateInputs = () => { const newErrors: { [key: string]: string | null } = {}; - Object.keys(data.inputSchema.properties).forEach((key) => { - const value = data.hardcodedValues[key]; - const schema = data.inputSchema.properties[key]; - const error = validateInput(key, value, schema); - if (error) { - newErrors[key] = error; - } - }); + const validateRecursive = (schema: any, parentKey: string = '') => { + Object.entries(schema.properties).forEach(([key, propSchema]: [string, any]) => { + const fullKey = parentKey ? `${parentKey}.${key}` : key; + const value = getValue(fullKey); + + if (propSchema.type === 'object' && propSchema.properties) { + validateRecursive(propSchema, fullKey); + } else { + if (propSchema.required && !value) { + newErrors[fullKey] = `${fullKey} is required`; + } + } + }); + }; + + validateRecursive(data.inputSchema); setErrors(newErrors); return Object.values(newErrors).every((error) => error === null); }; @@ -320,9 +397,8 @@ const CustomNode: FC> = ({ data, id }) => { } }; - return ( -
+
{data.blockType || data.title}
-
- )} +
+ + {key} +
+ {renderInputField(key, schema)}
))}
@@ -397,4 +453,4 @@ const CustomNode: FC> = ({ data, id }) => { ); }; -export default memo(CustomNode); \ No newline at end of file +export default memo(CustomNode); diff --git a/rnd/autogpt_builder/src/components/Flow.tsx b/rnd/autogpt_builder/src/components/Flow.tsx index f79e07f6c..c07a9781b 100644 --- a/rnd/autogpt_builder/src/components/Flow.tsx +++ b/rnd/autogpt_builder/src/components/Flow.tsx @@ -19,6 +19,7 @@ import './flow.css'; type Schema = { type: string; properties: { [key: string]: any }; + additionalProperties?: { type: string }; required?: string[]; }; @@ -44,12 +45,6 @@ type AvailableNode = { outputSchema: Schema; }; -interface ExecData { - node_id: string; - status: string; - output_data: any; -} - const Sidebar: React.FC<{isOpen: boolean, availableNodes: AvailableNode[], addNode: (id: string, name: string) => void}> = ({isOpen, availableNodes, addNode}) => { const [searchQuery, setSearchQuery] = useState(''); @@ -61,28 +56,17 @@ const Sidebar: React.FC<{isOpen: boolean, availableNodes: AvailableNode[], addNo ); return ( -
-

Nodes

+
+

Nodes

setSearchQuery(e.target.value)} /> {filteredNodes.map((node) => ( -
- {node.name} +
+ {node.name}
))} @@ -183,38 +167,61 @@ const Flow: React.FC = () => { }; const prepareNodeInputData = (node: Node, allNodes: Node[], allEdges: Edge[]) => { - console.log("Preparing input data for node:", node.id, node.data.blockType); + console.log("Preparing input data for node:", node.id, node.data.blockType); - const blockSchema = availableNodes.find(n => n.id === node.data.block_id)?.inputSchema; + const blockSchema = availableNodes.find(n => n.id === node.data.block_id)?.inputSchema; - if (!blockSchema) { - console.error(`Schema not found for block ID: ${node.data.block_id}`); - return {}; + if (!blockSchema) { + console.error(`Schema not found for block ID: ${node.data.block_id}`); + return {}; + } + + const getNestedData = (schema: Schema, values: { [key: string]: any }): { [key: string]: any } => { + let inputData: { [key: string]: any } = {}; + + if (schema.properties) { + Object.keys(schema.properties).forEach((key) => { + if (values[key] !== undefined) { + if (schema.properties[key].type === 'object') { + inputData[key] = getNestedData(schema.properties[key], values[key]); + } else { + inputData[key] = values[key]; + } + } + }); } - let inputData: { [key: string]: any } = { ...node.data.hardcodedValues }; + if (schema.additionalProperties) { + inputData = { ...inputData, ...values }; + } - // Get data from connected nodes - const incomingEdges = allEdges.filter(edge => edge.target === node.id); - incomingEdges.forEach(edge => { - const sourceNode = allNodes.find(n => n.id === edge.source); - if (sourceNode && sourceNode.data.output_data) { - const outputKey = Object.keys(sourceNode.data.output_data)[0]; // Assuming single output - inputData[edge.targetHandle as string] = sourceNode.data.output_data[outputKey]; - } - }); - - // Filter out any inputs that are not in the block's schema - Object.keys(inputData).forEach(key => { - if (!blockSchema.properties[key]) { - delete inputData[key]; - } - }); - - console.log(`Final prepared input for ${node.data.blockType} (${node.id}):`, inputData); return inputData; }; + let inputData = getNestedData(blockSchema, node.data.hardcodedValues); + + // Get data from connected nodes + const incomingEdges = allEdges.filter(edge => edge.target === node.id); + incomingEdges.forEach(edge => { + const sourceNode = allNodes.find(n => n.id === edge.source); + if (sourceNode && sourceNode.data.output_data) { + const outputKey = Object.keys(sourceNode.data.output_data)[0]; // Assuming single output + inputData[edge.targetHandle as string] = sourceNode.data.output_data[outputKey]; + } + }); + + // Filter out any inputs that are not in the block's schema + Object.keys(inputData).forEach(key => { + if (!blockSchema.properties[key]) { + delete inputData[key]; + } + }); + + console.log(`Final prepared input for ${node.data.blockType} (${node.id}):`, inputData); + return inputData; +}; + + const runAgent = async () => { try { console.log("All nodes before formatting:", nodes); @@ -246,11 +253,19 @@ const Flow: React.FC = () => { }; }); + const links = edges.map(edge => ({ + source_id: edge.source, + sink_id: edge.target, + source_name: edge.sourceHandle || '', + sink_name: edge.targetHandle || '' + })); + const payload = { id: agentId || '', name: 'Agent Name', description: 'Agent Description', nodes: formattedNodes, + links: links // Ensure this field is included }; console.log("Payload being sent to the API:", JSON.stringify(payload, null, 2)); @@ -268,14 +283,12 @@ const Flow: React.FC = () => { } const createData = await createResponse.json(); - const newAgentId = createData.id; setAgentId(newAgentId); console.log('Response from the API:', JSON.stringify(createData, null, 2)); const executeResponse = await fetch(`${apiUrl}/graphs/${newAgentId}/execute`, { - method: 'POST', headers: { 'Content-Type': 'application/json', @@ -290,14 +303,15 @@ const Flow: React.FC = () => { const executeData = await executeResponse.json(); const runId = executeData.id; - const pollExecution = async () => { const response = await fetch(`${apiUrl}/graphs/${newAgentId}/executions/${runId}`); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } + const data = await response.json(); - data.forEach(updateNodeData); + updateNodesWithExecutionData(data); + if (data.every((node: any) => node.status === 'COMPLETED')) { console.log('All nodes completed execution'); } else { @@ -312,47 +326,29 @@ const Flow: React.FC = () => { } }; - const updateNodesWithExecutionData = (executionData: any[]) => { - setNodes((nds) => - nds.map((node) => { - const nodeExecution = executionData.find((exec) => exec.node_id === node.id); - if (nodeExecution) { - return { - ...node, - data: { - ...node.data, - status: nodeExecution.status, - output_data: nodeExecution.output_data, - isPropertiesOpen: true, - }, - }; - } - return node; - }) - ); - }; + +const updateNodesWithExecutionData = (executionData: any[]) => { + setNodes((nds) => + nds.map((node) => { + const nodeExecution = executionData.find((exec) => exec.node_id === node.id); + if (nodeExecution) { + return { + ...node, + data: { + ...node.data, + status: nodeExecution.status, + output_data: nodeExecution.output_data, + isPropertiesOpen: true, + }, + }; + } + return node; + }) + ); +}; const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen); - const updateNodeData = (execData: ExecData) => { - setNodes((nds) => - nds.map((node) => { - if (node.id === execData.node_id) { - return { - ...node, - data: { - ...node.data, - status: execData.status, - output_data: execData.output_data, - isPropertiesOpen: true, // Open the properties - }, - }; - } - return node; - }) - ); - }; - return (