mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-08 13:55:06 -05:00
Compare commits
3 Commits
fix/execut
...
aarushikan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e81caa812 | ||
|
|
6f3b6ffa2d | ||
|
|
f0a061ab8a |
@@ -1,466 +0,0 @@
|
||||
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 { BlockSchema } from '@/lib/types';
|
||||
import SchemaTooltip from './SchemaTooltip';
|
||||
import { beautifyString } from '@/lib/utils';
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
const [isPropertiesOpen, setIsPropertiesOpen] = useState(data.isPropertiesOpen || false);
|
||||
const [isAdvancedOpen, setIsAdvancedOpen] = useState(false);
|
||||
const [keyValuePairs, setKeyValuePairs] = useState<{ key: string, value: string }[]>([]);
|
||||
const [newKey, setNewKey] = useState<string>('');
|
||||
const [newValue, setNewValue] = useState<string>('');
|
||||
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);
|
||||
};
|
||||
|
||||
const toggleAdvancedSettings = () => {
|
||||
setIsAdvancedOpen(!isAdvancedOpen);
|
||||
};
|
||||
|
||||
const hasOptionalFields = () => {
|
||||
return data.inputSchema && Object.keys(data.inputSchema.properties).some((key) => {
|
||||
return !(data.inputSchema.required?.includes(key));
|
||||
});
|
||||
};
|
||||
|
||||
const generateHandles = (schema: BlockSchema, type: 'source' | 'target') => {
|
||||
if (!schema?.properties) return null;
|
||||
const keys = Object.keys(schema.properties);
|
||||
return keys.map((key) => (
|
||||
<div key={key} className="handle-container">
|
||||
{type === 'target' && (
|
||||
<>
|
||||
<Handle
|
||||
type={type}
|
||||
position={Position.Left}
|
||||
id={key}
|
||||
style={{ background: '#555', borderRadius: '50%', width: '10px', height: '10px' }}
|
||||
/>
|
||||
<span className="handle-label">{schema.properties[key].title || beautifyString(key)}</span>
|
||||
</>
|
||||
)}
|
||||
{type === 'source' && (
|
||||
<>
|
||||
<span className="handle-label">{schema.properties[key].title || beautifyString(key)}</span>
|
||||
<Handle
|
||||
type={type}
|
||||
position={Position.Right}
|
||||
id={key}
|
||||
style={{ background: '#555', borderRadius: '50%', width: '10px', height: '10px' }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
};
|
||||
|
||||
const handleInputChange = (key: string, value: any) => {
|
||||
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 getValue = (key: string) => {
|
||||
const keys = key.split('.');
|
||||
return keys.reduce((acc, k) => (acc && acc[k] !== undefined) ? acc[k] : '', data.hardcodedValues);
|
||||
};
|
||||
|
||||
const isHandleConnected = (key: string) => {
|
||||
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;
|
||||
});
|
||||
};
|
||||
|
||||
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);
|
||||
const value = getValue(key);
|
||||
setModalValue(typeof value === 'object' ? JSON.stringify(value, null, 2) : value);
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const handleModalSave = (value: string) => {
|
||||
if (activeKey) {
|
||||
try {
|
||||
const parsedValue = JSON.parse(value);
|
||||
handleInputChange(activeKey, parsedValue);
|
||||
} catch (error) {
|
||||
handleInputChange(activeKey, value);
|
||||
}
|
||||
}
|
||||
setIsModalOpen(false);
|
||||
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 <div className="connected-input">Connected</div>;
|
||||
}
|
||||
|
||||
const renderClickableInput = (value: string | null = null, placeholder: string = "") => (
|
||||
<div className="clickable-input" onClick={() => handleInputClick(fullKey)}>
|
||||
{value || <i className="text-gray-500">{placeholder}</i>}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (schema.type === 'object' && schema.properties) {
|
||||
return (
|
||||
<div key={fullKey} className="object-input">
|
||||
<strong>{displayKey}:</strong>
|
||||
{Object.entries(schema.properties).map(([propKey, propSchema]: [string, any]) => (
|
||||
<div key={`${fullKey}.${propKey}`} className="nested-input">
|
||||
{renderInputField(propKey, propSchema, fullKey, propSchema.title || beautifyString(propKey))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.type === 'object' && schema.additionalProperties) {
|
||||
const objectValue = value || {};
|
||||
return (
|
||||
<div key={fullKey} className="object-input">
|
||||
<strong>{displayKey}:</strong>
|
||||
{Object.entries(objectValue).map(([propKey, propValue]: [string, any]) => (
|
||||
<div key={`${fullKey}.${propKey}`} className="nested-input">
|
||||
<div className="clickable-input" onClick={() => handleInputClick(`${fullKey}.${propKey}`)}>
|
||||
{beautifyString(propKey)}: {typeof propValue === 'object' ? JSON.stringify(propValue, null, 2) : propValue}
|
||||
</div>
|
||||
<Button onClick={() => handleInputChange(`${fullKey}.${propKey}`, undefined)} className="array-item-remove">
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
{key === 'expected_format' && (
|
||||
<div className="nested-input">
|
||||
{keyValuePairs.map((pair, index) => (
|
||||
<div key={index} className="key-value-input">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Key"
|
||||
value={beautifyString(pair.key)}
|
||||
onChange={(e) => {
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Value"
|
||||
value={beautifyString(pair.value)}
|
||||
onChange={(e) => {
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className="key-value-input">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Key"
|
||||
value={newKey}
|
||||
onChange={(e) => setNewKey(e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Value"
|
||||
value={newValue}
|
||||
onChange={(e) => setNewValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={handleAddProperty}>Add Property</Button>
|
||||
</div>
|
||||
)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.anyOf) {
|
||||
const types = schema.anyOf.map((s: any) => s.type);
|
||||
if (types.includes('string') && types.includes('null')) {
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{renderClickableInput(value, schema.placeholder || `Enter ${displayKey} (optional)`)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.allOf) {
|
||||
return (
|
||||
<div key={fullKey} className="object-input">
|
||||
<strong>{displayKey}:</strong>
|
||||
{schema.allOf[0].properties && Object.entries(schema.allOf[0].properties).map(([propKey, propSchema]: [string, any]) => (
|
||||
<div key={`${fullKey}.${propKey}`} className="nested-input">
|
||||
{renderInputField(propKey, propSchema, fullKey, propSchema.title || beautifyString(propKey))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.oneOf) {
|
||||
return (
|
||||
<div key={fullKey} className="object-input">
|
||||
<strong>{displayKey}:</strong>
|
||||
{schema.oneOf[0].properties && Object.entries(schema.oneOf[0].properties).map(([propKey, propSchema]: [string, any]) => (
|
||||
<div key={`${fullKey}.${propKey}`} className="nested-input">
|
||||
{renderInputField(propKey, propSchema, fullKey, propSchema.title || beautifyString(propKey))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
switch (schema.type) {
|
||||
case 'string':
|
||||
return schema.enum ? (
|
||||
<div key={fullKey} className="input-container">
|
||||
<select
|
||||
value={value || ''}
|
||||
onChange={(e) => handleInputChange(fullKey, e.target.value)}
|
||||
className="select-input"
|
||||
>
|
||||
<option value="">Select {displayKey}</option>
|
||||
{schema.enum.map((option: string) => (
|
||||
<option key={option} value={option}>
|
||||
{beautifyString(option)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
) : (
|
||||
<div key={fullKey} className="input-container">
|
||||
{renderClickableInput(value, schema.placeholder || `Enter ${displayKey}`)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
case 'boolean':
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
<select
|
||||
value={value === undefined ? '' : value.toString()}
|
||||
onChange={(e) => handleInputChange(fullKey, e.target.value === 'true')}
|
||||
className="select-input"
|
||||
>
|
||||
<option value="">Select {displayKey}</option>
|
||||
<option value="true">True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
case 'number':
|
||||
case 'integer':
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
<input
|
||||
type="number"
|
||||
value={value || ''}
|
||||
onChange={(e) => handleInputChange(fullKey, parseFloat(e.target.value))}
|
||||
className="number-input"
|
||||
/>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
case 'array':
|
||||
if (schema.items && schema.items.type === 'string') {
|
||||
const arrayValues = value || [];
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{arrayValues.map((item: string, index: number) => (
|
||||
<div key={`${fullKey}.${index}`} className="array-item-container">
|
||||
<input
|
||||
type="text"
|
||||
value={item}
|
||||
onChange={(e) => handleInputChange(`${fullKey}.${index}`, e.target.value)}
|
||||
className="array-item-input"
|
||||
/>
|
||||
<Button onClick={() => handleInputChange(`${fullKey}.${index}`, '')} className="array-item-remove">
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button onClick={() => handleInputChange(fullKey, [...arrayValues, ''])} className="array-item-add">
|
||||
Add Item
|
||||
</Button>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{renderClickableInput(value, schema.placeholder || `Enter ${beautifyString(displayKey)} (Complex)`)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const validateInputs = () => {
|
||||
const newErrors: { [key: string]: string | null } = {};
|
||||
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);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`custom-node dark-theme ${data.status === 'RUNNING' ? 'running' : data.status === 'COMPLETED' ? 'completed' : data.status === 'FAILED' ? 'failed' : ''}`}>
|
||||
<div className="node-header">
|
||||
<div className="node-title">{beautifyString(data.blockType?.replace(/Block$/, '') || data.title)}</div>
|
||||
</div>
|
||||
<div className="node-content">
|
||||
<div className="input-section">
|
||||
{data.inputSchema &&
|
||||
Object.entries(data.inputSchema.properties).map(([key, schema]) => {
|
||||
const isRequired = data.inputSchema.required?.includes(key);
|
||||
return (isRequired || isAdvancedOpen) && (
|
||||
<div key={key}>
|
||||
<div className="handle-container">
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
id={key}
|
||||
style={{ background: '#555', borderRadius: '50%', width: '10px', height: '10px' }}
|
||||
/>
|
||||
<span className="handle-label">{schema.title || beautifyString(key)}</span>
|
||||
<SchemaTooltip schema={schema} />
|
||||
</div>
|
||||
{renderInputField(key, schema, '', schema.title || beautifyString(key))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="output-section">
|
||||
{data.outputSchema && generateHandles(data.outputSchema, 'source')}
|
||||
</div>
|
||||
</div>
|
||||
{isPropertiesOpen && (
|
||||
<div className="node-properties">
|
||||
<h4>Node Output</h4>
|
||||
<p>
|
||||
<strong>Status:</strong>{' '}
|
||||
{typeof data.status === 'object' ? JSON.stringify(data.status) : data.status || 'N/A'}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Output Data:</strong>{' '}
|
||||
{typeof data.output_data === 'object'
|
||||
? JSON.stringify(data.output_data)
|
||||
: data.output_data || 'N/A'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="node-footer">
|
||||
<Button onClick={toggleProperties} className="toggle-button">
|
||||
Toggle Properties
|
||||
</Button>
|
||||
{hasOptionalFields() && (
|
||||
<Button onClick={toggleAdvancedSettings} className="toggle-button">
|
||||
Toggle Advanced
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<ModalComponent
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
onSave={handleModalSave}
|
||||
value={modalValue}
|
||||
key={activeKey}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(CustomNode);
|
||||
50
rnd/autogpt_builder/src/components/CustomNode/CustomNode.tsx
Normal file
50
rnd/autogpt_builder/src/components/CustomNode/CustomNode.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { useState, useEffect, FC, memo } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { CustomNodeData } from './types';
|
||||
import { NodeHeader } from './NodeHeader';
|
||||
import { NodeContent } from './NodeContent';
|
||||
import { NodeProperties } from './NodeProperties';
|
||||
import { NodeFooter } from './NodeFooter';
|
||||
import { useCustomNode } from './hooks/useCustomNode';
|
||||
import ModalComponent from '../ModalComponent';
|
||||
import { hasOptionalFields } from "@/components/CustomNode/utils";
|
||||
|
||||
const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
const {
|
||||
isPropertiesOpen,
|
||||
isAdvancedOpen,
|
||||
isModalOpen,
|
||||
modalValue,
|
||||
activeKey,
|
||||
toggleProperties,
|
||||
toggleAdvancedSettings,
|
||||
handleInputClick,
|
||||
handleModalSave,
|
||||
setIsModalOpen,
|
||||
} = useCustomNode(data, id);
|
||||
|
||||
return (
|
||||
<div className={`custom-node dark-theme ${data.status === 'RUNNING' ? 'running' : data.status === 'COMPLETED' ? 'completed' : data.status === 'FAILED' ? 'failed' : ''}`}>
|
||||
<NodeHeader title={data.blockType || data.title} />
|
||||
<NodeContent
|
||||
data={data}
|
||||
isAdvancedOpen={isAdvancedOpen}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
{isPropertiesOpen && <NodeProperties status={data.status} outputData={data.output_data} />}
|
||||
<NodeFooter
|
||||
toggleProperties={toggleProperties}
|
||||
toggleAdvancedSettings={toggleAdvancedSettings}
|
||||
hasOptionalFields={hasOptionalFields(data.inputSchema)}
|
||||
/>
|
||||
<ModalComponent
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
onSave={handleModalSave}
|
||||
value={modalValue}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(CustomNode);
|
||||
106
rnd/autogpt_builder/src/components/CustomNode/InputField.tsx
Normal file
106
rnd/autogpt_builder/src/components/CustomNode/InputField.tsx
Normal file
@@ -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<InputFieldProps> = ({
|
||||
schema,
|
||||
fullKey,
|
||||
displayKey,
|
||||
value,
|
||||
error,
|
||||
handleInputChange,
|
||||
handleInputClick,
|
||||
}) => {
|
||||
const renderClickableInput = (value: string | null = null, placeholder: string = "") => (
|
||||
<div className="clickable-input" onClick={() => handleInputClick(fullKey)}>
|
||||
{value || <i className="text-gray-500">{placeholder}</i>}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (schema.type === 'object' && schema.properties) {
|
||||
return (
|
||||
<div className="object-input">
|
||||
<strong>{displayKey}:</strong>
|
||||
{Object.entries(schema.properties).map(([propKey, propSchema]: [string, any]) => (
|
||||
<div key={`${fullKey}.${propKey}`} className="nested-input">
|
||||
<InputField
|
||||
schema={propSchema}
|
||||
fullKey={`${fullKey}.${propKey}`}
|
||||
displayKey={propSchema.title || beautifyString(propKey)}
|
||||
value={value && value[propKey]}
|
||||
error={error}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.type === 'string') {
|
||||
return schema.enum ? (
|
||||
<div className="input-container">
|
||||
<select
|
||||
value={value || ''}
|
||||
onChange={(e) => handleInputChange(fullKey, e.target.value)}
|
||||
className="select-input"
|
||||
>
|
||||
<option value="">Select {displayKey}</option>
|
||||
{schema.enum.map((option: string) => (
|
||||
<option key={option} value={option}>
|
||||
{beautifyString(option)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
) : (
|
||||
<div className="input-container">
|
||||
{renderClickableInput(value, schema.placeholder || `Enter ${displayKey}`)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.type === 'boolean') {
|
||||
return (
|
||||
<div className="input-container">
|
||||
<select
|
||||
value={value === undefined ? '' : value.toString()}
|
||||
onChange={(e) => handleInputChange(fullKey, e.target.value === 'true')}
|
||||
className="select-input"
|
||||
>
|
||||
<option value="">Select {displayKey}</option>
|
||||
<option value="true">True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.type === 'number' || schema.type === 'integer') {
|
||||
return (
|
||||
<div className="input-container">
|
||||
<Input
|
||||
type="number"
|
||||
value={value || ''}
|
||||
onChange={(e) => handleInputChange(fullKey, parseFloat(e.target.value))}
|
||||
className="number-input"
|
||||
/>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Default case for complex or unhandled types
|
||||
return (
|
||||
<div className="input-container">
|
||||
{renderClickableInput(value, schema.placeholder || `Enter ${beautifyString(displayKey)} (Complex)`)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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<NodeContentProps> = ({ data, isAdvancedOpen, handleInputClick }) => {
|
||||
return (
|
||||
<div className="node-content">
|
||||
<div className="input-section">
|
||||
{data.inputSchema &&
|
||||
Object.entries(data.inputSchema.properties).map(([key, schema]) => {
|
||||
const isRequired = data.inputSchema.required?.includes(key);
|
||||
return (isRequired || isAdvancedOpen) && (
|
||||
<div key={key}>
|
||||
<div className="handle-container">
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
id={key}
|
||||
style={{ background: '#555', borderRadius: '50%', width: '10px', height: '10px' }}
|
||||
/>
|
||||
<span className="handle-label">{schema.title || beautifyString(key)}</span>
|
||||
<SchemaTooltip schema={schema} />
|
||||
</div>
|
||||
<InputField
|
||||
schema={schema}
|
||||
fullKey={key}
|
||||
displayKey={schema.title || beautifyString(key)}
|
||||
value={data.hardcodedValues[key]}
|
||||
error={null}
|
||||
handleInputChange={(key, value) => data.setHardcodedValues({ ...data.hardcodedValues, [key]: value })}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="output-section">
|
||||
{data.outputSchema && generateHandles(data.outputSchema, 'source')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
27
rnd/autogpt_builder/src/components/CustomNode/NodeFooter.tsx
Normal file
27
rnd/autogpt_builder/src/components/CustomNode/NodeFooter.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { Button } from '../ui/button';
|
||||
|
||||
interface NodeFooterProps {
|
||||
toggleProperties: () => void;
|
||||
toggleAdvancedSettings: () => void;
|
||||
hasOptionalFields: boolean;
|
||||
}
|
||||
|
||||
export const NodeFooter: React.FC<NodeFooterProps> = ({
|
||||
toggleProperties,
|
||||
toggleAdvancedSettings,
|
||||
hasOptionalFields,
|
||||
}) => {
|
||||
return (
|
||||
<div className="node-footer">
|
||||
<Button onClick={toggleProperties} className="toggle-button">
|
||||
Toggle Properties
|
||||
</Button>
|
||||
{hasOptionalFields && (
|
||||
<Button onClick={toggleAdvancedSettings} className="toggle-button">
|
||||
Toggle Advanced
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
14
rnd/autogpt_builder/src/components/CustomNode/NodeHeader.tsx
Normal file
14
rnd/autogpt_builder/src/components/CustomNode/NodeHeader.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { beautifyString } from '@/lib/utils';
|
||||
|
||||
interface NodeHeaderProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const NodeHeader: React.FC<NodeHeaderProps> = ({ title }) => {
|
||||
return (
|
||||
<div className="node-header">
|
||||
<div className="node-title">{beautifyString(title.replace(/Block$/, ''))}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
interface NodePropertiesProps {
|
||||
status: string | undefined;
|
||||
outputData: any;
|
||||
}
|
||||
|
||||
export const NodeProperties: React.FC<NodePropertiesProps> = ({ status, outputData }) => {
|
||||
return (
|
||||
<div className="node-properties">
|
||||
<h4>Node Output</h4>
|
||||
<p>
|
||||
<strong>Status:</strong>{' '}
|
||||
{typeof status === 'object' ? JSON.stringify(status) : status || 'N/A'}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Output Data:</strong>{' '}
|
||||
{typeof outputData === 'object'
|
||||
? JSON.stringify(outputData)
|
||||
: outputData || 'N/A'}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { CustomNodeData } from "@/components/CustomNode/types";
|
||||
import {getValue} from "@/components/CustomNode/utils";
|
||||
|
||||
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<string | null>(null);
|
||||
const [modalValue, setModalValue] = useState<string>('');
|
||||
|
||||
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,
|
||||
setIsModalOpen,
|
||||
modalValue,
|
||||
activeKey,
|
||||
toggleProperties,
|
||||
toggleAdvancedSettings,
|
||||
handleInputClick,
|
||||
handleModalSave,
|
||||
};
|
||||
};
|
||||
24
rnd/autogpt_builder/src/components/CustomNode/types/index.ts
Normal file
24
rnd/autogpt_builder/src/components/CustomNode/types/index.ts
Normal file
@@ -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;
|
||||
};
|
||||
22
rnd/autogpt_builder/src/components/CustomNode/utils/index.ts
Normal file
22
rnd/autogpt_builder/src/components/CustomNode/utils/index.ts
Normal file
@@ -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;
|
||||
});
|
||||
};
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user