mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-08 13:55:06 -05:00
wip breaking down custom node
This commit is contained in:
@@ -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 = {
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -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<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,
|
||||
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