feat(agent_builder): Updates to Builder GUI + Nodes (#7345)

* Updates to Builder GUI + Nodes

* fix apiUrl back to local host
pull/7347/head
Bently 2024-07-08 21:33:07 +01:00 committed by GitHub
parent f9bedb0fd9
commit 1e755f9e8d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 119 additions and 44 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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