diff --git a/rnd/autogpt_builder/src/components/CustomNode.tsx b/rnd/autogpt_builder/src/components/CustomNode/CustomNode.tsx similarity index 98% rename from rnd/autogpt_builder/src/components/CustomNode.tsx rename to rnd/autogpt_builder/src/components/CustomNode/CustomNode.tsx index aa06a9b27a..ae9d0cb823 100644 --- a/rnd/autogpt_builder/src/components/CustomNode.tsx +++ b/rnd/autogpt_builder/src/components/CustomNode/CustomNode.tsx @@ -1,12 +1,12 @@ import React, { useState, useEffect, FC, memo } from 'react'; import { Handle, Position, NodeProps } from 'reactflow'; import 'reactflow/dist/style.css'; -import './customnode.css'; -import ModalComponent from './ModalComponent'; -import { Button } from './ui/button'; -import { Input } from './ui/input'; +import '../customnode.css'; +import ModalComponent from '../ModalComponent'; +import { Button } from '../ui/button'; +import { Input } from '../ui/input'; import { BlockSchema } from '@/lib/types'; -import SchemaTooltip from './SchemaTooltip'; +import SchemaTooltip from '../SchemaTooltip'; import { beautifyString } from '@/lib/utils'; type CustomNodeData = { diff --git a/rnd/autogpt_builder/src/components/CustomNode/InputField.tsx b/rnd/autogpt_builder/src/components/CustomNode/InputField.tsx new file mode 100644 index 0000000000..3d237b7e1b --- /dev/null +++ b/rnd/autogpt_builder/src/components/CustomNode/InputField.tsx @@ -0,0 +1,106 @@ +import React from 'react'; +import { Input } from '../ui/input'; +import { Button } from '../ui/button'; +import { beautifyString } from '@/lib/utils'; +import { InputFieldProps } from './types'; + +export const InputField: React.FC = ({ + schema, + fullKey, + displayKey, + value, + error, + handleInputChange, + handleInputClick, +}) => { + const renderClickableInput = (value: string | null = null, placeholder: string = "") => ( +
handleInputClick(fullKey)}> + {value || {placeholder}} +
+ ); + + if (schema.type === 'object' && schema.properties) { + return ( +
+ {displayKey}: + {Object.entries(schema.properties).map(([propKey, propSchema]: [string, any]) => ( +
+ +
+ ))} +
+ ); + } + + if (schema.type === 'string') { + return schema.enum ? ( +
+ + {error && {error}} +
+ ) : ( +
+ {renderClickableInput(value, schema.placeholder || `Enter ${displayKey}`)} + {error && {error}} +
+ ); + } + + if (schema.type === 'boolean') { + return ( +
+ + {error && {error}} +
+ ); + } + + if (schema.type === 'number' || schema.type === 'integer') { + return ( +
+ handleInputChange(fullKey, parseFloat(e.target.value))} + className="number-input" + /> + {error && {error}} +
+ ); + } + + // Default case for complex or unhandled types + return ( +
+ {renderClickableInput(value, schema.placeholder || `Enter ${beautifyString(displayKey)} (Complex)`)} + {error && {error}} +
+ ); +}; \ No newline at end of file diff --git a/rnd/autogpt_builder/src/components/CustomNode/NodeContent.tsx b/rnd/autogpt_builder/src/components/CustomNode/NodeContent.tsx new file mode 100644 index 0000000000..f7c479a3b1 --- /dev/null +++ b/rnd/autogpt_builder/src/components/CustomNode/NodeContent.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Handle, Position } from 'reactflow'; +import { CustomNodeData } from './types'; +import { InputField } from './InputField'; +import SchemaTooltip from '../SchemaTooltip'; +import { isHandleConnected } from './utils'; +import { beautifyString } from '@/lib/utils'; + +type NodeContentProps = { + data: CustomNodeData; + isAdvancedOpen: boolean; + handleInputClick: (key: string) => void; +}; + +export const NodeContent: React.FC = ({ data, isAdvancedOpen, handleInputClick }) => { + return ( +
+
+ {data.inputSchema && + Object.entries(data.inputSchema.properties).map(([key, schema]) => { + const isRequired = data.inputSchema.required?.includes(key); + return (isRequired || isAdvancedOpen) && ( +
+
+ + {schema.title || beautifyString(key)} + +
+ data.setHardcodedValues({ ...data.hardcodedValues, [key]: value })} + handleInputClick={handleInputClick} + /> +
+ ); + })} +
+
+ {data.outputSchema && generateHandles(data.outputSchema, 'source')} +
+
+ ); +}; \ No newline at end of file diff --git a/rnd/autogpt_builder/src/components/CustomNode/NodeFooter.tsx b/rnd/autogpt_builder/src/components/CustomNode/NodeFooter.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rnd/autogpt_builder/src/components/CustomNode/NodeHeader.tsx b/rnd/autogpt_builder/src/components/CustomNode/NodeHeader.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rnd/autogpt_builder/src/components/CustomNode/NodeProperties.tsx b/rnd/autogpt_builder/src/components/CustomNode/NodeProperties.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rnd/autogpt_builder/src/components/CustomNode/hooks/useCustomNode.ts b/rnd/autogpt_builder/src/components/CustomNode/hooks/useCustomNode.ts new file mode 100644 index 0000000000..f53c1df4de --- /dev/null +++ b/rnd/autogpt_builder/src/components/CustomNode/hooks/useCustomNode.ts @@ -0,0 +1,55 @@ +import { useState, useEffect } from 'react'; +import { CustomNodeData } from './types'; + +export const useCustomNode = (data: CustomNodeData, id: string) => { + const [isPropertiesOpen, setIsPropertiesOpen] = useState(data.isPropertiesOpen || false); + const [isAdvancedOpen, setIsAdvancedOpen] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); + const [activeKey, setActiveKey] = useState(null); + const [modalValue, setModalValue] = useState(''); + + useEffect(() => { + if (data.output_data || data.status) { + setIsPropertiesOpen(true); + } + }, [data.output_data, data.status]); + + useEffect(() => { + console.log(`Node ${id} data:`, data); + }, [id, data]); + + const toggleProperties = () => setIsPropertiesOpen(!isPropertiesOpen); + const toggleAdvancedSettings = () => setIsAdvancedOpen(!isAdvancedOpen); + + const handleInputClick = (key: string) => { + setActiveKey(key); + const value = getValue(key, data.hardcodedValues); + setModalValue(typeof value === 'object' ? JSON.stringify(value, null, 2) : value); + setIsModalOpen(true); + }; + + const handleModalSave = (value: string) => { + if (activeKey) { + try { + const parsedValue = JSON.parse(value); + data.setHardcodedValues({ ...data.hardcodedValues, [activeKey]: parsedValue }); + } catch (error) { + data.setHardcodedValues({ ...data.hardcodedValues, [activeKey]: value }); + } + } + setIsModalOpen(false); + setActiveKey(null); + }; + + return { + isPropertiesOpen, + isAdvancedOpen, + isModalOpen, + modalValue, + activeKey, + toggleProperties, + toggleAdvancedSettings, + handleInputClick, + handleModalSave, + }; +}; \ No newline at end of file diff --git a/rnd/autogpt_builder/src/components/CustomNode/types/index.ts b/rnd/autogpt_builder/src/components/CustomNode/types/index.ts new file mode 100644 index 0000000000..0c138bdd35 --- /dev/null +++ b/rnd/autogpt_builder/src/components/CustomNode/types/index.ts @@ -0,0 +1,24 @@ +import { BlockSchema } from '@/lib/types'; + +export type CustomNodeData = { + blockType: string; + title: string; + inputSchema: BlockSchema; + outputSchema: BlockSchema; + hardcodedValues: { [key: string]: any }; + setHardcodedValues: (values: { [key: string]: any }) => void; + connections: Array<{ source: string; sourceHandle: string; target: string; targetHandle: string }>; + isPropertiesOpen: boolean; + status?: string; + output_data?: any; +}; + +export type InputFieldProps = { + schema: any; + fullKey: string; + displayKey: string; + value: any; + error: string | null; + handleInputChange: (key: string, value: any) => void; + handleInputClick: (key: string) => void; +}; \ No newline at end of file diff --git a/rnd/autogpt_builder/src/components/CustomNode/utils/index.ts b/rnd/autogpt_builder/src/components/CustomNode/utils/index.ts new file mode 100644 index 0000000000..498e849dad --- /dev/null +++ b/rnd/autogpt_builder/src/components/CustomNode/utils/index.ts @@ -0,0 +1,22 @@ +import { BlockSchema } from '@/lib/types'; + +export const hasOptionalFields = (schema: BlockSchema): boolean => { + return schema && Object.keys(schema.properties).some((key) => { + return !(schema.required?.includes(key)); + }); +}; + +export const getValue = (key: string, hardcodedValues: { [key: string]: any }): any => { + const keys = key.split('.'); + return keys.reduce((acc, k) => (acc && acc[k] !== undefined) ? acc[k] : '', hardcodedValues); +}; + +export const isHandleConnected = (key: string, connections: any[], id: string): boolean => { + return connections && connections.some((conn: any) => { + if (typeof conn === 'string') { + const [source, target] = conn.split(' -> '); + return target.includes(key) && target.includes(id); + } + return conn.target === id && conn.targetHandle === key; + }); +}; diff --git a/rnd/autogpt_builder/src/components/Flow.tsx b/rnd/autogpt_builder/src/components/Flow.tsx index 7532525039..273d8c7b9e 100644 --- a/rnd/autogpt_builder/src/components/Flow.tsx +++ b/rnd/autogpt_builder/src/components/Flow.tsx @@ -13,7 +13,7 @@ import ReactFlow, { Connection, } from 'reactflow'; import 'reactflow/dist/style.css'; -import CustomNode from './CustomNode'; +import CustomNode from './CustomNode/CustomNode'; import './flow.css'; import AutoGPTServerAPI, { Block, Graph } from '@/lib/autogpt_server_api'; import { ObjectSchema } from '@/lib/types';