mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-12 15:55:03 -05:00
Compare commits
26 Commits
fix/claude
...
rushi/add-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9656a80f0 | ||
|
|
2e78da24c3 | ||
|
|
02e3662726 | ||
|
|
a241f81aed | ||
|
|
f2c06e017e | ||
|
|
db14c39449 | ||
|
|
7a7bc9d716 | ||
|
|
883fa052e0 | ||
|
|
21b893aa06 | ||
|
|
54490b7881 | ||
|
|
84e8cf6acc | ||
|
|
fa57a8f544 | ||
|
|
4db5b5e012 | ||
|
|
c1fc01036a | ||
|
|
f99c1c8c57 | ||
|
|
4490e4dd9f | ||
|
|
430deb7d1c | ||
|
|
2bf62a1e4b | ||
|
|
5c37ac8f74 | ||
|
|
b80d55471f | ||
|
|
cda1720730 | ||
|
|
0ea1878faf | ||
|
|
b6aa387af5 | ||
|
|
67876913d3 | ||
|
|
d58c446471 | ||
|
|
95ac720620 |
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, FC, memo } from 'react';
|
import React, { useState, useEffect, FC, memo, useCallback } from 'react';
|
||||||
import { Handle, Position, NodeProps } from 'reactflow';
|
import { Handle, Position, NodeProps } from 'reactflow';
|
||||||
import 'reactflow/dist/style.css';
|
import 'reactflow/dist/style.css';
|
||||||
import './customnode.css';
|
import './customnode.css';
|
||||||
@@ -8,6 +8,12 @@ type Schema = {
|
|||||||
type: string;
|
type: string;
|
||||||
properties: { [key: string]: any };
|
properties: { [key: string]: any };
|
||||||
required?: string[];
|
required?: string[];
|
||||||
|
enum?: string[];
|
||||||
|
items?: Schema;
|
||||||
|
additionalProperties?: { type: string };
|
||||||
|
allOf?: any[];
|
||||||
|
anyOf?: any[];
|
||||||
|
oneOf?: any[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type CustomNodeData = {
|
type CustomNodeData = {
|
||||||
@@ -25,6 +31,9 @@ type CustomNodeData = {
|
|||||||
|
|
||||||
const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||||
const [isPropertiesOpen, setIsPropertiesOpen] = useState(data.isPropertiesOpen || false);
|
const [isPropertiesOpen, setIsPropertiesOpen] = useState(data.isPropertiesOpen || 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 [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [activeKey, setActiveKey] = useState<string | null>(null);
|
const [activeKey, setActiveKey] = useState<string | null>(null);
|
||||||
const [modalValue, setModalValue] = useState<string>('');
|
const [modalValue, setModalValue] = useState<string>('');
|
||||||
@@ -76,41 +85,24 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleInputChange = (key: string, value: any) => {
|
const handleInputChange = (key: string, value: any) => {
|
||||||
const newValues = { ...data.hardcodedValues, [key]: value };
|
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);
|
console.log(`Updating hardcoded values for node ${id}:`, newValues);
|
||||||
data.setHardcodedValues(newValues);
|
data.setHardcodedValues(newValues);
|
||||||
setErrors((prevErrors) => ({ ...prevErrors, [key]: null }));
|
setErrors((prevErrors) => ({ ...prevErrors, [key]: null }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateInput = (key: string, value: any, schema: any) => {
|
const getValue = (key: string) => {
|
||||||
switch (schema.type) {
|
const keys = key.split('.');
|
||||||
case 'string':
|
return keys.reduce((acc, k) => (acc && acc[k] !== undefined) ? acc[k] : '', data.hardcodedValues);
|
||||||
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) => {
|
const isHandleConnected = (key: string) => {
|
||||||
@@ -123,66 +115,185 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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) => {
|
const handleInputClick = (key: string) => {
|
||||||
setActiveKey(key);
|
setActiveKey(key);
|
||||||
setModalValue(data.hardcodedValues[key] || '');
|
const value = getValue(key);
|
||||||
|
setModalValue(typeof value === 'object' ? JSON.stringify(value, null, 2) : value);
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModalSave = (value: string) => {
|
const handleModalSave = (value: string) => {
|
||||||
if (activeKey) {
|
if (activeKey) {
|
||||||
handleInputChange(activeKey, value);
|
try {
|
||||||
|
const parsedValue = JSON.parse(value);
|
||||||
|
handleInputChange(activeKey, parsedValue);
|
||||||
|
} catch (error) {
|
||||||
|
handleInputChange(activeKey, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
setActiveKey(null);
|
setActiveKey(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addArrayItem = (key: string) => {
|
const autoResize = useCallback((element) => {
|
||||||
const currentValues = data.hardcodedValues[key] || [];
|
element.style.height = 'auto';
|
||||||
handleInputChange(key, [...currentValues, '']);
|
element.style.height = element.scrollHeight + 'px';
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const removeArrayItem = (key: string, index: number) => {
|
const renderInputField = (key: string, schema: any, parentKey: string = ''): JSX.Element => {
|
||||||
const currentValues = data.hardcodedValues[key] || [];
|
const fullKey = parentKey ? `${parentKey}.${key}` : key;
|
||||||
currentValues.splice(index, 1);
|
const error = errors[fullKey];
|
||||||
handleInputChange(key, [...currentValues]);
|
const value = getValue(fullKey);
|
||||||
};
|
|
||||||
|
|
||||||
const handleArrayItemChange = (key: string, index: number, value: string) => {
|
if (isHandleConnected(fullKey)) {
|
||||||
const currentValues = data.hardcodedValues[key] || [];
|
return <div className="connected-input">Connected</div>;
|
||||||
currentValues[index] = value;
|
}
|
||||||
handleInputChange(key, [...currentValues]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const addDynamicTextInput = () => {
|
const renderClickableInput = (displayValue: string) => (
|
||||||
const dynamicKeyPrefix = 'texts_$_';
|
<div className="clickable-input" onClick={() => handleInputClick(fullKey)}>
|
||||||
const currentKeys = Object.keys(data.hardcodedValues).filter(key => key.startsWith(dynamicKeyPrefix));
|
{displayValue}
|
||||||
const nextIndex = currentKeys.length + 1;
|
</div>
|
||||||
const newKey = `${dynamicKeyPrefix}${nextIndex}`;
|
);
|
||||||
handleInputChange(newKey, '');
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeDynamicTextInput = (key: string) => {
|
if (schema.type === 'object' && schema.properties) {
|
||||||
const newValues = { ...data.hardcodedValues };
|
return (
|
||||||
delete newValues[key];
|
<div key={fullKey} className="object-input">
|
||||||
data.setHardcodedValues(newValues);
|
<strong>{key}:</strong>
|
||||||
};
|
{Object.entries(schema.properties).map(([propKey, propSchema]: [string, any]) => (
|
||||||
|
<div key={`${fullKey}.${propKey}`} className="nested-input">
|
||||||
|
{renderInputField(propKey, propSchema, fullKey)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const handleDynamicTextInputChange = (key: string, value: string) => {
|
if (schema.type === 'object' && schema.additionalProperties) {
|
||||||
handleInputChange(key, value);
|
const objectValue = value || {};
|
||||||
};
|
return (
|
||||||
|
<div key={fullKey} className="object-input">
|
||||||
|
<strong>{key}:</strong>
|
||||||
|
{Object.entries(objectValue).map(([propKey, propValue]: [string, any]) => (
|
||||||
|
<div key={`${fullKey}.${propKey}`} className="nested-input">
|
||||||
|
<div className="clickable-input" onClick={() => handleInputClick(`${fullKey}.${propKey}`)}>
|
||||||
|
{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={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={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 || `Enter ${key} (optional)`)}
|
||||||
|
{error && <span className="error-message">{error}</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.allOf) {
|
||||||
|
return (
|
||||||
|
<div key={fullKey} className="object-input">
|
||||||
|
<strong>{key}:</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)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.oneOf) {
|
||||||
|
return (
|
||||||
|
<div key={fullKey} className="object-input">
|
||||||
|
<strong>{key}:</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)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const renderInputField = (key: string, schema: any) => {
|
|
||||||
const error = errors[key];
|
|
||||||
switch (schema.type) {
|
switch (schema.type) {
|
||||||
case 'string':
|
case 'string':
|
||||||
return schema.enum ? (
|
return schema.enum ? (
|
||||||
<div key={key} className="input-container">
|
<div key={fullKey} className="input-container">
|
||||||
<select
|
<select
|
||||||
value={data.hardcodedValues[key] || ''}
|
value={value || ''}
|
||||||
onChange={(e) => handleInputChange(key, e.target.value)}
|
onChange={(e) => handleInputChange(fullKey, e.target.value)}
|
||||||
className="select-input"
|
className="select-input"
|
||||||
>
|
>
|
||||||
|
<option value="">Select {key}</option>
|
||||||
{schema.enum.map((option: string) => (
|
{schema.enum.map((option: string) => (
|
||||||
<option key={option} value={option}>
|
<option key={option} value={option}>
|
||||||
{option}
|
{option}
|
||||||
@@ -192,44 +303,44 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
|||||||
{error && <span className="error-message">{error}</span>}
|
{error && <span className="error-message">{error}</span>}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div key={key} className="input-container">
|
<div key={fullKey} className="input-container">
|
||||||
<div className="clickable-input" onClick={() => handleInputClick(key)}>
|
<textarea
|
||||||
{data.hardcodedValues[key] || `Enter ${key}`}
|
value={value}
|
||||||
</div>
|
onChange={(e) => {
|
||||||
|
handleInputChange(fullKey, e.target.value);
|
||||||
|
autoResize(e.target);
|
||||||
|
}}
|
||||||
|
className="text-input"
|
||||||
|
onFocus={(e) => autoResize(e.target)}
|
||||||
|
onInput={(e) => autoResize(e.target)}
|
||||||
|
placeholder={`Enter ${key}`}
|
||||||
|
/>
|
||||||
{error && <span className="error-message">{error}</span>}
|
{error && <span className="error-message">{error}</span>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
return (
|
return (
|
||||||
<div key={key} className="input-container">
|
<div key={fullKey} className="input-container">
|
||||||
<label className="radio-label">
|
<select
|
||||||
<input
|
value={value === undefined ? '' : value.toString()}
|
||||||
type="radio"
|
onChange={(e) => handleInputChange(fullKey, e.target.value === 'true')}
|
||||||
value="true"
|
className="select-input"
|
||||||
checked={data.hardcodedValues[key] === true}
|
>
|
||||||
onChange={() => handleInputChange(key, true)}
|
<option value="">Select {key}</option>
|
||||||
/>
|
<option value="true">True</option>
|
||||||
True
|
<option value="false">False</option>
|
||||||
</label>
|
</select>
|
||||||
<label className="radio-label">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value="false"
|
|
||||||
checked={data.hardcodedValues[key] === false}
|
|
||||||
onChange={() => handleInputChange(key, false)}
|
|
||||||
/>
|
|
||||||
False
|
|
||||||
</label>
|
|
||||||
{error && <span className="error-message">{error}</span>}
|
{error && <span className="error-message">{error}</span>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case 'number':
|
case 'number':
|
||||||
|
case 'integer':
|
||||||
return (
|
return (
|
||||||
<div key={key} className="input-container">
|
<div key={fullKey} className="input-container">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={data.hardcodedValues[key] || ''}
|
value={value || ''}
|
||||||
onChange={(e) => handleInputChange(key, parseFloat(e.target.value))}
|
onChange={(e) => handleInputChange(fullKey, parseFloat(e.target.value))}
|
||||||
className="number-input"
|
className="number-input"
|
||||||
/>
|
/>
|
||||||
{error && <span className="error-message">{error}</span>}
|
{error && <span className="error-message">{error}</span>}
|
||||||
@@ -237,23 +348,23 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
|||||||
);
|
);
|
||||||
case 'array':
|
case 'array':
|
||||||
if (schema.items && schema.items.type === 'string') {
|
if (schema.items && schema.items.type === 'string') {
|
||||||
const arrayValues = data.hardcodedValues[key] || [];
|
const arrayValues = value || [];
|
||||||
return (
|
return (
|
||||||
<div key={key} className="input-container">
|
<div key={fullKey} className="input-container">
|
||||||
{arrayValues.map((item: string, index: number) => (
|
{arrayValues.map((item: string, index: number) => (
|
||||||
<div key={`${key}-${index}`} className="array-item-container">
|
<div key={`${fullKey}.${index}`} className="array-item-container">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={item}
|
value={item}
|
||||||
onChange={(e) => handleArrayItemChange(key, index, e.target.value)}
|
onChange={(e) => handleInputChange(`${fullKey}.${index}`, e.target.value)}
|
||||||
className="array-item-input"
|
className="array-item-input"
|
||||||
/>
|
/>
|
||||||
<button onClick={() => removeArrayItem(key, index)} className="array-item-remove">
|
<button onClick={() => handleInputChange(`${fullKey}.${index}`, '')} className="array-item-remove">
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<button onClick={() => addArrayItem(key)} className="array-item-add">
|
<button onClick={() => handleInputChange(fullKey, [...arrayValues, ''])} className="array-item-add">
|
||||||
Add Item
|
Add Item
|
||||||
</button>
|
</button>
|
||||||
{error && <span className="error-message">{error}</span>}
|
{error && <span className="error-message">{error}</span>}
|
||||||
@@ -262,52 +373,33 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
default:
|
default:
|
||||||
return null;
|
return (
|
||||||
|
<div key={fullKey} className="input-container">
|
||||||
|
{renderClickableInput(value ? `${key} (Complex)` : `Enter ${key} (Complex)`)}
|
||||||
|
{error && <span className="error-message">{error}</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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">
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateInputs = () => {
|
const validateInputs = () => {
|
||||||
const newErrors: { [key: string]: string | null } = {};
|
const newErrors: { [key: string]: string | null } = {};
|
||||||
Object.keys(data.inputSchema.properties).forEach((key) => {
|
const validateRecursive = (schema: any, parentKey: string = '') => {
|
||||||
const value = data.hardcodedValues[key];
|
Object.entries(schema.properties).forEach(([key, propSchema]: [string, any]) => {
|
||||||
const schema = data.inputSchema.properties[key];
|
const fullKey = parentKey ? `${parentKey}.${key}` : key;
|
||||||
const error = validateInput(key, value, schema);
|
const value = getValue(fullKey);
|
||||||
if (error) {
|
|
||||||
newErrors[key] = error;
|
if (propSchema.type === 'object' && propSchema.properties) {
|
||||||
}
|
validateRecursive(propSchema, fullKey);
|
||||||
});
|
} else {
|
||||||
|
if (propSchema.required && !value) {
|
||||||
|
newErrors[fullKey] = `${fullKey} is required`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
validateRecursive(data.inputSchema);
|
||||||
setErrors(newErrors);
|
setErrors(newErrors);
|
||||||
return Object.values(newErrors).every((error) => error === null);
|
return Object.values(newErrors).every((error) => error === null);
|
||||||
};
|
};
|
||||||
@@ -320,9 +412,23 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const nodeClass = () => {
|
||||||
|
switch (data.status) {
|
||||||
|
case 'RUNNING':
|
||||||
|
return 'node-running';
|
||||||
|
case 'COMPLETED':
|
||||||
|
return 'node-completed';
|
||||||
|
case 'QUEUED':
|
||||||
|
return 'node-queued';
|
||||||
|
case 'FAILED':
|
||||||
|
return 'node-failed';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="custom-node">
|
<div className={`custom-node ${nodeClass()}`}>
|
||||||
<div className="node-header">
|
<div className="node-header">
|
||||||
<div className="node-title">{data.blockType || data.title}</div>
|
<div className="node-title">{data.blockType || data.title}</div>
|
||||||
<button onClick={toggleProperties} className="toggle-button">
|
<button onClick={toggleProperties} className="toggle-button">
|
||||||
@@ -332,38 +438,18 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
|||||||
<div className="node-content">
|
<div className="node-content">
|
||||||
<div className="input-section">
|
<div className="input-section">
|
||||||
{data.inputSchema &&
|
{data.inputSchema &&
|
||||||
Object.keys(data.inputSchema.properties).map((key) => (
|
Object.entries(data.inputSchema.properties).map(([key, schema]) => (
|
||||||
<div key={key}>
|
<div key={key}>
|
||||||
{key !== 'texts' ? (
|
<div className="handle-container">
|
||||||
<div>
|
<Handle
|
||||||
<div className="handle-container">
|
type="target"
|
||||||
<Handle
|
position={Position.Left}
|
||||||
type="target"
|
id={key}
|
||||||
position={Position.Left}
|
style={{ background: '#555', borderRadius: '50%' }}
|
||||||
id={key}
|
/>
|
||||||
style={{ background: '#555', borderRadius: '50%' }}
|
<span className="handle-label">{key}</span>
|
||||||
/>
|
</div>
|
||||||
<span className="handle-label">{key}</span>
|
{renderInputField(key, schema)}
|
||||||
</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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
import React, {useState, useCallback, useEffect, useMemo, useRef} from 'react';
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
addEdge,
|
addEdge,
|
||||||
applyNodeChanges,
|
applyNodeChanges,
|
||||||
@@ -19,6 +19,7 @@ import './flow.css';
|
|||||||
type Schema = {
|
type Schema = {
|
||||||
type: string;
|
type: string;
|
||||||
properties: { [key: string]: any };
|
properties: { [key: string]: any };
|
||||||
|
additionalProperties?: { type: string };
|
||||||
required?: string[];
|
required?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -44,12 +45,6 @@ type AvailableNode = {
|
|||||||
outputSchema: Schema;
|
outputSchema: Schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ExecData {
|
|
||||||
node_id: string;
|
|
||||||
status: string;
|
|
||||||
output_data: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Sidebar: React.FC<{isOpen: boolean, availableNodes: AvailableNode[], addNode: (id: string, name: string) => void}> =
|
const Sidebar: React.FC<{isOpen: boolean, availableNodes: AvailableNode[], addNode: (id: string, name: string) => void}> =
|
||||||
({isOpen, availableNodes, addNode}) => {
|
({isOpen, availableNodes, addNode}) => {
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
@@ -61,28 +56,17 @@ const Sidebar: React.FC<{isOpen: boolean, availableNodes: AvailableNode[], addNo
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div className={`sidebar ${isOpen ? 'open' : ''}`}>
|
||||||
position: 'absolute',
|
<h3>Nodes</h3>
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
width: '250px',
|
|
||||||
backgroundColor: '#333',
|
|
||||||
padding: '20px',
|
|
||||||
zIndex: 4,
|
|
||||||
overflowY: 'auto'
|
|
||||||
}}>
|
|
||||||
<h3 style={{color: '#fff'}}>Nodes</h3>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search nodes..."
|
placeholder="Search nodes..."
|
||||||
style={{width: '100%', marginBottom: '10px', padding: '5px'}}
|
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
/>
|
/>
|
||||||
{filteredNodes.map((node) => (
|
{filteredNodes.map((node) => (
|
||||||
<div key={node.id} style={{marginBottom: '10px', display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
|
<div key={node.id} className="sidebarNodeRowStyle">
|
||||||
<span style={{color: '#fff'}}>{node.name}</span>
|
<span>{node.name}</span>
|
||||||
<button onClick={() => addNode(node.id, node.name)}>Add</button>
|
<button onClick={() => addNode(node.id, node.name)}>Add</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -99,6 +83,7 @@ const Flow: React.FC = () => {
|
|||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
||||||
|
|
||||||
const apiUrl = 'http://localhost:8000';
|
const apiUrl = 'http://localhost:8000';
|
||||||
|
const nodeIdMapping = useRef<{ [uuid: string]: string }>({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`${apiUrl}/blocks`)
|
fetch(`${apiUrl}/blocks`)
|
||||||
@@ -183,38 +168,99 @@ const Flow: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const prepareNodeInputData = (node: Node<CustomNodeData>, allNodes: Node<CustomNodeData>[], allEdges: Edge[]) => {
|
const prepareNodeInputData = (node: Node<CustomNodeData>, allNodes: Node<CustomNodeData>[], allEdges: Edge[]) => {
|
||||||
console.log("Preparing input data for node:", node.id, node.data.blockType);
|
console.log("Preparing input data for node:", node.id, node.data.blockType);
|
||||||
|
|
||||||
const blockSchema = availableNodes.find(n => n.id === node.data.block_id)?.inputSchema;
|
const blockSchema = availableNodes.find(n => n.id === node.data.block_id)?.inputSchema;
|
||||||
|
|
||||||
if (!blockSchema) {
|
if (!blockSchema) {
|
||||||
console.error(`Schema not found for block ID: ${node.data.block_id}`);
|
console.error(`Schema not found for block ID: ${node.data.block_id}`);
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNestedData = (schema: Schema, values: { [key: string]: any }): { [key: string]: any } => {
|
||||||
|
let inputData: { [key: string]: any } = {};
|
||||||
|
|
||||||
|
if (schema.properties) {
|
||||||
|
Object.keys(schema.properties).forEach((key) => {
|
||||||
|
if (values[key] !== undefined) {
|
||||||
|
if (schema.properties[key].type === 'object') {
|
||||||
|
inputData[key] = getNestedData(schema.properties[key], values[key]);
|
||||||
|
} else {
|
||||||
|
inputData[key] = values[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputData: { [key: string]: any } = { ...node.data.hardcodedValues };
|
if (schema.additionalProperties) {
|
||||||
|
inputData = { ...inputData, ...values };
|
||||||
|
}
|
||||||
|
|
||||||
// Get data from connected nodes
|
|
||||||
const incomingEdges = allEdges.filter(edge => edge.target === node.id);
|
|
||||||
incomingEdges.forEach(edge => {
|
|
||||||
const sourceNode = allNodes.find(n => n.id === edge.source);
|
|
||||||
if (sourceNode && sourceNode.data.output_data) {
|
|
||||||
const outputKey = Object.keys(sourceNode.data.output_data)[0]; // Assuming single output
|
|
||||||
inputData[edge.targetHandle as string] = sourceNode.data.output_data[outputKey];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filter out any inputs that are not in the block's schema
|
|
||||||
Object.keys(inputData).forEach(key => {
|
|
||||||
if (!blockSchema.properties[key]) {
|
|
||||||
delete inputData[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Final prepared input for ${node.data.blockType} (${node.id}):`, inputData);
|
|
||||||
return inputData;
|
return inputData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let inputData = getNestedData(blockSchema, node.data.hardcodedValues);
|
||||||
|
|
||||||
|
// Get data from connected nodes
|
||||||
|
const incomingEdges = allEdges.filter(edge => edge.target === node.id);
|
||||||
|
incomingEdges.forEach(edge => {
|
||||||
|
const sourceNode = allNodes.find(n => n.id === edge.source);
|
||||||
|
if (sourceNode && sourceNode.data.output_data) {
|
||||||
|
const outputKey = Object.keys(sourceNode.data.output_data)[0]; // Assuming single output
|
||||||
|
inputData[edge.targetHandle as string] = sourceNode.data.output_data[outputKey];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter out any inputs that are not in the block's schema
|
||||||
|
Object.keys(inputData).forEach(key => {
|
||||||
|
if (!blockSchema.properties[key]) {
|
||||||
|
delete inputData[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Final prepared input for ${node.data.blockType} (${node.id}):`, inputData);
|
||||||
|
return inputData;
|
||||||
|
};
|
||||||
|
|
||||||
|
const simulateNodeExecution = (nodes: Node<CustomNodeData>[]) => {
|
||||||
|
let currentStep = 0;
|
||||||
|
const totalSteps = 3; // Number of steps for the simulation
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (currentStep >= totalSteps) {
|
||||||
|
clearInterval(interval);
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((node) => ({
|
||||||
|
...node,
|
||||||
|
data: {
|
||||||
|
...node.data,
|
||||||
|
status: 'COMPLETED',
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
console.log('All nodes completed execution');
|
||||||
|
alert('Agent ran successfully!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setNodes((nds) =>
|
||||||
|
nds.map((node) => {
|
||||||
|
let status = 'QUEUED';
|
||||||
|
if (currentStep === 1) status = 'RUNNING';
|
||||||
|
if (currentStep === 2) status = 'COMPLETED';
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
data: {
|
||||||
|
...node.data,
|
||||||
|
status: status,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
currentStep += 1;
|
||||||
|
}, 1000); // Interval between steps (1 second)
|
||||||
|
};
|
||||||
|
|
||||||
const runAgent = async () => {
|
const runAgent = async () => {
|
||||||
try {
|
try {
|
||||||
console.log("All nodes before formatting:", nodes);
|
console.log("All nodes before formatting:", nodes);
|
||||||
@@ -246,11 +292,19 @@ const Flow: React.FC = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const links = edges.map(edge => ({
|
||||||
|
source_id: edge.source,
|
||||||
|
sink_id: edge.target,
|
||||||
|
source_name: edge.sourceHandle || '',
|
||||||
|
sink_name: edge.targetHandle || ''
|
||||||
|
}));
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
id: agentId || '',
|
id: agentId || '',
|
||||||
name: 'Agent Name',
|
name: 'Agent Name',
|
||||||
description: 'Agent Description',
|
description: 'Agent Description',
|
||||||
nodes: formattedNodes,
|
nodes: formattedNodes,
|
||||||
|
links: links // Ensure this field is included
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("Payload being sent to the API:", JSON.stringify(payload, null, 2));
|
console.log("Payload being sent to the API:", JSON.stringify(payload, null, 2));
|
||||||
@@ -268,14 +322,12 @@ const Flow: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createData = await createResponse.json();
|
const createData = await createResponse.json();
|
||||||
|
|
||||||
const newAgentId = createData.id;
|
const newAgentId = createData.id;
|
||||||
setAgentId(newAgentId);
|
setAgentId(newAgentId);
|
||||||
|
|
||||||
console.log('Response from the API:', JSON.stringify(createData, null, 2));
|
console.log('Response from the API:', JSON.stringify(createData, null, 2));
|
||||||
|
|
||||||
const executeResponse = await fetch(`${apiUrl}/graphs/${newAgentId}/execute`, {
|
const executeResponse = await fetch(`${apiUrl}/graphs/${newAgentId}/execute`, {
|
||||||
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -290,15 +342,17 @@ const Flow: React.FC = () => {
|
|||||||
const executeData = await executeResponse.json();
|
const executeData = await executeResponse.json();
|
||||||
const runId = executeData.id;
|
const runId = executeData.id;
|
||||||
|
|
||||||
|
|
||||||
const pollExecution = async () => {
|
const pollExecution = async () => {
|
||||||
const response = await fetch(`${apiUrl}/graphs/${newAgentId}/executions/${runId}`);
|
const response = await fetch(`${apiUrl}/graphs/${newAgentId}/executions/${runId}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
data.forEach(updateNodeData);
|
// updateNodesWithExecutionData(data);
|
||||||
|
|
||||||
if (data.every((node: any) => node.status === 'COMPLETED')) {
|
if (data.every((node: any) => node.status === 'COMPLETED')) {
|
||||||
|
simulateNodeExecution(nodes);
|
||||||
console.log('All nodes completed execution');
|
console.log('All nodes completed execution');
|
||||||
} else {
|
} else {
|
||||||
setTimeout(pollExecution, 1000);
|
setTimeout(pollExecution, 1000);
|
||||||
@@ -312,47 +366,38 @@ const Flow: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateNodesWithExecutionData = (executionData: any[]) => {
|
|
||||||
setNodes((nds) =>
|
const updateNodesWithExecutionData = useCallback((executionData: any[]) => {
|
||||||
nds.map((node) => {
|
console.log("Updating nodes with execution data:", executionData);
|
||||||
const nodeExecution = executionData.find((exec) => exec.node_id === node.id);
|
|
||||||
if (nodeExecution) {
|
setNodes((nds) =>
|
||||||
return {
|
nds.map((node) => {
|
||||||
...node,
|
// Find the corresponding execution data for the current node
|
||||||
data: {
|
const nodeExecution = executionData.find((exec) => {
|
||||||
...node.data,
|
console.log("Checking exec.node_id:", exec.block_id, "against node.id:", node.data.block_id);
|
||||||
status: nodeExecution.status,
|
return nodeIdMapping.current[exec.block_id] === node.data.block_id;
|
||||||
output_data: nodeExecution.output_data,
|
});
|
||||||
isPropertiesOpen: true,
|
|
||||||
},
|
console.log("Node:", node, "Node Execution:", nodeExecution);
|
||||||
};
|
|
||||||
}
|
if (nodeExecution) {
|
||||||
return node;
|
return {
|
||||||
})
|
...node,
|
||||||
);
|
data: {
|
||||||
};
|
...node.data,
|
||||||
|
status: nodeExecution.status,
|
||||||
|
output_data: nodeExecution.output_data,
|
||||||
|
isPropertiesOpen: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [setNodes]);
|
||||||
|
|
||||||
const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);
|
const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);
|
||||||
|
|
||||||
const updateNodeData = (execData: ExecData) => {
|
|
||||||
setNodes((nds) =>
|
|
||||||
nds.map((node) => {
|
|
||||||
if (node.id === execData.node_id) {
|
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
data: {
|
|
||||||
...node.data,
|
|
||||||
status: execData.status,
|
|
||||||
output_data: execData.output_data,
|
|
||||||
isPropertiesOpen: true, // Open the properties
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100vh', width: '100%' }}>
|
<div style={{ height: '100vh', width: '100%' }}>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
.custom-node {
|
.custom-node {
|
||||||
padding: 20px;
|
padding: 15px;
|
||||||
border: 2px solid #fff;
|
border: 2px solid #fff;
|
||||||
border-radius: 20px;
|
border-radius: 12px;
|
||||||
background: #333;
|
background: #2c2c2c;
|
||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
width: 250px;
|
width: 300px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-header {
|
.node-header {
|
||||||
@@ -27,10 +30,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.node-content {
|
.node-content {
|
||||||
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
gap: 10px;
|
||||||
gap: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-section {
|
.input-section {
|
||||||
@@ -39,6 +42,9 @@
|
|||||||
|
|
||||||
.output-section {
|
.output-section {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.handle-container {
|
.handle-container {
|
||||||
@@ -137,3 +143,79 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.object-input {
|
||||||
|
margin-left: 10px;
|
||||||
|
border-left: 1px solid #555;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-input {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-value-input {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-value-input input {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-running {
|
||||||
|
animation: runningAnimation 1s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes runningAnimation {
|
||||||
|
0% {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-completed {
|
||||||
|
animation: completedAnimation 2s;
|
||||||
|
background-color: #4caf50;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes completedAnimation {
|
||||||
|
0% {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-color: #4caf50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-queued {
|
||||||
|
animation: queuedAnimation 2s infinite;
|
||||||
|
background-color: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes queuedAnimation {
|
||||||
|
0% {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-color: #ffc107;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-failed {
|
||||||
|
animation: failedAnimation 2s;
|
||||||
|
background-color: #f44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes failedAnimation {
|
||||||
|
0% {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-color: #f44336;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -148,3 +148,177 @@ input::placeholder, textarea::placeholder {
|
|||||||
.flow-controls.open {
|
.flow-controls.open {
|
||||||
transform: translateX(350px);
|
transform: translateX(350px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-node {
|
||||||
|
padding: 20px;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #333;
|
||||||
|
color: #e0e0e0;
|
||||||
|
width: 250px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-node.node-running {
|
||||||
|
border-color: yellow;
|
||||||
|
animation: running-animation 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-node.node-completed {
|
||||||
|
border-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-node.node-queued {
|
||||||
|
border-color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-node.node-failed {
|
||||||
|
border-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes running-animation {
|
||||||
|
0% { border-color: yellow; }
|
||||||
|
50% { border-color: #333; }
|
||||||
|
100% { border-color: yellow; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-button {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-section {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-section {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle-label {
|
||||||
|
color: #e0e0e0;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable-input {
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #555;
|
||||||
|
background: #444;
|
||||||
|
color: #e0e0e0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #555;
|
||||||
|
background: #444;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-label {
|
||||||
|
display: block;
|
||||||
|
margin: 5px 0;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #555;
|
||||||
|
background: #444;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.array-item-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.array-item-input {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #555;
|
||||||
|
background: #444;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.array-item-remove {
|
||||||
|
background: #d9534f;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.array-item-add {
|
||||||
|
background: #5bc0de;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-properties {
|
||||||
|
margin-top: 20px;
|
||||||
|
background: #444;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: #d9534f;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.object-input {
|
||||||
|
margin-left: 10px;
|
||||||
|
border-left: 1px solid #555;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-input {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|||||||
7
rnd/autogpt_builder/webpack.config.js
Normal file
7
rnd/autogpt_builder/webpack.config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
devServer: {
|
||||||
|
proxy: {
|
||||||
|
'/graphs': 'http://localhost:8000'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import praw
|
import praw
|
||||||
|
from typing import Any
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from autogpt_server.data.block import Block, BlockOutput, BlockSchema
|
from autogpt_server.data.block import Block, BlockOutput, BlockSchema
|
||||||
@@ -87,8 +88,9 @@ class RedditGetPostsBlock(Block):
|
|||||||
class RedditPostCommentBlock(Block):
|
class RedditPostCommentBlock(Block):
|
||||||
class Input(BlockSchema):
|
class Input(BlockSchema):
|
||||||
creds: RedditCredentials = Field(description="Reddit credentials")
|
creds: RedditCredentials = Field(description="Reddit credentials")
|
||||||
post_id: str = Field(description="Reddit post ID")
|
data: Any = Field(description="Reddit post")
|
||||||
comment: str = Field(description="Comment text")
|
# post_id: str = Field(description="Reddit post ID")
|
||||||
|
# comment: str = Field(description="Comment text")
|
||||||
|
|
||||||
class Output(BlockSchema):
|
class Output(BlockSchema):
|
||||||
comment_id: str
|
comment_id: str
|
||||||
@@ -102,6 +104,6 @@ class RedditPostCommentBlock(Block):
|
|||||||
|
|
||||||
def run(self, input_data: Input) -> BlockOutput:
|
def run(self, input_data: Input) -> BlockOutput:
|
||||||
client = get_praw(input_data.creds)
|
client = get_praw(input_data.creds)
|
||||||
submission = client.submission(id=input_data.post_id)
|
submission = client.submission(id=input_data.data["post_id"])
|
||||||
comment = submission.reply(input_data.comment)
|
comment = submission.reply(input_data.data["comment"])
|
||||||
yield "comment_id", comment.id
|
yield "comment_id", comment.id
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
@@ -7,7 +8,7 @@ from autogpt_server.data.block import Block, BlockOutput, BlockSchema
|
|||||||
|
|
||||||
class TextMatcherBlock(Block):
|
class TextMatcherBlock(Block):
|
||||||
class Input(BlockSchema):
|
class Input(BlockSchema):
|
||||||
text: str = Field(description="Text to match")
|
text: Any = Field(description="Text to match")
|
||||||
match: str = Field(description="Pattern (Regex) to match")
|
match: str = Field(description="Pattern (Regex) to match")
|
||||||
data: Any = Field(description="Data to be forwarded to output")
|
data: Any = Field(description="Data to be forwarded to output")
|
||||||
case_sensitive: bool = Field(description="Case sensitive match", default=True)
|
case_sensitive: bool = Field(description="Case sensitive match", default=True)
|
||||||
@@ -26,7 +27,7 @@ class TextMatcherBlock(Block):
|
|||||||
def run(self, input_data: Input) -> BlockOutput:
|
def run(self, input_data: Input) -> BlockOutput:
|
||||||
output = input_data.data or input_data.text
|
output = input_data.data or input_data.text
|
||||||
case = 0 if input_data.case_sensitive else re.IGNORECASE
|
case = 0 if input_data.case_sensitive else re.IGNORECASE
|
||||||
if re.search(input_data.match, input_data.text, case):
|
if re.search(input_data.match, json.dumps(input_data.text), case):
|
||||||
yield "positive", output
|
yield "positive", output
|
||||||
else:
|
else:
|
||||||
yield "negative", output
|
yield "negative", output
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import uvicorn
|
|||||||
|
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from fastapi import APIRouter, Body, FastAPI, HTTPException
|
from fastapi import APIRouter, Body, FastAPI, HTTPException
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from autogpt_server.data import db, execution, block
|
from autogpt_server.data import db, execution, block
|
||||||
from autogpt_server.data.graph import (
|
from autogpt_server.data.graph import (
|
||||||
@@ -43,6 +44,14 @@ class AgentServer(AppProcess):
|
|||||||
lifespan=self.lifespan,
|
lifespan=self.lifespan,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"], # Allows all origins
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"], # Allows all methods
|
||||||
|
allow_headers=["*"], # Allows all headers
|
||||||
|
)
|
||||||
|
|
||||||
# Define the API routes
|
# Define the API routes
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
router.add_api_route(
|
router.add_api_route(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from pathlib import Path
|
|||||||
from pkgutil import iter_modules
|
from pkgutil import iter_modules
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from cx_Freeze import Executable, setup # type: ignore
|
from cx_Freeze import Executable, setup # type: ignore
|
||||||
|
|
||||||
packages = [
|
packages = [
|
||||||
m.name
|
m.name
|
||||||
@@ -57,7 +57,6 @@ def txt_to_rtf(input_file: Union[str, Path], output_file: Union[str, Path]) -> N
|
|||||||
license_file = "LICENSE.rtf"
|
license_file = "LICENSE.rtf"
|
||||||
txt_to_rtf("../../LICENSE", license_file)
|
txt_to_rtf("../../LICENSE", license_file)
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="AutoGPT Server",
|
name="AutoGPT Server",
|
||||||
url="https://agpt.co",
|
url="https://agpt.co",
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ Make sure to only comment on a relevant post.
|
|||||||
"expected_format": {
|
"expected_format": {
|
||||||
"post_id": "str, the reddit post id",
|
"post_id": "str, the reddit post id",
|
||||||
"is_relevant": "bool, whether the post is relevant for marketing",
|
"is_relevant": "bool, whether the post is relevant for marketing",
|
||||||
"marketing_text": "str, marketing text, this is empty on irrelevant posts",
|
"comment": "str, marketing text, this is empty on irrelevant posts",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
text_matcher_input = {"match": "true", "case_sensitive": False}
|
text_matcher_input = {"match": "true", "case_sensitive": False}
|
||||||
|
|||||||
Reference in New Issue
Block a user