Skip to content

Commit

Permalink
Merge branch 'master' into swiftyos/open-1601-paginated-listing-of-st…
Browse files Browse the repository at this point in the history
…ore-agents
  • Loading branch information
ntindle authored Aug 5, 2024
2 parents 8243c06 + 52bd033 commit 99de3b2
Show file tree
Hide file tree
Showing 16 changed files with 649 additions and 102 deletions.
2 changes: 2 additions & 0 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ def setup():
bold=True,
)
)
else:
click.echo(click.style("🎉 Setup completed!\n", fg="green"))


@cli.group()
Expand Down
2 changes: 1 addition & 1 deletion docs/content/server/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This guide will help you setup the server and builder for the project.

<!-- The video is listed in the root Readme.md of the repo -->

We also offer this in video format. You can check it out [here](https://github.com/Significant-Gravitas/AutoGPT#how-to-get-started)
We also offer this in video format. You can check it out [here](https://github.com/Significant-Gravitas/AutoGPT#how-to-get-started).

!!! warning
**DO NOT FOLLOW ANY OUTSIDE TUTORIALS AS THEY WILL LIKELY BE OUT OF DATE**
Expand Down
20 changes: 18 additions & 2 deletions rnd/autogpt_builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@ This is the frontend for AutoGPT's next generation

## Getting Started

First, run the development server:
Run the following installation once.

```bash
npm install
# or
yarn install
# or
pnpm install
# or
bun install
```

Next, run the development server:

```bash
npm run dev
Expand All @@ -18,8 +30,12 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

For subsequent runs, you do not have to `npm install` again. Simply do `npm run dev`.

If the project is updated via git, you will need to `npm install` after each update.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Deploy

TODO
TODO
3 changes: 3 additions & 0 deletions rnd/autogpt_builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@
"dependencies": {
"@hookform/resolvers": "^3.9.0",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
Expand Down
113 changes: 33 additions & 80 deletions rnd/autogpt_builder/src/components/Flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,50 +15,21 @@ import ReactFlow, {
import 'reactflow/dist/style.css';
import CustomNode, { CustomNodeData } from './CustomNode';
import './flow.css';
import AutoGPTServerAPI, { Block, BlockIOSchema, Graph, NodeExecutionResult, ObjectSchema } from '@/lib/autogpt-server-api';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { ChevronRight, ChevronLeft } from "lucide-react";
import AutoGPTServerAPI, { Block, BlockIOSchema, Graph, NodeExecutionResult } from '@/lib/autogpt-server-api';
import { Play, Undo2, Redo2} from "lucide-react";
import { deepEquals, getTypeColor, removeEmptyStringsAndNulls, setNestedProperty } from '@/lib/utils';
import { beautifyString } from '@/lib/utils';
import { history } from './history';
import { CustomEdge, CustomEdgeData } from './CustomEdge';
import ConnectionLine from './ConnectionLine';
import Ajv from 'ajv';
import {Control, ControlPanel} from "@/components/edit/control/ControlPanel";
import {SaveControl} from "@/components/edit/control/SaveControl";
import {BlocksControl} from "@/components/edit/control/BlocksControl";

// This is for the history, this is the minimum distance a block must move before it is logged
// It helps to prevent spamming the history with small movements especially when pressing on a input in a block
const MINIMUM_MOVE_BEFORE_LOG = 50;

const Sidebar: React.FC<{ isOpen: boolean, availableNodes: Block[], addNode: (id: string, name: string) => void }> =
({ isOpen, availableNodes, addNode }) => {
const [searchQuery, setSearchQuery] = useState('');

if (!isOpen) return null;

const filteredNodes = availableNodes.filter(node =>
node.name.toLowerCase().includes(searchQuery.toLowerCase())
);

return (
<div className={`sidebar dark-theme ${isOpen ? 'open' : ''}`}>
<h3>Nodes</h3>
<Input
type="text"
placeholder="Search nodes..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
{filteredNodes.map((node) => (
<div key={node.id} className="sidebarNodeRowStyle dark-theme">
<span>{beautifyString(node.name).replace(/Block$/, '')}</span>
<Button onClick={() => addNode(node.id, node.name)}>Add</Button>
</div>
))}
</div>
);
};

const ajv = new Ajv({ strict: false, allErrors: true });

const FlowEditor: React.FC<{
Expand All @@ -70,7 +41,6 @@ const FlowEditor: React.FC<{
const [edges, setEdges, onEdgesChange] = useEdgesState<CustomEdgeData>([]);
const [nodeId, setNodeId] = useState<number>(1);
const [availableNodes, setAvailableNodes] = useState<Block[]>([]);
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
const [savedAgent, setSavedAgent] = useState<Graph | null>(null);
const [agentDescription, setAgentDescription] = useState<string>('');
const [agentName, setAgentName] = useState<string>('');
Expand Down Expand Up @@ -665,8 +635,6 @@ const FlowEditor: React.FC<{
);
};

const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);

const handleKeyDown = useCallback((event: KeyboardEvent) => {
if (isAnyModalOpen) return; // Prevent copy/paste if any modal is open

Expand Down Expand Up @@ -735,23 +703,26 @@ const FlowEditor: React.FC<{
clearNodesStatusAndOutput();
}, [clearNodesStatusAndOutput]);

const editorControls: Control[] = [
{
label: 'Undo',
icon: <Undo2 />,
onClick: handleUndo,
},
{
label: 'Redo',
icon: <Redo2 />,
onClick: handleRedo,
},
{
label: 'Run',
icon: <Play />,
onClick: runAgent,
}
];

return (
<div className={className}>
<Button
variant="outline"
size="icon"
onClick={toggleSidebar}
style={{
position: 'fixed',
left: isSidebarOpen ? '350px' : '10px',
zIndex: 10000,
backgroundColor: 'black',
color: 'white',
}}
>
{isSidebarOpen ? <ChevronLeft className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
</Button>
<Sidebar isOpen={isSidebarOpen} availableNodes={availableNodes} addNode={addNode} />
<ReactFlow
nodes={nodes.map(node => ({ ...node, data: { ...node.data, setIsAnyModalOpen } }))}
edges={edges.map(edge => ({...edge, data: { ...edge.data, clearNodesStatusAndOutput } }))}
Expand All @@ -767,34 +738,16 @@ const FlowEditor: React.FC<{
onNodeDragStart={onNodesChangeStart}
onNodeDragStop={onNodesChangeEnd}
>
<div style={{ position: 'absolute', right: 10, zIndex: 4 }}>
<Input
type="text"
placeholder="Agent Name"
value={agentName}
onChange={(e) => setAgentName(e.target.value)}
/>
<Input
type="text"
placeholder="Agent Description"
value={agentDescription}
onChange={(e) => setAgentDescription(e.target.value)}
/>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}> {/* Added gap for spacing */}
<Button onClick={() => saveAgent(savedAgent?.is_template)}>
Save {savedAgent?.is_template ? "Template" : "Agent"}
</Button>
{!savedAgent?.is_template &&
<Button onClick={runAgent}>Save & Run Agent</Button>
}
{!savedAgent &&
<Button onClick={() => saveAgent(true)}>Save as Template</Button>
}
<div>
<Button onClick={handleUndo} disabled={!history.canUndo()} style={{ marginRight: '10px' }}>Undo</Button>
<Button onClick={handleRedo} disabled={!history.canRedo()}>Redo</Button>
</div>
</div>
<div className={"flex flex-row absolute z-10 gap-2"}>
<ControlPanel controls={editorControls} >
<BlocksControl blocks={availableNodes} addBlock={addNode} />
<SaveControl
agentMeta={savedAgent}
onSave={saveAgent}
onDescriptionChange={setAgentDescription}
onNameChange={setAgentName}
/>
</ControlPanel>
</div>
</ReactFlow>
</div>
Expand Down
61 changes: 61 additions & 0 deletions rnd/autogpt_builder/src/components/edit/ControlPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {Card, CardContent} from "@/components/ui/card";
import {Tooltip, TooltipContent, TooltipTrigger} from "@/components/ui/tooltip";
import {Button} from "@/components/ui/button";
import {Separator} from "@/components/ui/separator";
import React from "react";

/**
* Represents a control element for the ControlPanel Component.
* @type {Object} Control
* @property {React.ReactNode} icon - The icon of the control from lucide-react https://lucide.dev/icons/
* @property {string} label - The label of the control, to be leveraged by ToolTip.
* @property {onclick} onClick - The function to be executed when the control is clicked.
*/
export type Control = {
icon: React.ReactNode;
label: string;
onClick: () => void;
}

interface ControlPanelProps {
controls: Control[];
children?: React.ReactNode;
}

/**
* ControlPanel component displays a panel with controls as icons with the ability to take in children.
* @param {Object} ControlPanelProps - The properties of the control panel component.
* @param {Array} ControlPanelProps.controls - An array of control objects representing actions to be preformed.
* @param {Array} ControlPanelProps.children - The child components of the control panel.
* @returns The rendered control panel component.
*/
export const ControlPanel= ( {controls, children}: ControlPanelProps) => {
return (
<aside className="hidden w-14 flex-col sm:flex">
<Card>
<CardContent className="p-0">
<div className="flex flex-col items-center gap-4 px-2 sm:py-5 rounded-radius">
{controls.map((control, index) => (
<Tooltip key={index} delayDuration={500}>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={() => control.onClick()}
>
{control.icon}
<span className="sr-only">{control.label}</span>
</Button>
</TooltipTrigger>
<TooltipContent side="right">{control.label}</TooltipContent>
</Tooltip>
))}
<Separator />
{children}
</div>
</CardContent>
</Card>
</aside>
);
}
export default ControlPanel;
83 changes: 83 additions & 0 deletions rnd/autogpt_builder/src/components/edit/control/BlocksControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React, { useState } from "react";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { ToyBrick } from "lucide-react";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { beautifyString } from "@/lib/utils";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Block } from '@/lib/autogpt-server-api';
import { PlusIcon } from "@radix-ui/react-icons";

interface BlocksControlProps {
blocks: Block[];
addBlock: (id: string, name: string) => void;
}

/**
* A React functional component that displays a control for managing blocks.
*
* @component
* @param {Object} BlocksControlProps - The properties for the BlocksControl component.
* @param {Block[]} BlocksControlProps.blocks - An array of blocks to be displayed and filtered.
* @param {(id: string, name: string) => void} BlocksControlProps.addBlock - A function to call when a block is added.
* @returns The rendered BlocksControl component.
*/
export const BlocksControl: React.FC<BlocksControlProps> = ({ blocks, addBlock }) => {
const [searchQuery, setSearchQuery] = useState('');

const filteredBlocks = blocks.filter((block: Block) =>
block.name.toLowerCase().includes(searchQuery.toLowerCase())
);

return (
<Popover>
<PopoverTrigger className="hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-50 dark:text-white">
<ToyBrick className="size-4"/>
</PopoverTrigger>
<PopoverContent side="right" sideOffset={15} align="start" className="w-80 p-0">
<Card className="border-none shadow-none">
<CardHeader className="p-4">
<div className="flex flex-row justify-between items-center">
<Label htmlFor="search-blocks">Blocks</Label>
</div>
<Input
id="search-blocks"
type="text"
placeholder="Search blocks..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</CardHeader>
<CardContent className="p-1">
<ScrollArea className="h-[60vh]">
{filteredBlocks.map((block) => (
<Card
key={block.id}
className="m-2"
>
<div className="flex items-center justify-between m-3">
<div className="flex-1 min-w-0 mr-2">
<span className="font-medium truncate block">{beautifyString(block.name)}</span>
</div>
<div className="flex items-center gap-1 flex-shrink-0">
<Button
variant="ghost"
size="icon"
onClick={() => addBlock(block.id, block.name)}
aria-label="Add block"
>
<PlusIcon />
</Button>
</div>
</div>
</Card>
))}
</ScrollArea>
</CardContent>
</Card>
</PopoverContent>
</Popover>
);
};
Loading

0 comments on commit 99de3b2

Please sign in to comment.