Compare commits

...

3 Commits

Author SHA1 Message Date
Aarushi
2e81caa812 fix imports 2024-07-18 13:09:31 +01:00
Aarushi
6f3b6ffa2d add node footer, header and properties 2024-07-18 12:58:22 +01:00
Aarushi
f0a061ab8a wip breaking down custom node 2024-07-18 12:45:07 +01:00
11 changed files with 377 additions and 467 deletions

View File

@@ -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">
&times;
</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">
&times;
</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);

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

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

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

View File

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

View File

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

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