feat(agent_builder): Updates to Builder GUI + Nodes (#7345)
* Updates to Builder GUI + Nodes * fix apiUrl back to local hostpull/7347/head
parent
f9bedb0fd9
commit
1e755f9e8d
|
@ -3,6 +3,9 @@ import { Handle, Position, NodeProps } from 'reactflow';
|
||||||
import 'reactflow/dist/style.css';
|
import 'reactflow/dist/style.css';
|
||||||
import './customnode.css';
|
import './customnode.css';
|
||||||
import ModalComponent from './ModalComponent';
|
import ModalComponent from './ModalComponent';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Input } from './ui/input';
|
||||||
|
import { Textarea } from './ui/textarea';
|
||||||
|
|
||||||
type Schema = {
|
type Schema = {
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -184,16 +187,16 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||||
<div className="clickable-input" onClick={() => handleInputClick(`${fullKey}.${propKey}`)}>
|
<div className="clickable-input" onClick={() => handleInputClick(`${fullKey}.${propKey}`)}>
|
||||||
{propKey}: {typeof propValue === 'object' ? JSON.stringify(propValue, null, 2) : propValue}
|
{propKey}: {typeof propValue === 'object' ? JSON.stringify(propValue, null, 2) : propValue}
|
||||||
</div>
|
</div>
|
||||||
<button onClick={() => handleInputChange(`${fullKey}.${propKey}`, undefined)} className="array-item-remove">
|
<Button onClick={() => handleInputChange(`${fullKey}.${propKey}`, undefined)} className="array-item-remove">
|
||||||
×
|
×
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{key === 'expected_format' && (
|
{key === 'expected_format' && (
|
||||||
<div className="nested-input">
|
<div className="nested-input">
|
||||||
{keyValuePairs.map((pair, index) => (
|
{keyValuePairs.map((pair, index) => (
|
||||||
<div key={index} className="key-value-input">
|
<div key={index} className="key-value-input">
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Key"
|
placeholder="Key"
|
||||||
value={pair.key}
|
value={pair.key}
|
||||||
|
@ -205,7 +208,7 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||||
handleInputChange('expected_format', expectedFormat);
|
handleInputChange('expected_format', expectedFormat);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Value"
|
placeholder="Value"
|
||||||
value={pair.value}
|
value={pair.value}
|
||||||
|
@ -220,20 +223,20 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className="key-value-input">
|
<div className="key-value-input">
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Key"
|
placeholder="Key"
|
||||||
value={newKey}
|
value={newKey}
|
||||||
onChange={(e) => setNewKey(e.target.value)}
|
onChange={(e) => setNewKey(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Value"
|
placeholder="Value"
|
||||||
value={newValue}
|
value={newValue}
|
||||||
onChange={(e) => setNewValue(e.target.value)}
|
onChange={(e) => setNewValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={handleAddProperty}>Add Property</button>
|
<Button onClick={handleAddProperty}>Add Property</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{error && <span className="error-message">{error}</span>}
|
{error && <span className="error-message">{error}</span>}
|
||||||
|
@ -344,14 +347,14 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||||
onChange={(e) => handleInputChange(`${fullKey}.${index}`, e.target.value)}
|
onChange={(e) => handleInputChange(`${fullKey}.${index}`, e.target.value)}
|
||||||
className="array-item-input"
|
className="array-item-input"
|
||||||
/>
|
/>
|
||||||
<button onClick={() => handleInputChange(`${fullKey}.${index}`, '')} className="array-item-remove">
|
<Button onClick={() => handleInputChange(`${fullKey}.${index}`, '')} className="array-item-remove">
|
||||||
×
|
×
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<button onClick={() => handleInputChange(fullKey, [...arrayValues, ''])} 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>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -398,12 +401,12 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`custom-node ${data.status === 'RUNNING' ? 'running' : data.status === 'COMPLETED' ? 'completed' : ''}`}>
|
<div className={`custom-node dark-theme ${data.status === 'RUNNING' ? 'running' : data.status === 'COMPLETED' ? 'completed' : ''}`}>
|
||||||
<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">
|
||||||
☰
|
☰
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="node-content">
|
<div className="node-content">
|
||||||
<div className="input-section">
|
<div className="input-section">
|
||||||
|
@ -442,7 +445,7 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<button onClick={handleSubmit}>Submit</button>
|
<Button onClick={handleSubmit}>Submit</Button>
|
||||||
<ModalComponent
|
<ModalComponent
|
||||||
isOpen={isModalOpen}
|
isOpen={isModalOpen}
|
||||||
onClose={() => setIsModalOpen(false)}
|
onClose={() => setIsModalOpen(false)}
|
||||||
|
|
|
@ -17,6 +17,8 @@ import CustomNode from './CustomNode';
|
||||||
import './flow.css';
|
import './flow.css';
|
||||||
import AutoGPTServerAPI, { Block, Flow } from '@/lib/autogpt_server_api';
|
import AutoGPTServerAPI, { Block, Flow } from '@/lib/autogpt_server_api';
|
||||||
import { ObjectSchema } from '@/lib/types';
|
import { ObjectSchema } from '@/lib/types';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Input } from './ui/input';
|
||||||
|
|
||||||
type CustomNodeData = {
|
type CustomNodeData = {
|
||||||
blockType: string;
|
blockType: string;
|
||||||
|
@ -43,18 +45,18 @@ const Sidebar: React.FC<{isOpen: boolean, availableNodes: Block[], addNode: (id:
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`sidebar ${isOpen ? 'open' : ''}`}>
|
<div className={`sidebar dark-theme ${isOpen ? 'open' : ''}`}>
|
||||||
<h3>Nodes</h3>
|
<h3>Nodes</h3>
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search nodes..."
|
placeholder="Search nodes..."
|
||||||
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} className="sidebarNodeRowStyle">
|
<div key={node.id} className="sidebarNodeRowStyle dark-theme">
|
||||||
<span>{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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -352,18 +354,18 @@ const updateNodesWithExecutionData = (executionData: any[]) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100vh', width: '100%' }}>
|
<div style={{ height: '100vh', width: '100%' }}>
|
||||||
<button
|
<Button
|
||||||
onClick={toggleSidebar}
|
onClick={toggleSidebar}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: isSidebarOpen ? '260px' : '10px',
|
left: isSidebarOpen ? '260px' : '10px',
|
||||||
top: '10px',
|
top: '10px',
|
||||||
zIndex: 5,
|
zIndex: 10000,
|
||||||
transition: 'left 0.3s'
|
transition: 'left 0.3s'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isSidebarOpen ? 'Hide Sidebar' : 'Show Sidebar'}
|
{isSidebarOpen ? 'Hide Sidebar' : 'Show Sidebar'}
|
||||||
</button>
|
</Button>
|
||||||
<Sidebar isOpen={isSidebarOpen} availableNodes={availableNodes} addNode={addNode} />
|
<Sidebar isOpen={isSidebarOpen} availableNodes={availableNodes} addNode={addNode} />
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
|
@ -374,7 +376,7 @@ const updateNodesWithExecutionData = (executionData: any[]) => {
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
>
|
>
|
||||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||||
<button onClick={runAgent}>Run Agent</button>
|
<Button onClick={runAgent}>Run Agent</Button>
|
||||||
</div>
|
</div>
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import './modal.css';
|
import './modal.css';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Textarea } from './ui/textarea';
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
@ -22,15 +24,16 @@ const ModalComponent: FC<ModalProps> = ({ isOpen, onClose, onSave, value }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal-overlay">
|
<div className="modal-overlay">
|
||||||
<div className="modal">
|
<div className="modal dark-theme">
|
||||||
<textarea
|
<center><h1>Enter input text</h1></center>
|
||||||
|
<Textarea
|
||||||
className="modal-textarea"
|
className="modal-textarea"
|
||||||
value={tempValue}
|
value={tempValue}
|
||||||
onChange={(e) => setTempValue(e.target.value)}
|
onChange={(e) => setTempValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className="modal-actions">
|
<div className="modal-actions">
|
||||||
<button onClick={onClose}>Cancel</button>
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
<button onClick={handleSave}>Save</button>
|
<Button onClick={handleSave}>Save</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: #2c2c2c;
|
background: #2c2c2c;
|
||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
width: 300px; /* Adjust width for better layout */
|
width: 500px;
|
||||||
box-sizing: border-box; /* Ensure padding doesn't affect overall width */
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-header {
|
.node-header {
|
||||||
|
@ -29,28 +29,38 @@
|
||||||
|
|
||||||
.node-content {
|
.node-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column; /* Use column to stack elements */
|
justify-content: space-between;
|
||||||
gap: 10px;
|
align-items: flex-start;
|
||||||
|
gap: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-section {
|
.input-section {
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.output-section {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.handle-container {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
position: relative;
|
flex: 1;
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.handle-label {
|
.handle-label {
|
||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle-label {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle-container {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-container {
|
.input-container {
|
||||||
|
@ -59,11 +69,26 @@
|
||||||
|
|
||||||
.clickable-input {
|
.clickable-input {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
width: 325px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #555;
|
border: 1px solid #555;
|
||||||
background: #444;
|
background: #444;
|
||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable-input span {
|
||||||
|
display: inline-block;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: calc(100% - 100px);
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-input {
|
.select-input {
|
||||||
|
@ -126,10 +151,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-properties {
|
.node-properties {
|
||||||
margin-top: 20px;
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
background: #444;
|
background: #444;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
width: 325px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
|
@ -183,3 +210,7 @@
|
||||||
.custom-node {
|
.custom-node {
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark-theme {
|
||||||
|
@apply dark:border-gray-800 dark:bg-gray-950;
|
||||||
|
}
|
||||||
|
|
|
@ -105,7 +105,11 @@ input::placeholder, textarea::placeholder {
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar h3 {
|
.sidebar h3 {
|
||||||
margin: 0 0 20px;
|
margin: 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar input {
|
||||||
|
margin: 0 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarNodeRowStyle {
|
.sidebarNodeRowStyle {
|
||||||
|
@ -148,3 +152,7 @@ input::placeholder, textarea::placeholder {
|
||||||
.flow-controls.open {
|
.flow-controls.open {
|
||||||
transform: translateX(350px);
|
transform: translateX(350px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark-theme {
|
||||||
|
@apply dark:border-gray-800 dark:bg-gray-950;
|
||||||
|
}
|
||||||
|
|
|
@ -32,3 +32,7 @@
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark-theme {
|
||||||
|
@apply dark:border-gray-800 dark:bg-gray-950;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
export interface TextareaProps
|
||||||
|
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||||
|
|
||||||
|
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||||
|
({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Textarea.displayName = "Textarea"
|
||||||
|
|
||||||
|
export { Textarea }
|
Loading…
Reference in New Issue