wip breaking down custom node

This commit is contained in:
Aarushi
2024-07-18 12:45:07 +01:00
parent 354e626965
commit f0a061ab8a
10 changed files with 265 additions and 6 deletions

View File

@@ -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 = {

View 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>
);
};

View File

@@ -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>
);
};

View File

@@ -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,
};
};

View 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;
};

View 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;
});
};

View File

@@ -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';