From e94a7b08c98ee0f46ace7082389e4a8ede2fe1a4 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski <34861343+kcze@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:14:20 +0100 Subject: [PATCH] refactor(builder): Move `renderInputField` to `NodeInputField` component (#7581) --- .../src/components/CustomNode.tsx | 275 +--------------- .../src/components/NodeInputField.tsx | 295 ++++++++++++++++++ 2 files changed, 307 insertions(+), 263 deletions(-) create mode 100644 rnd/autogpt_builder/src/components/NodeInputField.tsx diff --git a/rnd/autogpt_builder/src/components/CustomNode.tsx b/rnd/autogpt_builder/src/components/CustomNode.tsx index a1cbd0703c8a..e3c4fff6f433 100644 --- a/rnd/autogpt_builder/src/components/CustomNode.tsx +++ b/rnd/autogpt_builder/src/components/CustomNode.tsx @@ -4,12 +4,11 @@ import 'reactflow/dist/style.css'; import './customnode.css'; import InputModalComponent from './InputModalComponent'; import OutputModalComponent from './OutputModalComponent'; -import { Button } from './ui/button'; -import { Input } from './ui/input'; import { BlockSchema } from '@/lib/types'; import { beautifyString } from '@/lib/utils'; import { Switch } from "@/components/ui/switch" import NodeHandle from './NodeHandle'; +import NodeInputField from './NodeInputField'; type CustomNodeData = { blockType: string; @@ -27,9 +26,6 @@ type CustomNodeData = { const CustomNode: FC> = ({ data, id }) => { const [isOutputOpen, setIsOutputOpen] = useState(data.isOutputOpen || false); const [isAdvancedOpen, setIsAdvancedOpen] = useState(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(''); @@ -88,6 +84,7 @@ const CustomNode: FC> = ({ data, id }) => { }; const getValue = (key: string) => { + console.log(`Getting value for key: ${key}`); const keys = key.split('.'); return keys.reduce((acc, k) => (acc && acc[k] !== undefined) ? acc[k] : '', data.hardcodedValues); }; @@ -104,18 +101,8 @@ 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) => { + console.log(`Opening modal for key: ${key}`); setActiveKey(key); const value = getValue(key); setModalValue(typeof value === 'object' ? JSON.stringify(value, null, 2) : value); @@ -135,252 +122,6 @@ const CustomNode: FC> = ({ data, id }) => { setActiveKey(null); }; - const renderInputField = (key: string, schema: any, parentKey: string = '', displayKey: string = ''): JSX.Element => { - const fullKey = parentKey ? `${parentKey}.${key}` : key; - const error = errors[fullKey]; - const value = getValue(fullKey); - if (displayKey === '') { - displayKey = key; - } - - if (isHandleConnected(fullKey)) { - return <>; - } - - const renderClickableInput = (value: string | null = null, placeholder: string = "", secret: boolean = false) => { - - // if secret is true, then the input field will be a password field if the value is not null - return secret ? ( -
handleInputClick(fullKey)}> - {value ? ******** : {placeholder}} -
- ) : ( -
handleInputClick(fullKey)}> - {value || {placeholder}} -
- ) - }; - - if (schema.type === 'object' && schema.properties) { - return ( -
- {displayKey}: - {Object.entries(schema.properties).map(([propKey, propSchema]: [string, any]) => ( -
- {renderInputField(propKey, propSchema, fullKey, propSchema.title || beautifyString(propKey))} -
- ))} -
- ); - } - - if (schema.type === 'object' && schema.additionalProperties) { - const objectValue = value || {}; - return ( -
- {displayKey}: - {Object.entries(objectValue).map(([propKey, propValue]: [string, any]) => ( -
-
handleInputClick(`${fullKey}.${propKey}`)}> - {beautifyString(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}} -
- ); - } - - if (schema.anyOf) { - const types = schema.anyOf.map((s: any) => s.type); - if (types.includes('string') && types.includes('null')) { - return ( -
- {renderClickableInput(value, schema.placeholder || `Enter ${displayKey} (optional)`)} - {error && {error}} -
- ); - } - } - - if (schema.allOf) { - return ( -
- {displayKey}: - {schema.allOf[0].properties && Object.entries(schema.allOf[0].properties).map(([propKey, propSchema]: [string, any]) => ( -
- {renderInputField(propKey, propSchema, fullKey, propSchema.title || beautifyString(propKey))} -
- ))} -
- ); - } - - if (schema.oneOf) { - return ( -
- {displayKey}: - {schema.oneOf[0].properties && Object.entries(schema.oneOf[0].properties).map(([propKey, propSchema]: [string, any]) => ( -
- {renderInputField(propKey, propSchema, fullKey, propSchema.title || beautifyString(propKey))} -
- ))} -
- ); - } - - switch (schema.type) { - case 'string': - if (schema.enum) { - - return ( -
- - {error && {error}} -
- ) - } - - else if (schema.secret) { - return (
- {renderClickableInput(value, schema.placeholder || `Enter ${displayKey}`, true)} - {error && {error}} -
) - - } - else { - return ( -
- {renderClickableInput(value, schema.placeholder || `Enter ${displayKey}`)} - {error && {error}} -
- ); - } - case 'boolean': - return ( -
- - {error && {error}} -
- ); - case 'number': - case 'integer': - return ( -
- handleInputChange(fullKey, parseFloat(e.target.value))} - className="number-input" - /> - {error && {error}} -
- ); - case 'array': - if (schema.items && schema.items.type === 'string') { - const arrayValues = value || []; - return ( -
- {arrayValues.map((item: string, index: number) => ( -
- handleInputChange(`${fullKey}.${index}`, e.target.value)} - className="array-item-input" - /> - -
- ))} - - {error && {error}} -
- ); - } - return null; - default: - return ( -
- {renderClickableInput(value, schema.placeholder || `Enter ${beautifyString(displayKey)} (Complex)`)} - {error && {error}} -
- ); - } - }; - const validateInputs = () => { const newErrors: { [key: string]: string | null } = {}; const validateRecursive = (schema: any, parentKey: string = '') => { @@ -426,7 +167,15 @@ const CustomNode: FC> = ({ data, id }) => { return (isRequired || isAdvancedOpen) && (
- {renderInputField(key, schema, '', schema.title || beautifyString(key))} + {isHandleConnected(key) ? <> : + }
); })} diff --git a/rnd/autogpt_builder/src/components/NodeInputField.tsx b/rnd/autogpt_builder/src/components/NodeInputField.tsx new file mode 100644 index 000000000000..f2b81fda6aeb --- /dev/null +++ b/rnd/autogpt_builder/src/components/NodeInputField.tsx @@ -0,0 +1,295 @@ +import { beautifyString } from "@/lib/utils"; +import { FC, useState } from "react"; +import { Button } from "./ui/button"; +import { Input } from "./ui/input"; + +type BlockInputFieldProps = { + keyName: string + schema: any + parentKey?: string + value: string | Array | { [key: string]: string } + handleInputClick: (key: string) => void + handleInputChange: (key: string, value: any) => void + errors: { [key: string]: string | null } +} + +const NodeInputField: FC = + ({ keyName: key, schema, parentKey = '', value, handleInputClick, handleInputChange, errors }) => { + const [newKey, setNewKey] = useState(''); + const [newValue, setNewValue] = useState(''); + const [keyValuePairs, setKeyValuePairs] = useState<{ key: string, value: string }[]>([]); + + const fullKey = parentKey ? `${parentKey}.${key}` : key; + const error = errors[fullKey]; + const displayKey = schema.title || beautifyString(key); + + 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 renderClickableInput = (value: string | null = null, placeholder: string = "", secret: boolean = false) => { + + // if secret is true, then the input field will be a password field if the value is not null + return secret ? ( +
handleInputClick(fullKey)}> + {value ? ******** : {placeholder}} +
+ ) : ( +
handleInputClick(fullKey)}> + {value || {placeholder}} +
+ ) + }; + + if (schema.type === 'object' && schema.properties) { + return ( +
+ {displayKey}: + {Object.entries(schema.properties).map(([propKey, propSchema]: [string, any]) => ( +
+ +
+ ))} +
+ ); + } + + if (schema.type === 'object' && schema.additionalProperties) { + const objectValue = value || {}; + return ( +
+ {displayKey}: + {Object.entries(objectValue).map(([propKey, propValue]: [string, any]) => ( +
+
handleInputClick(`${fullKey}.${propKey}`)}> + {beautifyString(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}} +
+ ); + } + + if (schema.anyOf) { + const types = schema.anyOf.map((s: any) => s.type); + if (types.includes('string') && types.includes('null')) { + return ( +
+ {renderClickableInput(value as string, schema.placeholder || `Enter ${displayKey} (optional)`)} + {error && {error}} +
+ ); + } + } + + if (schema.allOf) { + return ( +
+ {displayKey}: + {schema.allOf[0].properties && Object.entries(schema.allOf[0].properties).map(([propKey, propSchema]: [string, any]) => ( +
+ +
+ ))} +
+ ); + } + + if (schema.oneOf) { + return ( +
+ {displayKey}: + {schema.oneOf[0].properties && Object.entries(schema.oneOf[0].properties).map(([propKey, propSchema]: [string, any]) => ( +
+ +
+ ))} +
+ ); + } + + switch (schema.type) { + case 'string': + if (schema.enum) { + + return ( +
+ + {error && {error}} +
+ ) + } + + else if (schema.secret) { + return (
+ {renderClickableInput(value as string, schema.placeholder || `Enter ${displayKey}`, true)} + {error && {error}} +
) + + } + else { + return ( +
+ {renderClickableInput(value as string, schema.placeholder || `Enter ${displayKey}`)} + {error && {error}} +
+ ); + } + case 'boolean': + return ( +
+ + {error && {error}} +
+ ); + case 'number': + case 'integer': + return ( +
+ handleInputChange(fullKey, parseFloat(e.target.value))} + className="number-input" + /> + {error && {error}} +
+ ); + case 'array': + if (schema.items && schema.items.type === 'string') { + const arrayValues = value as Array || []; + return ( +
+ {arrayValues.map((item: string, index: number) => ( +
+ handleInputChange(`${fullKey}.${index}`, e.target.value)} + className="array-item-input" + /> + +
+ ))} + + {error && {error}} +
+ ); + } + return null; + default: + return ( +
+ {renderClickableInput(value as string, schema.placeholder || `Enter ${beautifyString(displayKey)} (Complex)`)} + {error && {error}} +
+ ); + } + } + +export default NodeInputField;