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 './customnode.css';
|
||||
import ModalComponent from './ModalComponent';
|
||||
import { Button } from './ui/button';
|
||||
import { Input } from './ui/input';
|
||||
import { Textarea } from './ui/textarea';
|
||||
|
||||
type Schema = {
|
||||
type: string;
|
||||
|
@ -184,16 +187,16 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
|||
<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 onClick={() => handleInputChange(`${fullKey}.${propKey}`, undefined)} className="array-item-remove">
|
||||
×
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
{key === 'expected_format' && (
|
||||
<div className="nested-input">
|
||||
{keyValuePairs.map((pair, index) => (
|
||||
<div key={index} className="key-value-input">
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Key"
|
||||
value={pair.key}
|
||||
|
@ -205,7 +208,7 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
|||
handleInputChange('expected_format', expectedFormat);
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Value"
|
||||
value={pair.value}
|
||||
|
@ -220,20 +223,20 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
|||
</div>
|
||||
))}
|
||||
<div className="key-value-input">
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Key"
|
||||
value={newKey}
|
||||
onChange={(e) => setNewKey(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Value"
|
||||
value={newValue}
|
||||
onChange={(e) => setNewValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button onClick={handleAddProperty}>Add Property</button>
|
||||
<Button onClick={handleAddProperty}>Add Property</Button>
|
||||
</div>
|
||||
)}
|
||||
{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)}
|
||||
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>
|
||||
))}
|
||||
<button onClick={() => handleInputChange(fullKey, [...arrayValues, ''])} className="array-item-add">
|
||||
<Button onClick={() => handleInputChange(fullKey, [...arrayValues, ''])} className="array-item-add">
|
||||
Add Item
|
||||
</button>
|
||||
</Button>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
|
@ -398,12 +401,12 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
|||
};
|
||||
|
||||
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-title">{data.blockType || data.title}</div>
|
||||
<button onClick={toggleProperties} className="toggle-button">
|
||||
<Button onClick={toggleProperties} className="toggle-button">
|
||||
☰
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="node-content">
|
||||
<div className="input-section">
|
||||
|
@ -442,7 +445,7 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
|||
</p>
|
||||
</div>
|
||||
)}
|
||||
<button onClick={handleSubmit}>Submit</button>
|
||||
<Button onClick={handleSubmit}>Submit</Button>
|
||||
<ModalComponent
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
|
|
|
@ -17,6 +17,8 @@ import CustomNode from './CustomNode';
|
|||
import './flow.css';
|
||||
import AutoGPTServerAPI, { Block, Flow } from '@/lib/autogpt_server_api';
|
||||
import { ObjectSchema } from '@/lib/types';
|
||||
import { Button } from './ui/button';
|
||||
import { Input } from './ui/input';
|
||||
|
||||
type CustomNodeData = {
|
||||
blockType: string;
|
||||
|
@ -43,18 +45,18 @@ const Sidebar: React.FC<{isOpen: boolean, availableNodes: Block[], addNode: (id:
|
|||
);
|
||||
|
||||
return (
|
||||
<div className={`sidebar ${isOpen ? 'open' : ''}`}>
|
||||
<div className={`sidebar dark-theme ${isOpen ? 'open' : ''}`}>
|
||||
<h3>Nodes</h3>
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search nodes..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
{filteredNodes.map((node) => (
|
||||
<div key={node.id} className="sidebarNodeRowStyle">
|
||||
<div key={node.id} className="sidebarNodeRowStyle dark-theme">
|
||||
<span>{node.name}</span>
|
||||
<button onClick={() => addNode(node.id, node.name)}>Add</button>
|
||||
<Button onClick={() => addNode(node.id, node.name)}>Add</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
@ -352,18 +354,18 @@ const updateNodesWithExecutionData = (executionData: any[]) => {
|
|||
|
||||
return (
|
||||
<div style={{ height: '100vh', width: '100%' }}>
|
||||
<button
|
||||
<Button
|
||||
onClick={toggleSidebar}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: isSidebarOpen ? '260px' : '10px',
|
||||
top: '10px',
|
||||
zIndex: 5,
|
||||
zIndex: 10000,
|
||||
transition: 'left 0.3s'
|
||||
}}
|
||||
>
|
||||
{isSidebarOpen ? 'Hide Sidebar' : 'Show Sidebar'}
|
||||
</button>
|
||||
</Button>
|
||||
<Sidebar isOpen={isSidebarOpen} availableNodes={availableNodes} addNode={addNode} />
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
|
@ -374,7 +376,7 @@ const updateNodesWithExecutionData = (executionData: any[]) => {
|
|||
nodeTypes={nodeTypes}
|
||||
>
|
||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||
<button onClick={runAgent}>Run Agent</button>
|
||||
<Button onClick={runAgent}>Run Agent</Button>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React, { FC } from 'react';
|
||||
import './modal.css';
|
||||
import { Button } from './ui/button';
|
||||
import { Textarea } from './ui/textarea';
|
||||
|
||||
interface ModalProps {
|
||||
isOpen: boolean;
|
||||
|
@ -22,15 +24,16 @@ const ModalComponent: FC<ModalProps> = ({ isOpen, onClose, onSave, value }) => {
|
|||
|
||||
return (
|
||||
<div className="modal-overlay">
|
||||
<div className="modal">
|
||||
<textarea
|
||||
<div className="modal dark-theme">
|
||||
<center><h1>Enter input text</h1></center>
|
||||
<Textarea
|
||||
className="modal-textarea"
|
||||
value={tempValue}
|
||||
onChange={(e) => setTempValue(e.target.value)}
|
||||
/>
|
||||
<div className="modal-actions">
|
||||
<button onClick={onClose}>Cancel</button>
|
||||
<button onClick={handleSave}>Save</button>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button onClick={handleSave}>Save</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
border-radius: 12px;
|
||||
background: #2c2c2c;
|
||||
color: #e0e0e0;
|
||||
width: 300px; /* Adjust width for better layout */
|
||||
box-sizing: border-box; /* Ensure padding doesn't affect overall width */
|
||||
width: 500px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.node-header {
|
||||
|
@ -29,28 +29,38 @@
|
|||
|
||||
.node-content {
|
||||
display: flex;
|
||||
flex-direction: column; /* Use column to stack elements */
|
||||
gap: 10px;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.input-section {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.output-section {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.handle-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
margin-bottom: 5px;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.handle-label {
|
||||
color: #e0e0e0;
|
||||
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 {
|
||||
|
@ -59,11 +69,26 @@
|
|||
|
||||
.clickable-input {
|
||||
padding: 5px;
|
||||
width: 325px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #555;
|
||||
background: #444;
|
||||
color: #e0e0e0;
|
||||
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 {
|
||||
|
@ -126,10 +151,12 @@
|
|||
}
|
||||
|
||||
.node-properties {
|
||||
margin-top: 20px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
background: #444;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
width: 325px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
|
@ -183,3 +210,7 @@
|
|||
.custom-node {
|
||||
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 {
|
||||
margin: 0 0 20px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.sidebar input {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.sidebarNodeRowStyle {
|
||||
|
@ -148,3 +152,7 @@ input::placeholder, textarea::placeholder {
|
|||
.flow-controls.open {
|
||||
transform: translateX(350px);
|
||||
}
|
||||
|
||||
.dark-theme {
|
||||
@apply dark:border-gray-800 dark:bg-gray-950;
|
||||
}
|
||||
|
|
|
@ -32,3 +32,7 @@
|
|||
gap: 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