feat(autogpt_builder) Update custom node to handle deeply nested structures (#7319)

* feat(rnd): Add type hint and strong pydantic type validation for block input/output + add reddit agent-blocks.

* feat(rnd): Add type hint and strong pydantic type validation for block input/output + add reddit agent-blocks.

* Fix reddit block

* Fix serialization

* Eliminate deprecated class property

* Remove RedditCredentialsBlock

* Cache jsonschema computation, add dictionary construction

* Add dict_split and list_split to output, add more blocks

* Add objc_split for completeness, int both input and output

* Update reddit block

* Add reddit test (untested)

* Resolved json issue on pydantic

* Add creds check on client

* Add dict <--> pydantic object flexibility

* Fix error retry

* Skip reddit test

* Code cleanup

* Chang prompt

* Make this work

* Fix linting

* Hide input_links and output_links from Node

* Add docs

* updating UI to handle deeply nested data structures for reddit usecase

* changing expected key in reddit post to comment

---------

Co-authored-by: Zamil Majdy <zamil.majdy@agpt.co>
pull/7328/head
Aarushi 2024-07-04 17:54:41 +01:00 committed by GitHub
parent 833944e228
commit 6456285753
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 393 additions and 277 deletions

View File

@ -8,6 +8,12 @@ type Schema = {
type: string;
properties: { [key: string]: any };
required?: string[];
enum?: string[];
items?: Schema;
additionalProperties?: { type: string };
allOf?: any[];
anyOf?: any[];
oneOf?: any[];
};
type CustomNodeData = {
@ -25,6 +31,9 @@ type CustomNodeData = {
const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
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 [activeKey, setActiveKey] = useState<string | null>(null);
const [modalValue, setModalValue] = useState<string>('');
@ -76,41 +85,24 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
};
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);
data.setHardcodedValues(newValues);
setErrors((prevErrors) => ({ ...prevErrors, [key]: null }));
};
const validateInput = (key: string, value: any, schema: any) => {
switch (schema.type) {
case 'string':
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 getValue = (key: string) => {
const keys = key.split('.');
return keys.reduce((acc, k) => (acc && acc[k] !== undefined) ? acc[k] : '', data.hardcodedValues);
};
const isHandleConnected = (key: string) => {
@ -123,66 +115,180 @@ 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) => {
setActiveKey(key);
setModalValue(data.hardcodedValues[key] || '');
const value = getValue(key);
setModalValue(typeof value === 'object' ? JSON.stringify(value, null, 2) : value);
setIsModalOpen(true);
};
const handleModalSave = (value: string) => {
if (activeKey) {
handleInputChange(activeKey, value);
try {
const parsedValue = JSON.parse(value);
handleInputChange(activeKey, parsedValue);
} catch (error) {
handleInputChange(activeKey, value);
}
}
setIsModalOpen(false);
setActiveKey(null);
};
const addArrayItem = (key: string) => {
const currentValues = data.hardcodedValues[key] || [];
handleInputChange(key, [...currentValues, '']);
};
const renderInputField = (key: string, schema: any, parentKey: string = ''): JSX.Element => {
const fullKey = parentKey ? `${parentKey}.${key}` : key;
const error = errors[fullKey];
const value = getValue(fullKey);
const removeArrayItem = (key: string, index: number) => {
const currentValues = data.hardcodedValues[key] || [];
currentValues.splice(index, 1);
handleInputChange(key, [...currentValues]);
};
if (isHandleConnected(fullKey)) {
return <div className="connected-input">Connected</div>;
}
const handleArrayItemChange = (key: string, index: number, value: string) => {
const currentValues = data.hardcodedValues[key] || [];
currentValues[index] = value;
handleInputChange(key, [...currentValues]);
};
const renderClickableInput = (displayValue: string) => (
<div className="clickable-input" onClick={() => handleInputClick(fullKey)}>
{displayValue}
</div>
);
const addDynamicTextInput = () => {
const dynamicKeyPrefix = 'texts_$_';
const currentKeys = Object.keys(data.hardcodedValues).filter(key => key.startsWith(dynamicKeyPrefix));
const nextIndex = currentKeys.length + 1;
const newKey = `${dynamicKeyPrefix}${nextIndex}`;
handleInputChange(newKey, '');
};
if (schema.type === 'object' && schema.properties) {
return (
<div key={fullKey} className="object-input">
<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 removeDynamicTextInput = (key: string) => {
const newValues = { ...data.hardcodedValues };
delete newValues[key];
data.setHardcodedValues(newValues);
};
if (schema.type === 'object' && schema.additionalProperties) {
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">
&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={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>
);
}
const handleDynamicTextInputChange = (key: string, value: string) => {
handleInputChange(key, value);
};
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) {
case 'string':
return schema.enum ? (
<div key={key} className="input-container">
<div key={fullKey} className="input-container">
<select
value={data.hardcodedValues[key] || ''}
onChange={(e) => handleInputChange(key, e.target.value)}
value={value || ''}
onChange={(e) => handleInputChange(fullKey, e.target.value)}
className="select-input"
>
<option value="">Select {key}</option>
{schema.enum.map((option: string) => (
<option key={option} value={option}>
{option}
@ -192,44 +298,34 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
{error && <span className="error-message">{error}</span>}
</div>
) : (
<div key={key} className="input-container">
<div className="clickable-input" onClick={() => handleInputClick(key)}>
{data.hardcodedValues[key] || `Enter ${key}`}
</div>
<div key={fullKey} className="input-container">
{renderClickableInput(value || `Enter ${key}`)}
{error && <span className="error-message">{error}</span>}
</div>
);
case 'boolean':
return (
<div key={key} className="input-container">
<label className="radio-label">
<input
type="radio"
value="true"
checked={data.hardcodedValues[key] === true}
onChange={() => handleInputChange(key, true)}
/>
True
</label>
<label className="radio-label">
<input
type="radio"
value="false"
checked={data.hardcodedValues[key] === false}
onChange={() => handleInputChange(key, false)}
/>
False
</label>
<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 {key}</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={key} className="input-container">
<div key={fullKey} className="input-container">
<input
type="number"
value={data.hardcodedValues[key] || ''}
onChange={(e) => handleInputChange(key, parseFloat(e.target.value))}
value={value || ''}
onChange={(e) => handleInputChange(fullKey, parseFloat(e.target.value))}
className="number-input"
/>
{error && <span className="error-message">{error}</span>}
@ -237,23 +333,23 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
);
case 'array':
if (schema.items && schema.items.type === 'string') {
const arrayValues = data.hardcodedValues[key] || [];
const arrayValues = value || [];
return (
<div key={key} className="input-container">
<div key={fullKey} className="input-container">
{arrayValues.map((item: string, index: number) => (
<div key={`${key}-${index}`} className="array-item-container">
<div key={`${fullKey}.${index}`} className="array-item-container">
<input
type="text"
value={item}
onChange={(e) => handleArrayItemChange(key, index, e.target.value)}
onChange={(e) => handleInputChange(`${fullKey}.${index}`, e.target.value)}
className="array-item-input"
/>
<button onClick={() => removeArrayItem(key, index)} className="array-item-remove">
<button onClick={() => handleInputChange(`${fullKey}.${index}`, '')} className="array-item-remove">
&times;
</button>
</div>
))}
<button onClick={() => addArrayItem(key)} className="array-item-add">
<button onClick={() => handleInputChange(fullKey, [...arrayValues, ''])} className="array-item-add">
Add Item
</button>
{error && <span className="error-message">{error}</span>}
@ -262,52 +358,33 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
}
return null;
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">
&times;
</button>
</>
)}
</div>
</div>
));
};
const validateInputs = () => {
const newErrors: { [key: string]: string | null } = {};
Object.keys(data.inputSchema.properties).forEach((key) => {
const value = data.hardcodedValues[key];
const schema = data.inputSchema.properties[key];
const error = validateInput(key, value, schema);
if (error) {
newErrors[key] = error;
}
});
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);
};
@ -320,9 +397,8 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
}
};
return (
<div className="custom-node">
<div className={`custom-node ${data.status === 'RUNNING' ? 'running' : data.status === 'COMPLETED' ? 'completed' : ''}`}>
<div className="node-header">
<div className="node-title">{data.blockType || data.title}</div>
<button onClick={toggleProperties} className="toggle-button">
@ -332,38 +408,18 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
<div className="node-content">
<div className="input-section">
{data.inputSchema &&
Object.keys(data.inputSchema.properties).map((key) => (
Object.entries(data.inputSchema.properties).map(([key, schema]) => (
<div key={key}>
{key !== 'texts' ? (
<div>
<div className="handle-container">
<Handle
type="target"
position={Position.Left}
id={key}
style={{ background: '#555', borderRadius: '50%' }}
/>
<span className="handle-label">{key}</span>
</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 className="handle-container">
<Handle
type="target"
position={Position.Left}
id={key}
style={{ background: '#555', borderRadius: '50%' }}
/>
<span className="handle-label">{key}</span>
</div>
{renderInputField(key, schema)}
</div>
))}
</div>
@ -397,4 +453,4 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
);
};
export default memo(CustomNode);
export default memo(CustomNode);

View File

@ -19,6 +19,7 @@ import './flow.css';
type Schema = {
type: string;
properties: { [key: string]: any };
additionalProperties?: { type: string };
required?: string[];
};
@ -44,12 +45,6 @@ type AvailableNode = {
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}> =
({isOpen, availableNodes, addNode}) => {
const [searchQuery, setSearchQuery] = useState('');
@ -61,28 +56,17 @@ const Sidebar: React.FC<{isOpen: boolean, availableNodes: AvailableNode[], addNo
);
return (
<div style={{
position: 'absolute',
left: 0,
top: 0,
bottom: 0,
width: '250px',
backgroundColor: '#333',
padding: '20px',
zIndex: 4,
overflowY: 'auto'
}}>
<h3 style={{color: '#fff'}}>Nodes</h3>
<div className={`sidebar ${isOpen ? 'open' : ''}`}>
<h3>Nodes</h3>
<input
type="text"
placeholder="Search nodes..."
style={{width: '100%', marginBottom: '10px', padding: '5px'}}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
{filteredNodes.map((node) => (
<div key={node.id} style={{marginBottom: '10px', display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
<span style={{color: '#fff'}}>{node.name}</span>
<div key={node.id} className="sidebarNodeRowStyle">
<span>{node.name}</span>
<button onClick={() => addNode(node.id, node.name)}>Add</button>
</div>
))}
@ -183,38 +167,61 @@ const Flow: React.FC = () => {
};
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) {
console.error(`Schema not found for block ID: ${node.data.block_id}`);
return {};
if (!blockSchema) {
console.error(`Schema not found for block ID: ${node.data.block_id}`);
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;
};
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 runAgent = async () => {
try {
console.log("All nodes before formatting:", nodes);
@ -246,11 +253,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 = {
id: agentId || '',
name: 'Agent Name',
description: 'Agent Description',
nodes: formattedNodes,
links: links // Ensure this field is included
};
console.log("Payload being sent to the API:", JSON.stringify(payload, null, 2));
@ -268,14 +283,12 @@ const Flow: React.FC = () => {
}
const createData = await createResponse.json();
const newAgentId = createData.id;
setAgentId(newAgentId);
console.log('Response from the API:', JSON.stringify(createData, null, 2));
const executeResponse = await fetch(`${apiUrl}/graphs/${newAgentId}/execute`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -290,14 +303,15 @@ const Flow: React.FC = () => {
const executeData = await executeResponse.json();
const runId = executeData.id;
const pollExecution = async () => {
const response = await fetch(`${apiUrl}/graphs/${newAgentId}/executions/${runId}`);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
data.forEach(updateNodeData);
updateNodesWithExecutionData(data);
if (data.every((node: any) => node.status === 'COMPLETED')) {
console.log('All nodes completed execution');
} else {
@ -312,47 +326,29 @@ const Flow: React.FC = () => {
}
};
const updateNodesWithExecutionData = (executionData: any[]) => {
setNodes((nds) =>
nds.map((node) => {
const nodeExecution = executionData.find((exec) => exec.node_id === node.id);
if (nodeExecution) {
return {
...node,
data: {
...node.data,
status: nodeExecution.status,
output_data: nodeExecution.output_data,
isPropertiesOpen: true,
},
};
}
return node;
})
);
};
const updateNodesWithExecutionData = (executionData: any[]) => {
setNodes((nds) =>
nds.map((node) => {
const nodeExecution = executionData.find((exec) => exec.node_id === node.id);
if (nodeExecution) {
return {
...node,
data: {
...node.data,
status: nodeExecution.status,
output_data: nodeExecution.output_data,
isPropertiesOpen: true,
},
};
}
return node;
})
);
};
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 (
<div style={{ height: '100vh', width: '100%' }}>
<button
@ -384,4 +380,4 @@ const Flow: React.FC = () => {
);
};
export default Flow;
export default Flow;

View File

@ -1,10 +1,11 @@
.custom-node {
padding: 20px;
padding: 15px;
border: 2px solid #fff;
border-radius: 20px;
background: #333;
border-radius: 12px;
background: #2c2c2c;
color: #e0e0e0;
width: 250px;
width: 300px; /* Adjust width for better layout */
box-sizing: border-box; /* Ensure padding doesn't affect overall width */
}
.node-header {
@ -28,9 +29,8 @@
.node-content {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 20px;
flex-direction: column; /* Use column to stack elements */
gap: 10px;
}
.input-section {
@ -137,3 +137,49 @@
font-size: 12px;
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;
}
@keyframes runningAnimation {
0% { background-color: #f39c12; }
50% { background-color: #e67e22; }
100% { background-color: #f39c12; }
}
.running {
animation: runningAnimation 1s infinite;
}
/* Animation for completed status */
@keyframes completedAnimation {
0% { background-color: #27ae60; }
100% { background-color: #2ecc71; }
}
.completed {
animation: completedAnimation 1s infinite;
}
/* Add more styles for better look */
.custom-node {
transition: all 0.3s ease-in-out;
}

View File

@ -0,0 +1,7 @@
module.exports = {
devServer: {
proxy: {
'/graphs': 'http://localhost:8000'
}
}
};

View File

@ -3,6 +3,7 @@
from datetime import datetime, timedelta
import praw
from typing import Any
from pydantic import BaseModel, Field
from autogpt_server.data.block import Block, BlockOutput, BlockSchema
@ -87,8 +88,9 @@ class RedditGetPostsBlock(Block):
class RedditPostCommentBlock(Block):
class Input(BlockSchema):
creds: RedditCredentials = Field(description="Reddit credentials")
post_id: str = Field(description="Reddit post ID")
comment: str = Field(description="Comment text")
data: Any = Field(description="Reddit post")
# post_id: str = Field(description="Reddit post ID")
# comment: str = Field(description="Comment text")
class Output(BlockSchema):
comment_id: str
@ -102,6 +104,6 @@ class RedditPostCommentBlock(Block):
def run(self, input_data: Input) -> BlockOutput:
client = get_praw(input_data.creds)
submission = client.submission(id=input_data.post_id)
comment = submission.reply(input_data.comment)
submission = client.submission(id=input_data.data["post_id"])
comment = submission.reply(input_data.data["comment"])
yield "comment_id", comment.id

View File

@ -1,4 +1,5 @@
import re
import json
from typing import Any
from pydantic import Field
@ -7,7 +8,7 @@ from autogpt_server.data.block import Block, BlockOutput, BlockSchema
class TextMatcherBlock(Block):
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")
data: Any = Field(description="Data to be forwarded to output")
case_sensitive: bool = Field(description="Case sensitive match", default=True)
@ -26,7 +27,7 @@ class TextMatcherBlock(Block):
def run(self, input_data: Input) -> BlockOutput:
output = input_data.data or input_data.text
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
else:
yield "negative", output

View File

@ -6,6 +6,7 @@ import uvicorn
from contextlib import asynccontextmanager
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.graph import (
@ -43,6 +44,14 @@ class AgentServer(AppProcess):
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
router = APIRouter()
router.add_api_route(

View File

@ -3,7 +3,7 @@ from pathlib import Path
from pkgutil import iter_modules
from typing import Union
from cx_Freeze import Executable, setup # type: ignore
from cx_Freeze import Executable, setup # type: ignore
packages = [
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"
txt_to_rtf("../../LICENSE", license_file)
setup(
name="AutoGPT Server",
url="https://agpt.co",
@ -102,7 +101,7 @@ setup(
"applications_shortcut": True,
"volume_label": "AutoGPTServer",
"background": "builtin-arrow",
"license": {
"default-language": "en_US",
"licenses": {"en_US": license_file},