From 6b353269b4bd528f41ea7074a51b20fcf7554031 Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Sun, 23 Apr 2023 13:26:34 -0700 Subject: [PATCH 01/22] implement zustand for messages --- package-lock.json | 34 ++++++++++- package.json | 3 +- src/components/AutonomousAgent.ts | 25 +++++--- src/components/TaskWindow.tsx | 72 ++++++++++++++++++++--- src/components/store/helpers.ts | 17 ++++++ src/components/store/index.ts | 1 + src/components/store/messageStore.ts | 85 +++++++++++++++++++++++++++ src/pages/index.tsx | 42 +++++++++++-- src/server/api/routers/agentRouter.ts | 4 +- src/types/agentTypes.ts | 69 ++++++++++++++++++++-- 10 files changed, 322 insertions(+), 30 deletions(-) create mode 100644 src/components/store/helpers.ts create mode 100644 src/components/store/index.ts create mode 100644 src/components/store/messageStore.ts diff --git a/package-lock.json b/package-lock.json index 953f12ba3f..697a808707 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,8 @@ "stripe": "^12.0.0", "superjson": "1.9.1", "uuid": "^9.0.0", - "zod": "^3.20.2" + "zod": "^3.20.2", + "zustand": "^4.3.7" }, "devDependencies": { "@testing-library/jest-dom": "^5.16.5", @@ -12275,6 +12276,29 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zustand": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.7.tgz", + "integrity": "sha512-dY8ERwB9Nd21ellgkBZFhudER8KVlelZm8388B5nDAXhO/+FZDhYMuRnqDgu5SYyRgz/iaf8RKnbUs/cHfOGlQ==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "immer": ">=9.0", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -21008,6 +21032,14 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==" }, + "zustand": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.7.tgz", + "integrity": "sha512-dY8ERwB9Nd21ellgkBZFhudER8KVlelZm8388B5nDAXhO/+FZDhYMuRnqDgu5SYyRgz/iaf8RKnbUs/cHfOGlQ==", + "requires": { + "use-sync-external-store": "1.2.0" + } + }, "zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 722738631a..c106a7e225 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "stripe": "^12.0.0", "superjson": "1.9.1", "uuid": "^9.0.0", - "zod": "^3.20.2" + "zod": "^3.20.2", + "zustand": "^4.3.7" }, "devDependencies": { "@testing-library/jest-dom": "^5.16.5", diff --git a/src/components/AutonomousAgent.ts b/src/components/AutonomousAgent.ts index c870449342..60d3527b50 100644 --- a/src/components/AutonomousAgent.ts +++ b/src/components/AutonomousAgent.ts @@ -9,8 +9,9 @@ import { import type { Session } from "next-auth"; import type { Message } from "../types/agentTypes"; import { env } from "../env/client.mjs"; -import { v4 } from "uuid"; +import { v4, v1 } from "uuid"; import type { RequestBody } from "../utils/interfaces"; +import { TASK_STATUS_STARTED, MESSAGE_TYPE_TASK } from "../types/agentTypes"; const TIMEOUT_LONG = 1000; const TIMOUT_SHORT = 800; @@ -52,9 +53,14 @@ class AutonomousAgent { // Initialize by getting tasks try { this.tasks = await this.getInitialTasks(); - for (const task of this.tasks) { + for (const value of this.tasks) { await new Promise((r) => setTimeout(r, TIMOUT_SHORT)); - this.sendTaskMessage(task); + this.sendMessage({ + id: v1().toString(), + value, + status: TASK_STATUS_STARTED, + type: MESSAGE_TYPE_TASK, + }); } } catch (e) { console.log(e); @@ -98,6 +104,7 @@ class AutonomousAgent { this.sendThinkingMessage(); const result = await this.executeTask(currentTask as string); + console.log(result); this.sendExecutionMessage(currentTask as string, result); // Wait before adding tasks @@ -111,13 +118,18 @@ class AutonomousAgent { result ); this.tasks = this.tasks.concat(newTasks); - for (const task of newTasks) { + for (const value of newTasks) { await new Promise((r) => setTimeout(r, TIMOUT_SHORT)); - this.sendTaskMessage(task); + this.sendMessage({ + id: v1().toString(), + value, + status: TASK_STATUS_STARTED, + type: MESSAGE_TYPE_TASK, + }); } if (newTasks.length == 0) { - this.sendActionMessage("Task marked as complete!"); + this.sendActionMessage(`${currentTask || "Task"} marked as complete!`); } } catch (e) { console.log(e); @@ -153,7 +165,6 @@ class AutonomousAgent { goal: this.goal, }; const res = await this.post(`/api/agent/start`, data); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument return res.data.newTasks as string[]; } diff --git a/src/components/TaskWindow.tsx b/src/components/TaskWindow.tsx index 1fbc49799e..a855edad27 100644 --- a/src/components/TaskWindow.tsx +++ b/src/components/TaskWindow.tsx @@ -1,13 +1,22 @@ import React from "react"; -import { FaListAlt } from "react-icons/fa"; +import { + FaListAlt, + FaCheckCircle, + FaCircleNotch, + FaPlayCircle, +} from "react-icons/fa"; import FadeIn from "./motions/FadeIn"; import Expand from "./motions/expand"; -import type { Message } from "../types/agentTypes"; +import { + Task, + TASK_STATUS_STARTED, + TASK_STATUS_EXECUTING, + TASK_STATUS_COMPLETED, +} from "../types/agentTypes"; +import { useMessageStore } from "../components/store"; -type TaskWindowProps = { - tasks: Message[]; -}; -export const TaskWindow = ({ tasks }: TaskWindowProps) => { +export const TaskWindow = () => { + const tasks = useMessageStore.use.tasks(); return (
@@ -24,11 +33,56 @@ export const TaskWindow = ({ tasks }: TaskWindowProps) => { ); }; -const Task = ({ task }: { task: Message }) => { +const Task = ({ task }: { task: Task }) => { + const getTaskStatusIcon = (taskStatus: string) => { + const taskStatusIconClass = "mr-1 mb-0.5 inline-block"; + switch (taskStatus) { + case TASK_STATUS_STARTED: + return ( + + ); + break; + case TASK_STATUS_EXECUTING: + return ( + + ); + + break; + case TASK_STATUS_COMPLETED: + return ( + + ); + break; + } + }; + + const getTaskContainerStyle = (taskStatus: string) => { + switch (taskStatus) { + case TASK_STATUS_STARTED: + return "border-dashed border-white/20 text-white"; + break; + case TASK_STATUS_EXECUTING: + return "border-white/20 text-white"; + break; + case TASK_STATUS_COMPLETED: + return "border-green-500 hover:border-green-400 hover:text-green-400 text-green-500"; + break; + default: + return ""; + } + }; + return ( -
- {task.value} +
+ {getTaskStatusIcon("completed")} + {task.value}
); diff --git a/src/components/store/helpers.ts b/src/components/store/helpers.ts new file mode 100644 index 0000000000..1905b76135 --- /dev/null +++ b/src/components/store/helpers.ts @@ -0,0 +1,17 @@ +import type { StoreApi, UseBoundStore } from "zustand"; + +type WithSelectors = S extends { getState: () => infer T } + ? S & { use: { [K in keyof T]: () => T[K] } } + : never; + +export const createSelectors = >>( + _store: S +) => { + const store = _store as WithSelectors; + store.use = {}; + for (const k of Object.keys(store.getState())) { + (store.use as any)[k] = () => store((s) => s[k as keyof typeof s]); + } + + return store; +}; diff --git a/src/components/store/index.ts b/src/components/store/index.ts new file mode 100644 index 0000000000..e6930964f5 --- /dev/null +++ b/src/components/store/index.ts @@ -0,0 +1 @@ +export * from "./messageStore"; diff --git a/src/components/store/messageStore.ts b/src/components/store/messageStore.ts new file mode 100644 index 0000000000..0aca379609 --- /dev/null +++ b/src/components/store/messageStore.ts @@ -0,0 +1,85 @@ +import { createSelectors } from "./helpers"; +import type { StateCreator } from "zustand"; +import { create } from "zustand"; +import type { TaskStatus, Message, Task } from "../../types/agentTypes"; +import { isTask } from "../../types/agentTypes"; + +const resetters: (() => void)[] = []; + +const initialMessageState = { + messages: [], +}; + +interface MessageSlice { + messages: Message[]; + addMessage: (newMessage: Message) => void; +} + +const createMessageSlice: StateCreator< + MessageSlice & TaskSlice, + [], + [], + MessageSlice +> = (set) => { + resetters.push(() => set(initialMessageState)); + return { + ...initialMessageState, + addMessage: (newMessage) => { + set((state) => ({ + ...state, + messages: [...state.messages, newMessage], + tasks: isTask(newMessage) + ? [...state.tasks, newMessage] + : [...state.tasks], + })); + }, + }; +}; + +const initialTaskState = { + tasks: [], +}; + +interface TaskSlice { + tasks: Task[]; + updateTaskStatus: (id: string, newStatus: TaskStatus) => void; +} + +const createTaskSlice: StateCreator< + MessageSlice & TaskSlice, + [], + [], + TaskSlice +> = (set) => { + resetters.push(() => set(initialTaskState)); + return { + ...initialTaskState, + updateTaskStatus: (id, newStatus) => { + set((state) => { + const updatedTasks = state.tasks.map((task) => { + if (task.id === id) { + return { + ...task, + status: newStatus, + }; + } + return task; + }); + + return { + ...state, + tasks: updatedTasks, + }; + }); + }, + }; +}; + +export const useMessageStore = createSelectors( + create()((...a) => ({ + ...createMessageSlice(...a), + ...createTaskSlice(...a), + })) +); + +export const resetAllSlices = () => resetters.forEach((resetter) => resetter()); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 8ccab92022..413e7dbecc 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -20,14 +20,26 @@ import type { Message } from "../types/agentTypes"; import { useAgent } from "../hooks/useAgent"; import { isEmptyOrBlank } from "../utils/whitespace"; import type { ModelSettings } from "../utils/types"; +import { useMessageStore, resetAllSlices } from "../components/store"; +import { + TASK_STATUS_STARTED, + TASK_STATUS_EXECUTING, + TASK_STATUS_COMPLETED, + isTask, +} from "../types/agentTypes"; const Home: NextPage = () => { const { session, status } = useAuth(); const [name, setName] = React.useState(""); const [goalInput, setGoalInput] = React.useState(""); const [agent, setAgent] = React.useState(null); + const messages = useMessageStore.use.messages(); + const tasks = useMessageStore.use.tasks(); + const addMessage = useMessageStore.use.addMessage(); + const updateTaskStatus = useMessageStore.use.updateTaskStatus(); const customSettings = React.useState({ + customApiKey: "", customModelName: GPT_35_TURBO, customTemperature: 0.9, customMaxLoops: DEFAULT_MAX_LOOPS_FREE, @@ -35,7 +47,6 @@ const Home: NextPage = () => { }); const [shouldAgentStop, setShouldAgentStop] = React.useState(false); - const [messages, setMessages] = React.useState([]); const [showHelpDialog, setShowHelpDialog] = React.useState(false); const [showSettingsDialog, setShowSettingsDialog] = React.useState(false); const [hasSaved, setHasSaved] = React.useState(false); @@ -67,10 +78,29 @@ const Home: NextPage = () => { }, [agent]); const handleAddMessage = (message: Message) => { - setMessages((prev) => [...prev, message]); + handleAddTask(message); + if (skipAddMessage(message)) { + return; + } + addMessage(message); }; - const tasks = messages.filter((message) => message.type === "task"); + const handleAddTask = (message: Message) => { + if (!isTask(message)) { + return; + } + + if (isExistingTask(message) && message.id !== undefined) { + updateTaskStatus(message.id, message.status); + } + }; + + const skipAddMessage = (message: Message) => isExistingTask(message); + + const isExistingTask = (message: Message): boolean => + isTask(message) && + (message.status === TASK_STATUS_EXECUTING || + message.status === TASK_STATUS_COMPLETED); const disableDeployAgent = agent != null || isEmptyOrBlank(name) || isEmptyOrBlank(goalInput); @@ -86,7 +116,7 @@ const Home: NextPage = () => { ); setAgent(agent); setHasSaved(false); - setMessages([]); + resetAllSlices(); agent.run().then(console.log).catch(console.error); }; @@ -189,7 +219,7 @@ const Home: NextPage = () => { } scrollToBottom /> - {tasks.length > 0 && } + {tasks.length > 0 && }
@@ -254,7 +284,7 @@ const Home: NextPage = () => { Stopping ) : ( - "Stop agent" + Stop agent )} diff --git a/src/server/api/routers/agentRouter.ts b/src/server/api/routers/agentRouter.ts index 6bd7e047c5..ea3852a3b5 100644 --- a/src/server/api/routers/agentRouter.ts +++ b/src/server/api/routers/agentRouter.ts @@ -2,12 +2,12 @@ import { z } from "zod"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; import { prisma } from "../../db"; -import { messageParser } from "../../../types/agentTypes"; +import { messageSchema } from "../../../types/agentTypes"; const saveAgentParser = z.object({ name: z.string(), goal: z.string(), - tasks: z.array(messageParser), + tasks: z.array(messageSchema), }); export const agentRouter = createTRPCRouter({ diff --git a/src/types/agentTypes.ts b/src/types/agentTypes.ts index 5ba6864e0c..722d3d8da5 100644 --- a/src/types/agentTypes.ts +++ b/src/types/agentTypes.ts @@ -1,9 +1,70 @@ import { z } from "zod"; -export const messageParser = z.object({ - type: z.enum(["goal", "thinking", "task", "action", "system"]), - info: z.string().optional(), +export const [ + MESSAGE_TYPE_GOAL, + MESSAGE_TYPE_THINKING, + MESSAGE_TYPE_TASK, + MESSAGE_TYPE_ACTION, + MESSAGE_TYPE_SYSTEM, +] = [ + "goal" as const, + "thinking" as const, + "task" as const, + "action" as const, + "system" as const, +]; + +export const [ + TASK_STATUS_STARTED, + TASK_STATUS_EXECUTING, + TASK_STATUS_COMPLETED, +] = ["started" as const, "executing" as const, "completed" as const]; + +const TaskStatusSchema = z.union([ + z.literal(TASK_STATUS_STARTED), + z.literal(TASK_STATUS_EXECUTING), + z.literal(TASK_STATUS_COMPLETED), + z.literal(""), +]); + +export type TaskStatus = z.infer; + +export const messageSchemaBase = z.object({ + id: z.string().optional(), value: z.string(), }); -export type Message = z.infer; +export const taskSchema = z + .object({ + type: z.literal(MESSAGE_TYPE_TASK), + status: TaskStatusSchema, + }) + .merge(messageSchemaBase); + +export const nonTaskScehma = z + .object({ + type: z.union([ + z.literal(MESSAGE_TYPE_GOAL), + z.literal(MESSAGE_TYPE_THINKING), + z.literal(MESSAGE_TYPE_TASK), + z.literal(MESSAGE_TYPE_ACTION), + z.literal(MESSAGE_TYPE_SYSTEM), + ]), + info: z.string().optional(), + }) + .merge(messageSchemaBase); + +export const messageSchema = z.union([taskSchema, nonTaskScehma]); + +export type Task = z.infer; +export type Message = z.infer; + +/* Type Predicates */ +export const isTask = (value: unknown): value is Task => { + try { + taskSchema.parse(value); + return true; + } catch (err) { + return false; + } +}; From 4e36a487b99a4b2ab9a46f9d8cf81ba36b157734 Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Sun, 23 Apr 2023 19:33:31 -0700 Subject: [PATCH 02/22] implement live task status update in task window --- src/components/AutonomousAgent.ts | 99 +++++++++++++--------------- src/components/ChatWindow.tsx | 81 ++++++++++++++--------- src/components/TaskWindow.tsx | 64 ++++++++---------- src/components/pdf/PDFButton.tsx | 5 +- src/components/store/messageStore.ts | 35 +++++++--- src/components/utils/helpers.tsx | 25 +++++++ src/pages/index.tsx | 36 +++------- src/types/agentTypes.ts | 18 ++++- 8 files changed, 202 insertions(+), 161 deletions(-) create mode 100644 src/components/utils/helpers.tsx diff --git a/src/components/AutonomousAgent.ts b/src/components/AutonomousAgent.ts index 60d3527b50..43ccfad731 100644 --- a/src/components/AutonomousAgent.ts +++ b/src/components/AutonomousAgent.ts @@ -7,11 +7,19 @@ import { DEFAULT_MAX_LOOPS_PAID, } from "../utils/constants"; import type { Session } from "next-auth"; -import type { Message } from "../types/agentTypes"; import { env } from "../env/client.mjs"; import { v4, v1 } from "uuid"; import type { RequestBody } from "../utils/interfaces"; -import { TASK_STATUS_STARTED, MESSAGE_TYPE_TASK } from "../types/agentTypes"; +import { + TASK_STATUS_STARTED, + TASK_STATUS_EXECUTING, + TASK_STATUS_COMPLETED, + MESSAGE_TYPE_TASK, + MESSAGE_TYPE_GOAL, + MESSAGE_TYPE_THINKING, + MESSAGE_TYPE_SYSTEM, +} from "../types/agentTypes"; +import type { Message, Task } from "../types/agentTypes"; const TIMEOUT_LONG = 1000; const TIMOUT_SHORT = 800; @@ -19,7 +27,7 @@ const TIMOUT_SHORT = 800; class AutonomousAgent { name: string; goal: string; - tasks: string[] = []; + tasks: Message[] = []; completedTasks: string[] = []; modelSettings: ModelSettings; isRunning = true; @@ -50,17 +58,19 @@ class AutonomousAgent { this.sendGoalMessage(); this.sendThinkingMessage(); - // Initialize by getting tasks + // Initialize by getting taskValues try { - this.tasks = await this.getInitialTasks(); - for (const value of this.tasks) { + const taskValues = await this.getInitialTasks(); + for (const value of taskValues) { await new Promise((r) => setTimeout(r, TIMOUT_SHORT)); - this.sendMessage({ - id: v1().toString(), + const task: Task = { + taskId: v1().toString(), value, status: TASK_STATUS_STARTED, type: MESSAGE_TYPE_TASK, - }); + }; + this.sendMessage(task); + this.tasks.push(task); } } catch (e) { console.log(e); @@ -99,13 +109,16 @@ class AutonomousAgent { // Execute first task // Get and remove first task - this.completedTasks.push(this.tasks[0] || ""); - const currentTask = this.tasks.shift(); + this.completedTasks.push(this.tasks[0]?.value || ""); + + const currentTask = this.tasks.shift() as Task; this.sendThinkingMessage(); - const result = await this.executeTask(currentTask as string); - console.log(result); - this.sendExecutionMessage(currentTask as string, result); + const result = await this.executeTask(currentTask.value); + + currentTask.status = TASK_STATUS_EXECUTING; + currentTask.info = result; + this.sendMessage(currentTask); // Wait before adding tasks await new Promise((r) => setTimeout(r, TIMEOUT_LONG)); @@ -113,30 +126,30 @@ class AutonomousAgent { // Add new tasks try { - const newTasks = await this.getAdditionalTasks( - currentTask as string, - result - ); - this.tasks = this.tasks.concat(newTasks); + const newTasks = await this.getAdditionalTasks(currentTask.value, result); for (const value of newTasks) { await new Promise((r) => setTimeout(r, TIMOUT_SHORT)); - this.sendMessage({ - id: v1().toString(), + const task: Task = { + taskId: v1().toString(), value, status: TASK_STATUS_STARTED, type: MESSAGE_TYPE_TASK, - }); + }; + this.tasks.push(task); + this.sendMessage(task); } if (newTasks.length == 0) { - this.sendActionMessage(`${currentTask || "Task"} marked as complete!`); + currentTask.status = TASK_STATUS_COMPLETED; + this.sendMessage(currentTask); } } catch (e) { console.log(e); this.sendErrorMessage( `ERROR adding additional task(s). It might have been against our model's policies to run them. Continuing.` ); - this.sendActionMessage("Task marked as complete."); + currentTask.status = TASK_STATUS_COMPLETED; + this.sendMessage(currentTask); } await this.loop(); @@ -173,11 +186,13 @@ class AutonomousAgent { currentTask: string, result: string ): Promise { + const taskValues = this.tasks.map((task) => task.value); + if (this.shouldRunClientSide()) { return await AgentService.createTasksAgent( this.modelSettings, this.goal, - this.tasks, + taskValues, currentTask, result, this.completedTasks @@ -187,7 +202,7 @@ class AutonomousAgent { const data = { modelSettings: this.modelSettings, goal: this.goal, - tasks: this.tasks, + tasks: taskValues, lastTask: currentTask, result: result, completedTasks: this.completedTasks, @@ -248,12 +263,12 @@ class AutonomousAgent { } sendGoalMessage() { - this.sendMessage({ type: "goal", value: this.goal }); + this.sendMessage({ type: MESSAGE_TYPE_GOAL, value: this.goal }); } sendLoopMessage() { this.sendMessage({ - type: "system", + type: MESSAGE_TYPE_SYSTEM, value: this.modelSettings.customApiKey !== "" ? `This agent has maxed out on loops. To save your wallet, this agent is shutting down. You can configure the number of loops in the advanced settings.` @@ -263,44 +278,24 @@ class AutonomousAgent { sendManualShutdownMessage() { this.sendMessage({ - type: "system", + type: MESSAGE_TYPE_SYSTEM, value: `The agent has been manually shutdown.`, }); } sendCompletedMessage() { this.sendMessage({ - type: "system", + type: MESSAGE_TYPE_SYSTEM, value: "All tasks completed. Shutting down.", }); } sendThinkingMessage() { - this.sendMessage({ type: "thinking", value: "" }); - } - - sendTaskMessage(task: string) { - this.sendMessage({ type: "task", value: task }); + this.sendMessage({ type: MESSAGE_TYPE_THINKING, value: "" }); } sendErrorMessage(error: string) { - this.sendMessage({ type: "system", value: error }); - } - - sendExecutionMessage(task: string, execution: string) { - this.sendMessage({ - type: "action", - info: `Executing "${task}"`, - value: execution, - }); - } - - sendActionMessage(message: string) { - this.sendMessage({ - type: "action", - info: message, - value: "", - }); + this.sendMessage({ type: MESSAGE_TYPE_SYSTEM, value: error }); } } diff --git a/src/components/ChatWindow.tsx b/src/components/ChatWindow.tsx index 182250a7e0..9c0e85530a 100644 --- a/src/components/ChatWindow.tsx +++ b/src/components/ChatWindow.tsx @@ -4,10 +4,10 @@ import { FaBrain, FaClipboard, FaCopy, - FaDatabase, + FaCheckCircle, FaImage, - FaListAlt, FaPlayCircle, + FaThumbtack, FaSave, FaStar, } from "react-icons/fa"; @@ -25,7 +25,18 @@ import PDFButton from "./pdf/PDFButton"; import FadeIn from "./motions/FadeIn"; import Menu from "./Menu"; import type { Message } from "../types/agentTypes"; +import { + isAction, + getTaskStatus, + MESSAGE_TYPE_GOAL, + MESSAGE_TYPE_THINKING, + MESSAGE_TYPE_SYSTEM, + TASK_STATUS_STARTED, + TASK_STATUS_EXECUTING, + TASK_STATUS_COMPLETED, +} from "../types/agentTypes"; import clsx from "clsx"; +import { getMessageContainerStyle } from "./utils/helpers"; interface ChatWindowProps extends HeaderProps { children?: ReactNode; @@ -82,7 +93,7 @@ const ChatWindow = ({ className={clsx( "mb-2 mr-2 ", (fullscreen && "max-h-[75vh] flex-grow overflow-auto") || - "window-heights" + "window-heights" )} ref={scrollRef} onScroll={handleScroll} @@ -100,7 +111,7 @@ const ChatWindow = ({ Create an agent by adding a name / goal, and hitting deploy!", }} @@ -109,7 +120,7 @@ const ChatWindow = ({ { } }; - const exportOptions = [ { return (
setShowCopy(true)} onMouseLeave={() => setShowCopy(false)} onClick={handleCopyClick} > - {message.type != "system" && ( + {message.type != MESSAGE_TYPE_SYSTEM && ( // Avoid for system messages as they do not have an icon and will cause a weird space <>
@@ -285,19 +297,19 @@ const ChatMessage = ({ message }: { message: Message }) => { )} - {message.type == "thinking" && ( + {message.type == MESSAGE_TYPE_THINKING && ( (Restart if this takes more than 30 seconds) )} - {message.type == "action" ? ( + {isAction(message) ? (
- {message.value} + {message.info || ""}
) : ( @@ -311,8 +323,9 @@ const ChatMessage = ({ message }: { message: Message }) => { ) : ( @@ -347,29 +360,35 @@ const DonationMessage = () => { }; const getMessageIcon = (message: Message) => { - switch (message.type) { - case "goal": - return ; - case "task": - return ; - case "thinking": - return ; - case "action": - return ; + if (message.type === MESSAGE_TYPE_GOAL) { + return ; + } else if (message.type === MESSAGE_TYPE_THINKING) { + return ; + } else if (getTaskStatus(message) === TASK_STATUS_STARTED) { + return ; + } else if (getTaskStatus(message) === TASK_STATUS_EXECUTING) { + return ; + } else if (getTaskStatus(message) === TASK_STATUS_COMPLETED) { + return ; } + + return; }; const getMessagePrefix = (message: Message) => { - switch (message.type) { - case "goal": - return "Embarking on a new goal:"; - case "task": - return "Added task:"; - case "thinking": - return "Thinking..."; - case "action": - return message.info ? message.info : "Executing:"; + if (message.type === MESSAGE_TYPE_GOAL) { + return "Embarking on a new goal:"; + } else if (message.type === MESSAGE_TYPE_THINKING) { + return "Thinking..."; + } else if (getTaskStatus(message) === TASK_STATUS_STARTED) { + return "Added task:"; + } else if (getTaskStatus(message) === TASK_STATUS_EXECUTING) { + return `Executing: ${message.value}`; + } else if (getTaskStatus(message) === TASK_STATUS_COMPLETED) { + return "Completed: "; } + + return ""; }; export default ChatWindow; diff --git a/src/components/TaskWindow.tsx b/src/components/TaskWindow.tsx index a855edad27..3a22517bf1 100644 --- a/src/components/TaskWindow.tsx +++ b/src/components/TaskWindow.tsx @@ -1,10 +1,4 @@ import React from "react"; -import { - FaListAlt, - FaCheckCircle, - FaCircleNotch, - FaPlayCircle, -} from "react-icons/fa"; import FadeIn from "./motions/FadeIn"; import Expand from "./motions/expand"; import { @@ -13,9 +7,17 @@ import { TASK_STATUS_EXECUTING, TASK_STATUS_COMPLETED, } from "../types/agentTypes"; +import { getMessageContainerStyle } from "./utils/helpers"; import { useMessageStore } from "../components/store"; +import { + FaListAlt, + FaCheckCircle, + FaCircleNotch, + FaThumbtack, + FaStopCircle, +} from "react-icons/fa"; -export const TaskWindow = () => { +export const TaskWindow = ({ isAgentStopped }: { isAgentStopped: boolean }) => { const tasks = useMessageStore.use.tasks(); return ( @@ -25,7 +27,7 @@ export const TaskWindow = () => {
{tasks.map((task, i) => ( - + ))}
@@ -33,55 +35,41 @@ export const TaskWindow = () => { ); }; -const Task = ({ task }: { task: Task }) => { +const Task = ({ + task, + isAgentStopped, +}: { + task: Task; + isAgentStopped: boolean; +}) => { const getTaskStatusIcon = (taskStatus: string) => { const taskStatusIconClass = "mr-1 mb-0.5 inline-block"; switch (taskStatus) { case TASK_STATUS_STARTED: - return ( - - ); - break; + return ; case TASK_STATUS_EXECUTING: - return ( - + return isAgentStopped ? ( + + ) : ( + ); - - break; case TASK_STATUS_COMPLETED: return ( ); - break; - } - }; - - const getTaskContainerStyle = (taskStatus: string) => { - switch (taskStatus) { - case TASK_STATUS_STARTED: - return "border-dashed border-white/20 text-white"; - break; - case TASK_STATUS_EXECUTING: - return "border-white/20 text-white"; - break; - case TASK_STATUS_COMPLETED: - return "border-green-500 hover:border-green-400 hover:text-green-400 text-green-500"; - break; - default: - return ""; } }; return (
- {getTaskStatusIcon("completed")} + {getTaskStatusIcon(task.status)} {task.value}
diff --git a/src/components/pdf/PDFButton.tsx b/src/components/pdf/PDFButton.tsx index f14f3cd0bd..6877a07ce8 100644 --- a/src/components/pdf/PDFButton.tsx +++ b/src/components/pdf/PDFButton.tsx @@ -4,6 +4,7 @@ import { pdf } from "@react-pdf/renderer"; import React, { memo } from "react"; import MyDocument from "./MyDocument"; import type { Message } from "../../types/agentTypes"; +import { MESSAGE_TYPE_GOAL, MESSAGE_TYPE_TASK } from "../../types/agentTypes"; const PDFButton = ({ messages, @@ -42,10 +43,10 @@ const getContent = (messages: Message[]): string => { // Note "Thinking" messages have no `value` so they show up as new lines return messages .map((message) => { - if (message.type == "goal") { + if (message.type == MESSAGE_TYPE_GOAL) { return `Goal: ${message.value}`; } - if (message.type == "task") { + if (message.type == MESSAGE_TYPE_TASK) { return `Adding Task: ${message.value}`; } return message.value; diff --git a/src/components/store/messageStore.ts b/src/components/store/messageStore.ts index 0aca379609..ff6c1b3783 100644 --- a/src/components/store/messageStore.ts +++ b/src/components/store/messageStore.ts @@ -1,8 +1,17 @@ import { createSelectors } from "./helpers"; import type { StateCreator } from "zustand"; import { create } from "zustand"; -import type { TaskStatus, Message, Task } from "../../types/agentTypes"; -import { isTask } from "../../types/agentTypes"; +import type { Message, Task } from "../../types/agentTypes"; +import { + isTask, + TASK_STATUS_EXECUTING, + TASK_STATUS_COMPLETED, +} from "../../types/agentTypes"; + +const isExistingTask = (message: Message): boolean => + isTask(message) && + (message.status === TASK_STATUS_EXECUTING || + message.status === TASK_STATUS_COMPLETED); const resetters: (() => void)[] = []; @@ -25,12 +34,15 @@ const createMessageSlice: StateCreator< return { ...initialMessageState, addMessage: (newMessage) => { + const newTask = { ...newMessage }; + newMessage = { ...newMessage }; set((state) => ({ ...state, messages: [...state.messages, newMessage], - tasks: isTask(newMessage) - ? [...state.tasks, newMessage] - : [...state.tasks], + tasks: + isTask(newTask) && !isExistingTask(newTask) + ? [...state.tasks, newTask] + : [...state.tasks], })); }, }; @@ -42,7 +54,7 @@ const initialTaskState = { interface TaskSlice { tasks: Task[]; - updateTaskStatus: (id: string, newStatus: TaskStatus) => void; + updateTaskStatus: (updatedTask: Task) => void; } const createTaskSlice: StateCreator< @@ -54,13 +66,20 @@ const createTaskSlice: StateCreator< resetters.push(() => set(initialTaskState)); return { ...initialTaskState, - updateTaskStatus: (id, newStatus) => { + updateTaskStatus: (updatedTask) => { + const { taskId, info, status: newStatus } = updatedTask; + + if (!isExistingTask(updatedTask) || taskId === undefined) { + return; + } + set((state) => { const updatedTasks = state.tasks.map((task) => { - if (task.id === id) { + if (task.taskId === taskId) { return { ...task, status: newStatus, + info, }; } return task; diff --git a/src/components/utils/helpers.tsx b/src/components/utils/helpers.tsx new file mode 100644 index 0000000000..6c6dddf785 --- /dev/null +++ b/src/components/utils/helpers.tsx @@ -0,0 +1,25 @@ +import { + isTask, + TASK_STATUS_STARTED, + TASK_STATUS_EXECUTING, + TASK_STATUS_COMPLETED, +} from "../../types/agentTypes"; + +import type { Message } from "../../types/agentTypes"; + +export const getMessageContainerStyle = (message: Message) => { + if (!isTask(message)) { + return "border-white/10"; + } + + switch (message.status) { + case TASK_STATUS_STARTED: + return "border-white/20 text-white"; + case TASK_STATUS_EXECUTING: + return "border-white/20 text-white"; + case TASK_STATUS_COMPLETED: + return "border-green-500 hover:border-green-400 hover:text-green-400 text-green-500"; + default: + return ""; + } +}; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 413e7dbecc..fa2e759f26 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -21,12 +21,7 @@ import { useAgent } from "../hooks/useAgent"; import { isEmptyOrBlank } from "../utils/whitespace"; import type { ModelSettings } from "../utils/types"; import { useMessageStore, resetAllSlices } from "../components/store"; -import { - TASK_STATUS_STARTED, - TASK_STATUS_EXECUTING, - TASK_STATUS_COMPLETED, - isTask, -} from "../types/agentTypes"; +import { isTask } from "../types/agentTypes"; const Home: NextPage = () => { const { session, status } = useAuth(); @@ -78,30 +73,13 @@ const Home: NextPage = () => { }, [agent]); const handleAddMessage = (message: Message) => { - handleAddTask(message); - if (skipAddMessage(message)) { - return; + if (isTask(message)) { + updateTaskStatus(message); } - addMessage(message); - }; - const handleAddTask = (message: Message) => { - if (!isTask(message)) { - return; - } - - if (isExistingTask(message) && message.id !== undefined) { - updateTaskStatus(message.id, message.status); - } + addMessage(message); }; - const skipAddMessage = (message: Message) => isExistingTask(message); - - const isExistingTask = (message: Message): boolean => - isTask(message) && - (message.status === TASK_STATUS_EXECUTING || - message.status === TASK_STATUS_COMPLETED); - const disableDeployAgent = agent != null || isEmptyOrBlank(name) || isEmptyOrBlank(goalInput); @@ -219,7 +197,11 @@ const Home: NextPage = () => { } scrollToBottom /> - {tasks.length > 0 && } + {tasks.length > 0 && ( + + )}
diff --git a/src/types/agentTypes.ts b/src/types/agentTypes.ts index 722d3d8da5..98eb370b4d 100644 --- a/src/types/agentTypes.ts +++ b/src/types/agentTypes.ts @@ -30,12 +30,13 @@ const TaskStatusSchema = z.union([ export type TaskStatus = z.infer; export const messageSchemaBase = z.object({ - id: z.string().optional(), value: z.string(), + info: z.string().optional(), }); export const taskSchema = z .object({ + taskId: z.string(), type: z.literal(MESSAGE_TYPE_TASK), status: TaskStatusSchema, }) @@ -46,11 +47,9 @@ export const nonTaskScehma = z type: z.union([ z.literal(MESSAGE_TYPE_GOAL), z.literal(MESSAGE_TYPE_THINKING), - z.literal(MESSAGE_TYPE_TASK), z.literal(MESSAGE_TYPE_ACTION), z.literal(MESSAGE_TYPE_SYSTEM), ]), - info: z.string().optional(), }) .merge(messageSchemaBase); @@ -68,3 +67,16 @@ export const isTask = (value: unknown): value is Task => { return false; } }; + +/* Helper Functions */ +export const isAction = (value: unknown): boolean => { + return isTask(value) && value.status === TASK_STATUS_EXECUTING; +}; + +export const getTaskStatus = (value: unknown): string | undefined => { + if (!isTask(value)) { + return; + } + + return value.status; +}; From f7dcc7357e8515a2444d84384eb5617f3c8f1a38 Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Sun, 23 Apr 2023 19:56:38 -0700 Subject: [PATCH 03/22] revert unintended changes --- public/locales/pt/translation.json | 72 ++++++++++++++++++++++++++++++ src/pages/index.tsx | 18 +++----- 2 files changed, 78 insertions(+), 12 deletions(-) create mode 100644 public/locales/pt/translation.json diff --git a/public/locales/pt/translation.json b/public/locales/pt/translation.json new file mode 100644 index 0000000000..9976d9d8cd --- /dev/null +++ b/public/locales/pt/translation.json @@ -0,0 +1,72 @@ +{ + "> Create an agent by adding a name / goal, and hitting deploy!": "> Crie um agente adicionando um nome/objetivo e clicando em implantar!", + "YOU_CAN_PROVIDE_YOUR_OWN_OPENAI_KEY": "Você pode fornecer sua própria chave da API OpenAI na guia de configurações para aumentar os limites!", + "Save": "Salvar", + "Image": "Imagem", + "Copy": "Copiar", + "HELP_SUPPORT_THE_ADVANCEMENT_OF_AGENTGPT": "Ajude a apoiar o avanço do AgentGPT.", + "Please consider sponsoring the project on GitHub.": "Considere patrocinar o projeto no GitHub.", + "Support now 🚀": "Apoie agora 🚀", + "Embarking on a new goal:": "Embarcando em um novo objetivo:", + "Added task:": "Tarefa adicionada:", + "Thinking...": "Pensando...", + "Executing:": "Executando:", + "Help": "Ajuda", + "Settings": "Configurações", + "Sign Out": "Sair", + "Sign In": "Entrar", + "Account": "Conta", + "Go Pro": "Ir Pro", + "Welcome to AgentGPT 🤖": "Bem-vindo ao AgentGPT 🤖", + "allows you to configure and deploy Autonomous AI agents. Name your custom AI and have it embark on any goal imaginable. It will attempt to reach the goal by thinking of tasks to do, executing them, and learning from the results 🚀": "permite que você configure e implante agentes de AI autônomos. Nomeie sua IA personalizada e a faça embarcar em qualquer objetivo imaginável. Ela tentará alcançar o objetivo pensando em tarefas a serem realizadas, executando-as e aprendendo com os resultados 🚀", + "This platform is currently in beta, we are currently working on:": "Esta plataforma está atualmente em beta, estamos trabalhando em:", + "Long term memory 🧠": "Memória de longo prazo 🧠", + "Web browsing 🌐": "Navegação na Web 🌐", + "Interaction with websites and people 👨‍👩‍👦": "Interação com sites e pessoas 👨‍👩‍👦", + "Follow the journey below:": "Siga a jornada abaixo:", + "Key is invalid, please ensure that you have set up billing in your OpenAI account!": "Chave inválida, certifique-se de ter configurado a cobrança em sua conta OpenAI!", + "Higher values will make the output more random, while lower values make the output more focused and deterministic.": "Valores mais altos tornarão a saída mais aleatória, enquanto valores mais baixos tornarão a saída mais focada e determinística.", + "Controls the maximum number of loops that the agent will run (higher value will make more API calls).": "Controla o número máximo de loops que o agente irá executar (valores mais altos farão mais chamadas à API).", + "Settings ⚙": "Configurações ⚙", + "Here you can add your OpenAI API key. This will require you to pay for your own OpenAI usage but give you greater access to AgentGPT! You can additionally select any model OpenAI offers.": "Aqui você pode adicionar sua chave API da OpenAI. Isso exigirá que você pague pelo seu próprio uso da OpenAI, mas lhe dará maior acesso ao AgentGPT! Você também pode selecionar qualquer modelo que a OpenAI oferece.", + "To use the GPT-4 model, you need to also provide the API key for GPT-4. You can request for it": "Para usar o modelo GPT-4, você também precisa fornecer a chave API para o GPT-4. Você pode solicitá-la", + "here": "aqui", + "(ChatGPT Plus subscription will not work)": "(A assinatura ChatGPT Plus não funcionará)", + "Model: ": "Modelo: ", + "Key: ": "Chave: ", + "Advanced Settings": "Configurações Avançadas", + "NOTE: To get a key, sign up for an OpenAI account and visit the following": "NOTA: Para obter uma chave, faça uma inscrição em uma conta OpenAI e visite o seguinte", + "link": "link", + "This key is only used in the current browser session": "Esta chave é usada apenas na sessão atual do navegador", + "Beta 🚀": "Beta 🚀", + "Assemble, configure, and deploy autonomous AI Agents in your browser.": "Monte, configure e implante Agentes de IA autônomos em seu navegador.", + "Name:": "Nome:", + "Goal:": "Objetivo:", + "Deploy Agent": "Implantar Agente", + "Running": "Executando", + "Stopping": "Parando", + "Stop Agent": "Parar Agente", + "Make the world a better place.": "Faça do mundo um lugar melhor.", + "Select language": "Selecione o idioma", + "Task marked as complete.": "Tarefa marcada como concluída.", + "ERROR adding additional task(s). It might have been against our model's policies to run them. Continuing.": "ERRO ao adicionar tarefa(s) adicional(is). Pode ter violado as políticas do nosso modelo para executá-las. Continuando.", + "Rate limit exceeded. Please slow down. 😅": "Limite de taxa excedido. Por favor, diminua a velocidade. 😅", + "This agent has maxed out on loops. To save your wallet, this agent is shutting down. You can configure the number of loops in the advanced settings.": "Este agente atingiu o máximo de loops permitido. Para economizar seu dinheiro, este agente está desligando. Você pode configurar o número de loops nas configurações avançadas.", + "We're sorry, because this is a demo, we cannot have our agents running for too long. Note, if you desire longer runs, please provide your own API key in Settings. Shutting down.": "Lamentamos, mas como isso é uma demonstração, nossos agentes não podem executar por muito tempo. Se você deseja execuções mais longas, por favor, forneça sua própria chave de API nas configurações. Desligando.", + "The agent has been manually shutdown.": "O agente foi desligado manualmente.", + "All tasks completed. Shutting down.": "Todas as tarefas concluídas. Desligando.", + "ERROR accessing OpenAI APIs. Please check your API key or try again later": "ERRO ao acessar as APIs da OpenAI. Verifique sua chave de API ou tente novamente mais tarde.", + "ERROR using your OpenAI API key. You've exceeded your current quota, please check your plan and billing details.": "ERRO ao usar sua chave de API da OpenAI. Você excedeu sua cota atual, por favor, verifique seu plano e detalhes de faturamento.", + "ERROR your API key does not have GPT-4 access. You must first join OpenAI's wait-list. (This is different from ChatGPT Plus)": "ERRO sua chave de API não tem acesso ao GPT-4. Você deve primeiro se inscrever na lista de espera da OpenAI. (Isso é diferente do ChatGPT Plus)", + "ERROR retrieving initial tasks array. Retry, make your goal more clear, or revise your goal such that it is within our model's policies to run. Shutting Down.": "ERRO ao recuperar a matriz de tarefas iniciais. Tente novamente, torne seu objetivo mais claro ou revise-o de forma que esteja dentro das políticas do nosso modelo para executá-lo. Desligando.", + "Close": "Fechar", + "Sign in to be able to save agents and manage your account!": "Faça login para salvar agentes e gerenciar sua conta!", + "You need to create and save your first agent before anything shows up here!": "Você precisa criar e salvar seu primeiro agente antes que algo apareça aqui!", + "Current tasks": "Tarefas atuais", + "Too many requests, please try again later.": "Muitas solicitações, tente novamente mais tarde.", + "Copied to clipboard! 🚀": "Copiado para a área de transferência! 🚀", + "Agent's Language:": "Idioma do agente:", + "Export": "Exportar", + "Goal: ": "Objetivo: ", + "Adding Task: ": "Adicionando Tarefa: " +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index c552d8b23f..660fc08cd9 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -14,6 +14,7 @@ import AutonomousAgent from "../components/AutonomousAgent"; import Expand from "../components/motions/expand"; import HelpDialog from "../components/HelpDialog"; import { SettingsDialog } from "../components/SettingsDialog"; +import { GPT_35_TURBO, DEFAULT_MAX_LOOPS_FREE } from "../utils/constants"; import { TaskWindow } from "../components/TaskWindow"; import { useAuth } from "../hooks/useAuth"; import type { Message } from "../types/agentTypes"; @@ -27,23 +28,16 @@ import { useSettings } from "../hooks/useSettings"; const Home: NextPage = () => { const [t] = useTranslation(); - const { session, status } = useAuth(); - const [name, setName] = React.useState(""); - const [goalInput, setGoalInput] = React.useState(""); - const [agent, setAgent] = React.useState(null); + // zustand states const messages = useMessageStore.use.messages(); const tasks = useMessageStore.use.tasks(); const addMessage = useMessageStore.use.addMessage(); const updateTaskStatus = useMessageStore.use.updateTaskStatus(); - const customSettings = React.useState({ - customApiKey: "", - customModelName: GPT_35_TURBO, - customTemperature: 0.9, - customMaxLoops: DEFAULT_MAX_LOOPS_FREE, - maxTokens: 400, - }); - + const { session, status } = useAuth(); + const [name, setName] = React.useState(""); + const [goalInput, setGoalInput] = React.useState(""); + const [agent, setAgent] = React.useState(null); const { settings, saveSettings } = useSettings(); const [shouldAgentStop, setShouldAgentStop] = React.useState(false); const [showHelpDialog, setShowHelpDialog] = React.useState(false); From 285568773e8cea17fffa86ddbc979ec8be7f33de Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Sun, 23 Apr 2023 20:01:14 -0700 Subject: [PATCH 04/22] revert unintended changes --- src/pages/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 660fc08cd9..39f13ad867 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -14,13 +14,11 @@ import AutonomousAgent from "../components/AutonomousAgent"; import Expand from "../components/motions/expand"; import HelpDialog from "../components/HelpDialog"; import { SettingsDialog } from "../components/SettingsDialog"; -import { GPT_35_TURBO, DEFAULT_MAX_LOOPS_FREE } from "../utils/constants"; import { TaskWindow } from "../components/TaskWindow"; import { useAuth } from "../hooks/useAuth"; import type { Message } from "../types/agentTypes"; import { useAgent } from "../hooks/useAgent"; import { isEmptyOrBlank } from "../utils/whitespace"; -import type { ModelSettings } from "../utils/types"; import { useMessageStore, resetAllSlices } from "../components/store"; import { isTask } from "../types/agentTypes"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; From 7d8f9ab98c47723964cc283200b0c1d58a2845ce Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Mon, 24 Apr 2023 18:17:15 -0700 Subject: [PATCH 05/22] updated task states --- src/components/AutonomousAgent.ts | 9 ++-- src/components/ChatWindow.tsx | 65 ++++++++++++---------------- src/components/TaskWindow.tsx | 39 ++--------------- src/components/store/helpers.ts | 5 +++ src/components/store/messageStore.ts | 4 +- src/components/utils/helpers.tsx | 49 +++++++++++++++++++++ src/pages/index.tsx | 7 +-- src/types/agentTypes.ts | 9 +++- 8 files changed, 106 insertions(+), 81 deletions(-) diff --git a/src/components/AutonomousAgent.ts b/src/components/AutonomousAgent.ts index 7ba3713506..0a7face5c1 100644 --- a/src/components/AutonomousAgent.ts +++ b/src/components/AutonomousAgent.ts @@ -14,6 +14,7 @@ import { TASK_STATUS_STARTED, TASK_STATUS_EXECUTING, TASK_STATUS_COMPLETED, + TASK_STATUS_FINAL, MESSAGE_TYPE_TASK, MESSAGE_TYPE_GOAL, MESSAGE_TYPE_THINKING, @@ -113,10 +114,12 @@ class AutonomousAgent { const currentTask = this.tasks.shift() as Task; this.sendThinkingMessage(); + currentTask.status = TASK_STATUS_EXECUTING; + this.sendMessage(currentTask); const result = await this.executeTask(currentTask.value); - currentTask.status = TASK_STATUS_EXECUTING; + currentTask.status = TASK_STATUS_COMPLETED; currentTask.info = result; this.sendMessage(currentTask); @@ -140,7 +143,7 @@ class AutonomousAgent { } if (newTasks.length == 0) { - currentTask.status = TASK_STATUS_COMPLETED; + currentTask.status = TASK_STATUS_FINAL; this.sendMessage(currentTask); } } catch (e) { @@ -148,7 +151,7 @@ class AutonomousAgent { this.sendErrorMessage( `ERROR adding additional task(s). It might have been against our model's policies to run them. Continuing.` ); - currentTask.status = TASK_STATUS_COMPLETED; + currentTask.status = TASK_STATUS_FINAL; this.sendMessage(currentTask); } diff --git a/src/components/ChatWindow.tsx b/src/components/ChatWindow.tsx index 3e5491b606..ee4905193d 100644 --- a/src/components/ChatWindow.tsx +++ b/src/components/ChatWindow.tsx @@ -1,17 +1,7 @@ import type { ReactNode } from "react"; import React, { useEffect, useRef, useState } from "react"; import { useTranslation } from "next-i18next"; -import { - FaBrain, - FaClipboard, - FaCopy, - FaCheckCircle, - FaImage, - FaPlayCircle, - FaThumbtack, - FaSave, - FaStar, -} from "react-icons/fa"; +import { FaClipboard, FaCopy, FaImage, FaSave } from "react-icons/fa"; import PopIn from "./motions/popin"; import Expand from "./motions/expand"; import * as htmlToImage from "html-to-image"; @@ -35,9 +25,10 @@ import { TASK_STATUS_STARTED, TASK_STATUS_EXECUTING, TASK_STATUS_COMPLETED, + TASK_STATUS_FINAL, } from "../types/agentTypes"; import clsx from "clsx"; -import { getMessageContainerStyle } from "./utils/helpers"; +import { getMessageContainerStyle, getTaskStatusIcon } from "./utils/helpers"; interface ChatWindowProps extends HeaderProps { children?: ReactNode; @@ -45,6 +36,7 @@ interface ChatWindowProps extends HeaderProps { showDonation: boolean; fullscreen?: boolean; scrollToBottom?: boolean; + isAgentStopped: boolean; } const messageListId = "chat-window-message-list"; @@ -58,6 +50,7 @@ const ChatWindow = ({ onSave, fullscreen, scrollToBottom, + isAgentStopped, }: ChatWindowProps) => { const [t] = useTranslation(); const [hasUserScrolled, setHasUserScrolled] = useState(false); @@ -98,11 +91,17 @@ const ChatWindow = ({ onScroll={handleScroll} id={messageListId} > - {messages.map((message, index) => ( - - - - ))} + {messages.map((message, index) => { + if (getTaskStatus(message) === TASK_STATUS_EXECUTING) { + return null; + } + + return ( + + + + ); + })} {children} {messages.length === 0 && ( @@ -258,7 +257,13 @@ const MacWindowHeader = (props: HeaderProps) => {
); }; -const ChatMessage = ({ message }: { message: Message }) => { +const ChatMessage = ({ + message, + isAgentStopped, +}: { + message: Message; + isAgentStopped: boolean; +}) => { const [t] = useTranslation(); const [showCopy, setShowCopy] = useState(false); const [copied, setCopied] = useState(false); @@ -292,7 +297,7 @@ const ChatMessage = ({ message }: { message: Message }) => { // Avoid for system messages as they do not have an icon and will cause a weird space <>
- {getMessageIcon(message)} + {getTaskStatusIcon(message, { isAgentStopped })}
{getMessagePrefix(message)} @@ -361,22 +366,6 @@ const DonationMessage = () => { ); }; -const getMessageIcon = (message: Message) => { - if (message.type === MESSAGE_TYPE_GOAL) { - return ; - } else if (message.type === MESSAGE_TYPE_THINKING) { - return ; - } else if (getTaskStatus(message) === TASK_STATUS_STARTED) { - return ; - } else if (getTaskStatus(message) === TASK_STATUS_EXECUTING) { - return ; - } else if (getTaskStatus(message) === TASK_STATUS_COMPLETED) { - return ; - } - - return; -}; - const getMessagePrefix = (message: Message) => { const [t] = useTranslation(); if (message.type === MESSAGE_TYPE_GOAL) { @@ -385,10 +374,10 @@ const getMessagePrefix = (message: Message) => { return t("Thinking..."); } else if (getTaskStatus(message) === TASK_STATUS_STARTED) { return t("Added task:"); - } else if (getTaskStatus(message) === TASK_STATUS_EXECUTING) { - return `${t("Executing:")} ${message.value}`; } else if (getTaskStatus(message) === TASK_STATUS_COMPLETED) { - return t("Completed: "); + return `${t("Executing:")} ${message.value}`; + } else if (getTaskStatus(message) === TASK_STATUS_FINAL) { + return t("No more subtasks for: "); } return ""; }; diff --git a/src/components/TaskWindow.tsx b/src/components/TaskWindow.tsx index 69926f4be0..022559bbad 100644 --- a/src/components/TaskWindow.tsx +++ b/src/components/TaskWindow.tsx @@ -1,21 +1,10 @@ import React from "react"; import FadeIn from "./motions/FadeIn"; import Expand from "./motions/expand"; -import { - Task, - TASK_STATUS_STARTED, - TASK_STATUS_EXECUTING, - TASK_STATUS_COMPLETED, -} from "../types/agentTypes"; -import { getMessageContainerStyle } from "./utils/helpers"; +import { Task } from "../types/agentTypes"; +import { getMessageContainerStyle, getTaskStatusIcon } from "./utils/helpers"; import { useMessageStore } from "../components/store"; -import { - FaListAlt, - FaCheckCircle, - FaCircleNotch, - FaThumbtack, - FaStopCircle, -} from "react-icons/fa"; +import { FaListAlt } from "react-icons/fa"; import { useTranslation } from "react-i18next"; export const TaskWindow = ({ isAgentStopped }: { isAgentStopped: boolean }) => { @@ -44,26 +33,6 @@ const Task = ({ task: Task; isAgentStopped: boolean; }) => { - const getTaskStatusIcon = (taskStatus: string) => { - const taskStatusIconClass = "mr-1 mb-0.5 inline-block"; - switch (taskStatus) { - case TASK_STATUS_STARTED: - return ; - case TASK_STATUS_EXECUTING: - return isAgentStopped ? ( - - ) : ( - - ); - case TASK_STATUS_COMPLETED: - return ( - - ); - } - }; - return (
- {getTaskStatusIcon(task.status)} + {getTaskStatusIcon(task, { isAgentStopped })} {task.value}
diff --git a/src/components/store/helpers.ts b/src/components/store/helpers.ts index 1905b76135..79812592f5 100644 --- a/src/components/store/helpers.ts +++ b/src/components/store/helpers.ts @@ -1,5 +1,10 @@ import type { StoreApi, UseBoundStore } from "zustand"; +/* + Automatically creates selectors for each states in store. + Zustand recommends using selectors for calling state/actions for optimal performance + Reference: https://docs.pmnd.rs/zustand/guides/auto-generating-selectors +*/ type WithSelectors = S extends { getState: () => infer T } ? S & { use: { [K in keyof T]: () => T[K] } } : never; diff --git a/src/components/store/messageStore.ts b/src/components/store/messageStore.ts index ff6c1b3783..2985225ab4 100644 --- a/src/components/store/messageStore.ts +++ b/src/components/store/messageStore.ts @@ -6,12 +6,14 @@ import { isTask, TASK_STATUS_EXECUTING, TASK_STATUS_COMPLETED, + TASK_STATUS_FINAL, } from "../../types/agentTypes"; const isExistingTask = (message: Message): boolean => isTask(message) && (message.status === TASK_STATUS_EXECUTING || - message.status === TASK_STATUS_COMPLETED); + message.status === TASK_STATUS_COMPLETED || + message.status === TASK_STATUS_FINAL); const resetters: (() => void)[] = []; diff --git a/src/components/utils/helpers.tsx b/src/components/utils/helpers.tsx index 6c6dddf785..6ec0704bf3 100644 --- a/src/components/utils/helpers.tsx +++ b/src/components/utils/helpers.tsx @@ -1,8 +1,21 @@ +import { + FaBrain, + FaCircleNotch, + FaFlagCheckered, + FaCheckCircle, + FaStar, + FaStopCircle, + FaThumbtack, +} from "react-icons/fa"; import { isTask, TASK_STATUS_STARTED, TASK_STATUS_EXECUTING, TASK_STATUS_COMPLETED, + TASK_STATUS_FINAL, + MESSAGE_TYPE_GOAL, + MESSAGE_TYPE_THINKING, + getTaskStatus, } from "../../types/agentTypes"; import type { Message } from "../../types/agentTypes"; @@ -19,7 +32,43 @@ export const getMessageContainerStyle = (message: Message) => { return "border-white/20 text-white"; case TASK_STATUS_COMPLETED: return "border-green-500 hover:border-green-400 hover:text-green-400 text-green-500"; + case TASK_STATUS_FINAL: + return "border-green-500 hover:border-green-400 hover:text-green-400 text-green-500"; default: return ""; } }; + +export const getTaskStatusIcon = ( + message: Message, + config: { [key: string]: string | boolean } +) => { + const taskStatusIconClass = "mr-1 mb-0.5 inline-block"; + const { isAgentStopped } = config; + + if (message.type === MESSAGE_TYPE_GOAL) { + return ; + } else if (message.type === MESSAGE_TYPE_THINKING) { + return ; + } else if (getTaskStatus(message) === TASK_STATUS_STARTED) { + return ; + } else if (getTaskStatus(message) === TASK_STATUS_EXECUTING) { + return isAgentStopped ? ( + + ) : ( + + ); + } else if (getTaskStatus(message) === TASK_STATUS_COMPLETED) { + return ( + + ); + } else if (getTaskStatus(message) === TASK_STATUS_FINAL) { + return ( + + ); + } +}; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 39f13ad867..a8dc2f99d7 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -79,6 +79,8 @@ const Home: NextPage = () => { const disableDeployAgent = agent != null || isEmptyOrBlank(name) || isEmptyOrBlank(goalInput); + const isAgentStopped = () => !agent?.isRunning || agent === null; + const handleNewGoal = () => { const agent = new AutonomousAgent( name.trim(), @@ -193,11 +195,10 @@ const Home: NextPage = () => { : undefined } scrollToBottom + isAgentStopped={isAgentStopped()} /> {tasks.length > 0 && ( - + )} diff --git a/src/types/agentTypes.ts b/src/types/agentTypes.ts index 98eb370b4d..20ccb7a5df 100644 --- a/src/types/agentTypes.ts +++ b/src/types/agentTypes.ts @@ -18,12 +18,19 @@ export const [ TASK_STATUS_STARTED, TASK_STATUS_EXECUTING, TASK_STATUS_COMPLETED, -] = ["started" as const, "executing" as const, "completed" as const]; + TASK_STATUS_FINAL, +] = [ + "started" as const, + "executing" as const, + "completed" as const, + "final" as const, +]; const TaskStatusSchema = z.union([ z.literal(TASK_STATUS_STARTED), z.literal(TASK_STATUS_EXECUTING), z.literal(TASK_STATUS_COMPLETED), + z.literal(TASK_STATUS_FINAL), z.literal(""), ]); From 30e77ff4775154bd10963cfa59e28fa5d34c2085 Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Mon, 24 Apr 2023 18:30:11 -0700 Subject: [PATCH 06/22] remove isAction check --- src/components/ChatWindow.tsx | 3 +-- src/types/agentTypes.ts | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/ChatWindow.tsx b/src/components/ChatWindow.tsx index ee4905193d..bfb5d4851c 100644 --- a/src/components/ChatWindow.tsx +++ b/src/components/ChatWindow.tsx @@ -17,7 +17,6 @@ import FadeIn from "./motions/FadeIn"; import Menu from "./Menu"; import type { Message } from "../types/agentTypes"; import { - isAction, getTaskStatus, MESSAGE_TYPE_GOAL, MESSAGE_TYPE_THINKING, @@ -309,7 +308,7 @@ const ChatMessage = ({ )} - {isAction(message) ? ( + {message.info ? (
{ }; /* Helper Functions */ -export const isAction = (value: unknown): boolean => { - return isTask(value) && value.status === TASK_STATUS_EXECUTING; -}; - export const getTaskStatus = (value: unknown): string | undefined => { if (!isTask(value)) { return; From 8da0cb1dd7e238a47d47798bd1247b0d4593041c Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Mon, 24 Apr 2023 22:35:11 -0700 Subject: [PATCH 07/22] fix issues --- src/components/ChatWindow.tsx | 24 +++++++++++++----------- src/components/TaskWindow.tsx | 2 +- src/components/utils/helpers.tsx | 14 +++++++------- src/types/agentTypes.ts | 4 ++++ 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/components/ChatWindow.tsx b/src/components/ChatWindow.tsx index fa68714364..cb12ecb927 100644 --- a/src/components/ChatWindow.tsx +++ b/src/components/ChatWindow.tsx @@ -17,6 +17,7 @@ import FadeIn from "./motions/FadeIn"; import Menu from "./Menu"; import type { Message } from "../types/agentTypes"; import { + isAction, getTaskStatus, MESSAGE_TYPE_GOAL, MESSAGE_TYPE_THINKING, @@ -36,7 +37,7 @@ interface ChatWindowProps extends HeaderProps { showDonation: boolean; fullscreen?: boolean; scrollToBottom?: boolean; - isAgentStopped: boolean; + isAgentStopped?: boolean; } const messageListId = "chat-window-message-list"; @@ -309,7 +310,7 @@ const ChatMessage = ({ )} - {message.info ? ( + {isAction(message) ? (
{ }; const getMessagePrefix = (message: Message, t: Translation) => { - switch (message.type) { - case "goal": - return t("Embarking on a new goal:"); - case "task": - return t("Added task:"); - case "thinking": - return t("Thinking..."); - case "action": - return message.info ? message.info : t("Executing:"); + if (message.type === MESSAGE_TYPE_GOAL) { + return t("Embarking on a new goal:"); + } else if (message.type === MESSAGE_TYPE_THINKING) { + return t("Thinking..."); + } else if (getTaskStatus(message) === TASK_STATUS_STARTED) { + return t("Added task:"); + } else if (getTaskStatus(message) === TASK_STATUS_COMPLETED) { + return `Completing: ${message.value}`; + } else if (getTaskStatus(message) === TASK_STATUS_FINAL) { + return t("No more subtasks for:"); } return ""; }; diff --git a/src/components/TaskWindow.tsx b/src/components/TaskWindow.tsx index 022559bbad..f1c3a30aa9 100644 --- a/src/components/TaskWindow.tsx +++ b/src/components/TaskWindow.tsx @@ -36,7 +36,7 @@ const Task = ({ return (
diff --git a/src/components/utils/helpers.tsx b/src/components/utils/helpers.tsx index 6ec0704bf3..834479e30a 100644 --- a/src/components/utils/helpers.tsx +++ b/src/components/utils/helpers.tsx @@ -1,7 +1,7 @@ import { FaBrain, FaCircleNotch, - FaFlagCheckered, + FaRegCheckCircle, FaCheckCircle, FaStar, FaStopCircle, @@ -22,14 +22,14 @@ import type { Message } from "../../types/agentTypes"; export const getMessageContainerStyle = (message: Message) => { if (!isTask(message)) { - return "border-white/10"; + return "border-white/10 hover:border-white/40"; } switch (message.status) { case TASK_STATUS_STARTED: - return "border-white/20 text-white"; + return "border-white/20 text-white hover:border-white/40"; case TASK_STATUS_EXECUTING: - return "border-white/20 text-white"; + return "border-white/20 text-white hover:border-white/40"; case TASK_STATUS_COMPLETED: return "border-green-500 hover:border-green-400 hover:text-green-400 text-green-500"; case TASK_STATUS_FINAL: @@ -41,7 +41,7 @@ export const getMessageContainerStyle = (message: Message) => { export const getTaskStatusIcon = ( message: Message, - config: { [key: string]: string | boolean } + config: { [key: string]: string | boolean | undefined } ) => { const taskStatusIconClass = "mr-1 mb-0.5 inline-block"; const { isAgentStopped } = config; @@ -60,13 +60,13 @@ export const getTaskStatusIcon = ( ); } else if (getTaskStatus(message) === TASK_STATUS_COMPLETED) { return ( - ); } else if (getTaskStatus(message) === TASK_STATUS_FINAL) { return ( - ); diff --git a/src/types/agentTypes.ts b/src/types/agentTypes.ts index e7fb31c508..fa492d07b2 100644 --- a/src/types/agentTypes.ts +++ b/src/types/agentTypes.ts @@ -83,3 +83,7 @@ export const getTaskStatus = (value: unknown): string | undefined => { return value.status; }; + +export const isAction = (value: unknown): boolean => { + return isTask(value) && value.status === TASK_STATUS_COMPLETED; +}; From acd105d6d5c93110550b5382d3a4e104cda1b0a4 Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Mon, 24 Apr 2023 23:19:55 -0700 Subject: [PATCH 08/22] remove green text color for completed and final tasks --- src/components/TaskWindow.tsx | 2 +- src/components/utils/helpers.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/TaskWindow.tsx b/src/components/TaskWindow.tsx index f1c3a30aa9..ed5ef59b89 100644 --- a/src/components/TaskWindow.tsx +++ b/src/components/TaskWindow.tsx @@ -36,7 +36,7 @@ const Task = ({ return (
diff --git a/src/components/utils/helpers.tsx b/src/components/utils/helpers.tsx index 834479e30a..9f029e3c34 100644 --- a/src/components/utils/helpers.tsx +++ b/src/components/utils/helpers.tsx @@ -27,13 +27,13 @@ export const getMessageContainerStyle = (message: Message) => { switch (message.status) { case TASK_STATUS_STARTED: - return "border-white/20 text-white hover:border-white/40"; + return "border-white/20 hover:border-white/40"; case TASK_STATUS_EXECUTING: - return "border-white/20 text-white hover:border-white/40"; + return "border-white/20 hover:border-white/40"; case TASK_STATUS_COMPLETED: - return "border-green-500 hover:border-green-400 hover:text-green-400 text-green-500"; + return "border-green-500 hover:border-green-400"; case TASK_STATUS_FINAL: - return "border-green-500 hover:border-green-400 hover:text-green-400 text-green-500"; + return "border-green-500 hover:border-green-400"; default: return ""; } @@ -43,7 +43,7 @@ export const getTaskStatusIcon = ( message: Message, config: { [key: string]: string | boolean | undefined } ) => { - const taskStatusIconClass = "mr-1 mb-0.5 inline-block"; + const taskStatusIconClass = "mr-1 mb-1 inline-block"; const { isAgentStopped } = config; if (message.type === MESSAGE_TYPE_GOAL) { From 1561d2ee44fa8d84375e6b8f6ea7cf9811bed681 Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Wed, 26 Apr 2023 10:41:00 -0700 Subject: [PATCH 09/22] Empty-Commit From 05687e3889d2cac19b60fcec419226958239c006 Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Wed, 26 Apr 2023 11:19:22 -0700 Subject: [PATCH 10/22] update AgentTask to include status field --- prisma/schema.prisma | 1 + src/server/api/routers/agentRouter.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 42a0844bbc..1a78adccdb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -84,6 +84,7 @@ model AgentTask { id String @id @default(cuid()) agentId String type String + status String value String @db.Text info String? @db.Text sort Int diff --git a/src/server/api/routers/agentRouter.ts b/src/server/api/routers/agentRouter.ts index ea3852a3b5..1422a39adc 100644 --- a/src/server/api/routers/agentRouter.ts +++ b/src/server/api/routers/agentRouter.ts @@ -27,6 +27,7 @@ export const agentRouter = createTRPCRouter({ data: { agentId: agent.id, type: e.type, + status: e.status, info: e.info, value: e.value, sort: i, From b6facf605e23eb665c5759e4aa30835baba3d4ed Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Wed, 26 Apr 2023 11:30:00 -0700 Subject: [PATCH 11/22] update agentRouter --- src/server/api/routers/agentRouter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/api/routers/agentRouter.ts b/src/server/api/routers/agentRouter.ts index 1422a39adc..f937836617 100644 --- a/src/server/api/routers/agentRouter.ts +++ b/src/server/api/routers/agentRouter.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; import { prisma } from "../../db"; -import { messageSchema } from "../../../types/agentTypes"; +import { messageSchema, MESSAGE_TYPE_TASK } from "../../../types/agentTypes"; const saveAgentParser = z.object({ name: z.string(), @@ -27,7 +27,7 @@ export const agentRouter = createTRPCRouter({ data: { agentId: agent.id, type: e.type, - status: e.status, + ...(e.type === MESSAGE_TYPE_TASK && { status: e.status }), info: e.info, value: e.value, sort: i, From 50a384fa9ce4c7452dee225555ee2fb0ee8dae5b Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Wed, 26 Apr 2023 12:58:16 -0700 Subject: [PATCH 12/22] fix saved message rendering error --- src/types/agentTypes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/agentTypes.ts b/src/types/agentTypes.ts index fa492d07b2..f89e1b68d2 100644 --- a/src/types/agentTypes.ts +++ b/src/types/agentTypes.ts @@ -38,12 +38,12 @@ export type TaskStatus = z.infer; export const messageSchemaBase = z.object({ value: z.string(), - info: z.string().optional(), + info: z.string().optional().nullable(), }); export const taskSchema = z .object({ - taskId: z.string(), + taskId: z.string().optional(), type: z.literal(MESSAGE_TYPE_TASK), status: TaskStatusSchema, }) From bd84241a6ed5ab99279375cfee9fcae538e85228 Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Wed, 26 Apr 2023 12:58:53 -0700 Subject: [PATCH 13/22] fix saved message rendering error --- prisma/schema.prisma | 2 +- src/components/ChatWindow.tsx | 2 -- src/pages/agent/index.tsx | 6 +++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1a78adccdb..f5b3433351 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -84,7 +84,7 @@ model AgentTask { id String @id @default(cuid()) agentId String type String - status String + status String? value String @db.Text info String? @db.Text sort Int diff --git a/src/components/ChatWindow.tsx b/src/components/ChatWindow.tsx index 4ec16af955..d208d85340 100644 --- a/src/components/ChatWindow.tsx +++ b/src/components/ChatWindow.tsx @@ -57,8 +57,6 @@ const ChatWindow = ({ const [hasUserScrolled, setHasUserScrolled] = useState(false); const scrollRef = useRef(null); - console.log(messages); - const handleScroll = (event: React.UIEvent) => { const { scrollTop, scrollHeight, clientHeight } = event.currentTarget; diff --git a/src/pages/agent/index.tsx b/src/pages/agent/index.tsx index addedd9457..0ca469ac09 100644 --- a/src/pages/agent/index.tsx +++ b/src/pages/agent/index.tsx @@ -11,10 +11,10 @@ import Toast from "../../components/toast"; import { FaTrash, FaShare, FaBackspace } from "react-icons/fa"; import { env } from "../../env/client.mjs"; -import { useTranslation } from 'react-i18next'; +import { useTranslation } from "react-i18next"; const AgentPage: NextPage = () => { - const [ t ] = useTranslation(); + const [t] = useTranslation(); const [showCopied, setShowCopied] = useState(false); const router = useRouter(); @@ -75,7 +75,7 @@ const AgentPage: NextPage = () => {
From 77d6a8b7121b07669d57c7de2c16423ea719d011 Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Thu, 27 Apr 2023 20:20:15 -0700 Subject: [PATCH 14/22] implement agentStore --- src/components/ChatWindow.tsx | 8 ++--- src/components/TaskWindow.tsx | 14 +++----- src/components/store/agentStore.ts | 39 +++++++++++++++++++++ src/components/store/index.ts | 1 + src/components/store/messageStore.ts | 3 +- src/pages/index.tsx | 51 ++++++++++++++++------------ src/types/agentTypes.ts | 15 ++++++++ 7 files changed, 93 insertions(+), 38 deletions(-) create mode 100644 src/components/store/agentStore.ts diff --git a/src/components/ChatWindow.tsx b/src/components/ChatWindow.tsx index d208d85340..f362d84eab 100644 --- a/src/components/ChatWindow.tsx +++ b/src/components/ChatWindow.tsx @@ -37,7 +37,6 @@ interface ChatWindowProps extends HeaderProps { showDonation: boolean; fullscreen?: boolean; scrollToBottom?: boolean; - isAgentStopped?: boolean; } const messageListId = "chat-window-message-list"; @@ -51,7 +50,6 @@ const ChatWindow = ({ onSave, fullscreen, scrollToBottom, - isAgentStopped, }: ChatWindowProps) => { const [t] = useTranslation(); const [hasUserScrolled, setHasUserScrolled] = useState(false); @@ -99,7 +97,7 @@ const ChatWindow = ({ return ( - + ); })} @@ -269,12 +267,10 @@ const MacWindowHeader = (props: HeaderProps) => { }; const ChatMessage = ({ message, - isAgentStopped, className, }: { message: Message; className?: string; - isAgentStopped?: boolean; }) => { const [t] = useTranslation(); const [showCopy, setShowCopy] = useState(false); @@ -309,7 +305,7 @@ const ChatMessage = ({ // Avoid for system messages as they do not have an icon and will cause a weird space <>
- {getTaskStatusIcon(message, { isAgentStopped })} + {getTaskStatusIcon(message, {})}
{getMessagePrefix(message, t)} diff --git a/src/components/TaskWindow.tsx b/src/components/TaskWindow.tsx index ed5ef59b89..f07479a38a 100644 --- a/src/components/TaskWindow.tsx +++ b/src/components/TaskWindow.tsx @@ -6,8 +6,9 @@ import { getMessageContainerStyle, getTaskStatusIcon } from "./utils/helpers"; import { useMessageStore } from "../components/store"; import { FaListAlt } from "react-icons/fa"; import { useTranslation } from "react-i18next"; +import { useAgentStore } from "../components/store"; -export const TaskWindow = ({ isAgentStopped }: { isAgentStopped: boolean }) => { +export const TaskWindow = () => { const tasks = useMessageStore.use.tasks(); const [t] = useTranslation(); return ( @@ -18,7 +19,7 @@ export const TaskWindow = ({ isAgentStopped }: { isAgentStopped: boolean }) => {
{tasks.map((task, i) => ( - + ))}
@@ -26,13 +27,8 @@ export const TaskWindow = ({ isAgentStopped }: { isAgentStopped: boolean }) => { ); }; -const Task = ({ - task, - isAgentStopped, -}: { - task: Task; - isAgentStopped: boolean; -}) => { +const Task = ({ task }: { task: Task }) => { + const isAgentStopped = useAgentStore.use.isAgentStopped(); return (
void; + setAgent: (newAgent: AutonomousAgent | null) => void; +} + +const createAgentSlice: StateCreator = (set, get) => { + return { + ...initialAgentState, + setIsAgentStopped: () => { + set((state) => ({ + isAgentStopped: !state.agent?.isRunning, + })); + }, + setAgent: (newAgent) => { + console.log("newAgent: ", newAgent); + set(() => ({ + agent: newAgent, + })); + }, + }; +}; + +export const useAgentStore = createSelectors( + create()((...a) => ({ + ...createAgentSlice(...a), + })) +); diff --git a/src/components/store/index.ts b/src/components/store/index.ts index e6930964f5..790c98888e 100644 --- a/src/components/store/index.ts +++ b/src/components/store/index.ts @@ -1 +1,2 @@ export * from "./messageStore"; +export * from "./agentStore"; diff --git a/src/components/store/messageStore.ts b/src/components/store/messageStore.ts index 2985225ab4..f3596f936e 100644 --- a/src/components/store/messageStore.ts +++ b/src/components/store/messageStore.ts @@ -103,4 +103,5 @@ export const useMessageStore = createSelectors( })) ); -export const resetAllSlices = () => resetters.forEach((resetter) => resetter()); +export const resetAllMessageSlices = () => + resetters.forEach((resetter) => resetter()); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 28b232cde1..ea8aa25e96 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -19,25 +19,35 @@ import { useAuth } from "../hooks/useAuth"; import type { Message } from "../types/agentTypes"; import { useAgent } from "../hooks/useAgent"; import { isEmptyOrBlank } from "../utils/whitespace"; -import { useMessageStore, resetAllSlices } from "../components/store"; +import { + useMessageStore, + useAgentStore, + resetAllMessageSlices, +} from "../components/store"; import { isTask } from "../types/agentTypes"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { useSettings } from "../hooks/useSettings"; const Home: NextPage = () => { const [t] = useTranslation(); - // zustand states + // zustand states with state dependencies + const addMessage = useMessageStore.use.addMessage(); const messages = useMessageStore.use.messages(); const tasks = useMessageStore.use.tasks(); - const addMessage = useMessageStore.use.addMessage(); const updateTaskStatus = useMessageStore.use.updateTaskStatus(); + const setAgent = useAgentStore.use.setAgent(); + const isAgentStopped = useAgentStore.use.isAgentStopped(); + const setIsAgentStopped = useAgentStore.use.setIsAgentStopped(); + const agent = useAgentStore.use.agent(); + const { session, status } = useAuth(); const [name, setName] = React.useState(""); const [goalInput, setGoalInput] = React.useState(""); - const [agent, setAgent] = React.useState(null); + // const [agent, setAgent] = React.useState(null); const settingsModel = useSettings(); - const [shouldAgentStop, setShouldAgentStop] = React.useState(false); + // const [shouldAgentStop, setShouldAgentStop] = React.useState(false); + const [showHelpDialog, setShowHelpDialog] = React.useState(false); const [showSettingsDialog, setShowSettingsDialog] = React.useState(false); const [hasSaved, setHasSaved] = React.useState(false); @@ -63,10 +73,8 @@ const Home: NextPage = () => { }, []); useEffect(() => { - if (agent == null) { - setShouldAgentStop(false); - } - }, [agent]); + setIsAgentStopped(); + }, [agent, setIsAgentStopped]); const handleAddMessage = (message: Message) => { if (isTask(message)) { @@ -79,10 +87,10 @@ const Home: NextPage = () => { const disableDeployAgent = agent != null || isEmptyOrBlank(name) || isEmptyOrBlank(goalInput); - const isAgentStopped = () => !agent?.isRunning || agent === null; + // const isAgentStopped = () => !agent?.isRunning || agent === null; const handleNewGoal = () => { - const agent = new AutonomousAgent( + const newAgent = new AutonomousAgent( name.trim(), goalInput.trim(), handleAddMessage, @@ -90,10 +98,10 @@ const Home: NextPage = () => { settingsModel.settings, session ?? undefined ); - setAgent(agent); + setAgent(newAgent); setHasSaved(false); - resetAllSlices(); - agent.run().then(console.log).catch(console.error); + resetAllMessageSlices(); + newAgent?.run().then(console.log).catch(console.error); }; const handleKeyPress = ( @@ -110,8 +118,9 @@ const Home: NextPage = () => { }; const handleStopAgent = () => { - setShouldAgentStop(true); + // setShouldAgentStop(true); agent?.stopAgent(); + setIsAgentStopped(); }; const proTitle = ( @@ -122,7 +131,7 @@ const Home: NextPage = () => { const shouldShowSave = status === "authenticated" && - !agent?.isRunning && + isAgentStopped && messages.length && !hasSaved; @@ -195,11 +204,9 @@ const Home: NextPage = () => { : undefined } scrollToBottom - isAgentStopped={isAgentStopped()} + // isAgentStopped={isAgentStopped()} /> - {tasks.length > 0 && ( - - )} + {tasks.length > 0 && }
@@ -253,12 +260,12 @@ const Home: NextPage = () => { )}
From 444b1c13cab866aaaa8eadfca4314caa175c59b1 Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Thu, 27 Apr 2023 20:27:17 -0700 Subject: [PATCH 16/22] Revert "fix saved message rendering error" This reverts commit 50a384fa9ce4c7452dee225555ee2fb0ee8dae5b. --- src/types/agentTypes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/agentTypes.ts b/src/types/agentTypes.ts index 35e9b88f2e..378c240322 100644 --- a/src/types/agentTypes.ts +++ b/src/types/agentTypes.ts @@ -39,12 +39,12 @@ export type TaskStatus = z.infer; export const messageSchemaBase = z.object({ value: z.string(), - info: z.string().optional().nullable(), + info: z.string().optional(), }); export const taskSchema = z .object({ - taskId: z.string().optional(), + taskId: z.string(), type: z.literal(MESSAGE_TYPE_TASK), status: TaskStatusSchema, }) From e418eabffcc3fed3f9853c0f6b113440017035fd Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Thu, 27 Apr 2023 20:33:15 -0700 Subject: [PATCH 17/22] clean up comments --- src/pages/index.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index ea8aa25e96..05aa6b6deb 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -44,7 +44,6 @@ const Home: NextPage = () => { const { session, status } = useAuth(); const [name, setName] = React.useState(""); const [goalInput, setGoalInput] = React.useState(""); - // const [agent, setAgent] = React.useState(null); const settingsModel = useSettings(); // const [shouldAgentStop, setShouldAgentStop] = React.useState(false); @@ -87,8 +86,6 @@ const Home: NextPage = () => { const disableDeployAgent = agent != null || isEmptyOrBlank(name) || isEmptyOrBlank(goalInput); - // const isAgentStopped = () => !agent?.isRunning || agent === null; - const handleNewGoal = () => { const newAgent = new AutonomousAgent( name.trim(), @@ -118,7 +115,6 @@ const Home: NextPage = () => { }; const handleStopAgent = () => { - // setShouldAgentStop(true); agent?.stopAgent(); setIsAgentStopped(); }; @@ -204,7 +200,6 @@ const Home: NextPage = () => { : undefined } scrollToBottom - // isAgentStopped={isAgentStopped()} /> {tasks.length > 0 && } @@ -265,7 +260,7 @@ const Home: NextPage = () => { className="sm:mt-10" enabledClassName={"bg-red-600 hover:bg-red-400"} > - {!isAgentStopped ? ( + {!isAgentStopped && agent === null ? ( <> {t("Stopping")} From 6a73ad19a5eea679eee8e8f49656b37163f2d8c2 Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Thu, 27 Apr 2023 20:35:23 -0700 Subject: [PATCH 18/22] remove changes from other PR --- prisma/schema.prisma | 1 - 1 file changed, 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1a78adccdb..42a0844bbc 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -84,7 +84,6 @@ model AgentTask { id String @id @default(cuid()) agentId String type String - status String value String @db.Text info String? @db.Text sort Int From 040239283a7444cb0bbd19aee8e8280aef4299b0 Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Thu, 27 Apr 2023 20:51:47 -0700 Subject: [PATCH 19/22] change directory from store to stores --- src/components/Accordion.tsx | 2 +- src/components/TaskWindow.tsx | 4 ++-- src/components/{store => stores}/agentStore.ts | 2 +- src/components/{store => stores}/helpers.ts | 0 src/components/{store => stores}/index.ts | 0 src/components/{store => stores}/messageStore.ts | 0 src/pages/index.tsx | 2 +- 7 files changed, 5 insertions(+), 5 deletions(-) rename src/components/{store => stores}/agentStore.ts (92%) rename src/components/{store => stores}/helpers.ts (100%) rename src/components/{store => stores}/index.ts (100%) rename src/components/{store => stores}/messageStore.ts (100%) diff --git a/src/components/Accordion.tsx b/src/components/Accordion.tsx index 2e221b6284..badcffc181 100644 --- a/src/components/Accordion.tsx +++ b/src/components/Accordion.tsx @@ -11,7 +11,7 @@ const Accordion = ({ child, name }: AccordionProps) => { {({ open }) => ( <> - + {name} { const tasks = useMessageStore.use.tasks(); diff --git a/src/components/store/agentStore.ts b/src/components/stores/agentStore.ts similarity index 92% rename from src/components/store/agentStore.ts rename to src/components/stores/agentStore.ts index 547108ccca..64c7af3476 100644 --- a/src/components/store/agentStore.ts +++ b/src/components/stores/agentStore.ts @@ -1,7 +1,7 @@ import { createSelectors } from "./helpers"; import type { StateCreator } from "zustand"; import { create } from "zustand"; -import type AutonomousAgent from "../../components/AutonomousAgent"; +import type AutonomousAgent from "../AutonomousAgent"; const initialAgentState = { agent: null, diff --git a/src/components/store/helpers.ts b/src/components/stores/helpers.ts similarity index 100% rename from src/components/store/helpers.ts rename to src/components/stores/helpers.ts diff --git a/src/components/store/index.ts b/src/components/stores/index.ts similarity index 100% rename from src/components/store/index.ts rename to src/components/stores/index.ts diff --git a/src/components/store/messageStore.ts b/src/components/stores/messageStore.ts similarity index 100% rename from src/components/store/messageStore.ts rename to src/components/stores/messageStore.ts diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 05aa6b6deb..04333a659a 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -23,7 +23,7 @@ import { useMessageStore, useAgentStore, resetAllMessageSlices, -} from "../components/store"; +} from "../components/stores"; import { isTask } from "../types/agentTypes"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { useSettings } from "../hooks/useSettings"; From 6c5880228620cbbd31306be5725e0be19d42821f Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Fri, 28 Apr 2023 22:53:25 -0700 Subject: [PATCH 20/22] remove redundant code --- src/components/stores/agentStore.ts | 1 - src/pages/index.tsx | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/components/stores/agentStore.ts b/src/components/stores/agentStore.ts index 64c7af3476..e2022e4de1 100644 --- a/src/components/stores/agentStore.ts +++ b/src/components/stores/agentStore.ts @@ -24,7 +24,6 @@ const createAgentSlice: StateCreator = (set, get) => { })); }, setAgent: (newAgent) => { - console.log("newAgent: ", newAgent); set(() => ({ agent: newAgent, })); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index aad62e76a5..95d8aa4ac2 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -45,7 +45,6 @@ const Home: NextPage = () => { const [name, setName] = React.useState(""); const [goalInput, setGoalInput] = React.useState(""); const settingsModel = useSettings(); - // const [shouldAgentStop, setShouldAgentStop] = React.useState(false); const [showHelpDialog, setShowHelpDialog] = React.useState(false); const [showSettingsDialog, setShowSettingsDialog] = React.useState(false); @@ -115,7 +114,6 @@ const Home: NextPage = () => { const handleStopAgent = () => { agent?.stopAgent(); - setIsAgentStopped(); }; const proTitle = ( From fb15157cdf62256d4de11bb2cb0db927e7f52946 Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Sat, 29 Apr 2023 08:38:40 -0700 Subject: [PATCH 21/22] rename setIsAgentStopped --- src/components/stores/agentStore.ts | 4 ++-- src/pages/index.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/stores/agentStore.ts b/src/components/stores/agentStore.ts index e2022e4de1..6e06622938 100644 --- a/src/components/stores/agentStore.ts +++ b/src/components/stores/agentStore.ts @@ -11,14 +11,14 @@ const initialAgentState = { interface AgentSlice { agent: AutonomousAgent | null; isAgentStopped: boolean; - setIsAgentStopped: () => void; + updateIsAgentStopped: () => void; setAgent: (newAgent: AutonomousAgent | null) => void; } const createAgentSlice: StateCreator = (set, get) => { return { ...initialAgentState, - setIsAgentStopped: () => { + updateIsAgentStopped: () => { set((state) => ({ isAgentStopped: !state.agent?.isRunning, })); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 95d8aa4ac2..8dc1f81cae 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -38,7 +38,7 @@ const Home: NextPage = () => { const setAgent = useAgentStore.use.setAgent(); const isAgentStopped = useAgentStore.use.isAgentStopped(); - const setIsAgentStopped = useAgentStore.use.setIsAgentStopped(); + const updateIsAgentStopped = useAgentStore.use.updateIsAgentStopped(); const agent = useAgentStore.use.agent(); const { session, status } = useAuth(); @@ -70,8 +70,8 @@ const Home: NextPage = () => { }, []); useEffect(() => { - setIsAgentStopped(); - }, [agent, setIsAgentStopped]); + updateIsAgentStopped(); + }, [agent, updateIsAgentStopped]); const handleAddMessage = (message: Message) => { if (isTask(message)) { From a832040d4654f57c1be6ecf49957b0171502102d Mon Sep 17 00:00:00 2001 From: Joe Shen Date: Sat, 29 Apr 2023 12:45:31 -0700 Subject: [PATCH 22/22] refactor: remove agentStatusSchema --- src/types/agentTypes.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/types/agentTypes.ts b/src/types/agentTypes.ts index 35e9b88f2e..c8a24927b1 100644 --- a/src/types/agentTypes.ts +++ b/src/types/agentTypes.ts @@ -72,13 +72,9 @@ export const [AGENT_STATUS_RUNNING, AGENT_STATUS_STOPPED] = [ "stopped" as const, ]; -const AgentStatusSchema = z.union([ - z.literal(AGENT_STATUS_RUNNING), - z.literal(AGENT_STATUS_STOPPED), - z.literal(""), -]); - -export type AgentStatus = z.infer; +export type AgentStatus = + | typeof AGENT_STATUS_RUNNING + | typeof AGENT_STATUS_STOPPED; /* Type Predicates */ export const isTask = (value: unknown): value is Task => {