Skip to content

Commit

Permalink
(rnd) Add support for multiple, dynamic inputs (#7296)
Browse files Browse the repository at this point in the history
multi dynamic inputs
  • Loading branch information
aarushik93 authored Jul 2, 2024
1 parent 97e4cce commit 8feaced
Show file tree
Hide file tree
Showing 4 changed files with 492 additions and 521 deletions.
233 changes: 204 additions & 29 deletions rnd/autogpt_builder/src/components/CustomNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,36 @@ type Schema = {
required?: string[];
};

const CustomNode: FC<NodeProps> = ({ data, id }) => {
type CustomNodeData = {
blockType: string;
title: string;
inputSchema: Schema;
outputSchema: Schema;
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;
};

const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
const [isPropertiesOpen, setIsPropertiesOpen] = useState(data.isPropertiesOpen || false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [activeKey, setActiveKey] = useState<string | null>(null);
const [modalValue, setModalValue] = useState<string>('');
const [errors, setErrors] = useState<{ [key: string]: string | null }>({});

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);
};
Expand Down Expand Up @@ -59,13 +77,49 @@ const CustomNode: FC<NodeProps> = ({ data, id }) => {

const handleInputChange = (key: string, value: any) => {
const newValues = { ...data.hardcodedValues, [key]: 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 isHandleConnected = (key: string) => {
return data.connections && data.connections.some((conn: string) => {
const [source, target] = conn.split(' -> ');
return target.includes(key) && target.includes(data.title);
return data.connections && data.connections.some((conn: any) => {
if (typeof conn === 'string') {
const [source, target] = conn.split(' -> ');
return target.includes(key) && target.includes(data.title);
}
return conn.target === id && conn.targetHandle === key;
});
};

Expand All @@ -83,7 +137,43 @@ const CustomNode: FC<NodeProps> = ({ data, id }) => {
setActiveKey(null);
};

const addArrayItem = (key: string) => {
const currentValues = data.hardcodedValues[key] || [];
handleInputChange(key, [...currentValues, '']);
};

const removeArrayItem = (key: string, index: number) => {
const currentValues = data.hardcodedValues[key] || [];
currentValues.splice(index, 1);
handleInputChange(key, [...currentValues]);
};

const handleArrayItemChange = (key: string, index: number, value: string) => {
const currentValues = data.hardcodedValues[key] || [];
currentValues[index] = value;
handleInputChange(key, [...currentValues]);
};

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, '');
};

const removeDynamicTextInput = (key: string) => {
const newValues = { ...data.hardcodedValues };
delete newValues[key];
data.setHardcodedValues(newValues);
};

const handleDynamicTextInputChange = (key: string, value: string) => {
handleInputChange(key, value);
};

const renderInputField = (key: string, schema: any) => {
const error = errors[key];
switch (schema.type) {
case 'string':
return schema.enum ? (
Expand All @@ -99,12 +189,14 @@ const CustomNode: FC<NodeProps> = ({ data, id }) => {
</option>
))}
</select>
{error && <span className="error-message">{error}</span>}
</div>
) : (
<div key={key} className="input-container">
<div className="clickable-input" onClick={() => handleInputClick(key)}>
{data.hardcodedValues[key] || `Enter ${key}`}
</div>
{error && <span className="error-message">{error}</span>}
</div>
);
case 'boolean':
Expand All @@ -128,9 +220,9 @@ const CustomNode: FC<NodeProps> = ({ data, id }) => {
/>
False
</label>
{error && <span className="error-message">{error}</span>}
</div>
);
case 'integer':
case 'number':
return (
<div key={key} className="input-container">
Expand All @@ -140,23 +232,31 @@ const CustomNode: FC<NodeProps> = ({ data, id }) => {
onChange={(e) => handleInputChange(key, parseFloat(e.target.value))}
className="number-input"
/>
{error && <span className="error-message">{error}</span>}
</div>
);
case 'array':
if (schema.items && schema.items.type === 'string' && schema.items.enum) {
if (schema.items && schema.items.type === 'string') {
const arrayValues = data.hardcodedValues[key] || [];
return (
<div key={key} className="input-container">
<select
value={data.hardcodedValues[key] || ''}
onChange={(e) => handleInputChange(key, e.target.value)}
className="select-input"
>
{schema.items.enum.map((option: string) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
{arrayValues.map((item: string, index: number) => (
<div key={`${key}-${index}`} className="array-item-container">
<input
type="text"
value={item}
onChange={(e) => handleArrayItemChange(key, index, e.target.value)}
className="array-item-input"
/>
<button onClick={() => removeArrayItem(key, index)} className="array-item-remove">
&times;
</button>
</div>
))}
<button onClick={() => addArrayItem(key)} className="array-item-add">
Add Item
</button>
{error && <span className="error-message">{error}</span>}
</div>
);
}
Expand All @@ -166,11 +266,65 @@ const CustomNode: FC<NodeProps> = ({ data, id }) => {
}
};

const renderDynamicTextFields = () => {
const dynamicKeyPrefix = 'texts_$_';
const dynamicKeys = Object.keys(data.hardcodedValues).filter(key => key.startsWith(dynamicKeyPrefix));

return dynamicKeys.map((key, index) => (
<div key={key} className="input-container">
<div className="handle-container">
<Handle
type="target"
position={Position.Left}
id={key}
style={{ background: '#555', borderRadius: '50%' }}
/>
<span className="handle-label">{key}</span>
{!isHandleConnected(key) && (
<>
<input
type="text"
value={data.hardcodedValues[key]}
onChange={(e) => handleDynamicTextInputChange(key, e.target.value)}
className="dynamic-text-input"
/>
<button onClick={() => removeDynamicTextInput(key)} className="array-item-remove">
&times;
</button>
</>
)}
</div>
</div>
));
};

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;
}
});
setErrors(newErrors);
return Object.values(newErrors).every((error) => error === null);
};

const handleSubmit = () => {
if (validateInputs()) {
console.log("Valid data:", data.hardcodedValues);
} else {
console.log("Invalid data:", errors);
}
};


return (
<div className="custom-node">
<div className="node-header">
<div className="node-title">{data?.title.replace(/\d+/g, '')}</div>
<div className="node-title">{data.blockType || data.title}</div>
<button onClick={toggleProperties} className="toggle-button">
&#9776;
</button>
Expand All @@ -180,16 +334,36 @@ const CustomNode: FC<NodeProps> = ({ data, id }) => {
{data.inputSchema &&
Object.keys(data.inputSchema.properties).map((key) => (
<div key={key}>
<div className="handle-container">
<Handle
type="target"
position={Position.Left}
id={key}
style={{ background: '#555', borderRadius: '50%' }}
/>
<span className="handle-label">{key}</span>
</div>
{!isHandleConnected(key) && renderInputField(key, data.inputSchema.properties[key])}
{key !== 'texts' ? (
<div>
<div className="handle-container">
<Handle
type="target"
position={Position.Left}
id={key}
style={{ background: '#555', borderRadius: '50%' }}
/>
<span className="handle-label">{key}</span>
</div>
{!isHandleConnected(key) && renderInputField(key, data.inputSchema.properties[key])}
</div>
) : (
<div key={key} className="input-container">
<div className="handle-container">
<Handle
type="target"
position={Position.Left}
id={key}
style={{ background: '#555', borderRadius: '50%' }}
/>
<span className="handle-label">{key}</span>
</div>
{renderDynamicTextFields()}
<button onClick={addDynamicTextInput} className="array-item-add">
Add Text Input
</button>
</div>
)}
</div>
))}
</div>
Expand All @@ -212,6 +386,7 @@ const CustomNode: FC<NodeProps> = ({ data, id }) => {
</p>
</div>
)}
<button onClick={handleSubmit}>Submit</button>
<ModalComponent
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
Expand All @@ -222,4 +397,4 @@ const CustomNode: FC<NodeProps> = ({ data, id }) => {
);
};

export default memo(CustomNode);
export default memo(CustomNode);
Loading

0 comments on commit 8feaced

Please sign in to comment.