Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement agent global state #400

Merged
merged 29 commits into from
Apr 29, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6b35326
implement zustand for messages
Jshen123 Apr 23, 2023
4e36a48
implement live task status update in task window
Jshen123 Apr 24, 2023
a0b199d
resolve merge conflicts
Jshen123 Apr 24, 2023
f7dcc73
revert unintended changes
Jshen123 Apr 24, 2023
2855687
revert unintended changes
Jshen123 Apr 24, 2023
7d8f9ab
updated task states
Jshen123 Apr 25, 2023
30e77ff
remove isAction check
Jshen123 Apr 25, 2023
a280460
resolve merge conflicts
Jshen123 Apr 25, 2023
8da0cb1
fix issues
Jshen123 Apr 25, 2023
acd105d
remove green text color for completed and final tasks
Jshen123 Apr 25, 2023
f2e082e
resolve merge conflicts
Jshen123 Apr 26, 2023
d70d817
resolve merge conflicts
Jshen123 Apr 26, 2023
1561d2e
Empty-Commit
Jshen123 Apr 26, 2023
05687e3
update AgentTask to include status field
Jshen123 Apr 26, 2023
b6facf6
update agentRouter
Jshen123 Apr 26, 2023
ff5a11a
resolve merge conflicts
Jshen123 Apr 26, 2023
50a384f
fix saved message rendering error
Jshen123 Apr 26, 2023
bd84241
fix saved message rendering error
Jshen123 Apr 26, 2023
77d6a8b
implement agentStore
Jshen123 Apr 28, 2023
e16933b
Revert "fix saved message rendering error"
Jshen123 Apr 28, 2023
444b1c1
Revert "fix saved message rendering error"
Jshen123 Apr 28, 2023
e418eab
clean up comments
Jshen123 Apr 28, 2023
6a73ad1
remove changes from other PR
Jshen123 Apr 28, 2023
0402392
change directory from store to stores
Jshen123 Apr 28, 2023
3d14b53
Merge branch 'main' of https://github.com/reworkd/AgentGPT into feat/…
Jshen123 Apr 28, 2023
39886c6
Merge branch 'main' of https://github.com/reworkd/AgentGPT into feat/…
Jshen123 Apr 29, 2023
6c58802
remove redundant code
Jshen123 Apr 29, 2023
fb15157
rename setIsAgentStopped
Jshen123 Apr 29, 2023
a832040
refactor: remove agentStatusSchema
Jshen123 Apr 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const Accordion = ({ child, name }: AccordionProps) => {
<AccordionPrimitive>
{({ open }) => (
<>
<AccordionPrimitive.Button className="border:black delay-50 flex w-full items-center justify-between rounded-xl bg-[#4a4a4a] px-3 py-2 text-sm tracking-wider outline-0 transition-all placeholder:text-white/20 hover:border-[#1E88E5]/40 hover:bg-[#6b6b6b] focus:border-[#1E88E5] focus-visible:ring sm:py-2 md:text-lg">
<AccordionPrimitive.Button className="border:black delay-50 flex w-full items-center justify-between rounded-xl bg-[#4a4a4a] px-3 py-2 text-sm tracking-wider outline-0 transition-all placeholder:text-white/20 hover:border-[#1E88E5]/40 hover:bg-[#6b6b6b] focus:border-[#1E88E5] focus-visible:ring md:text-lg">
{name}
<FaChevronDown
className={`${open ? "rotate-180 transform" : ""} h-5 w-5`}
Expand Down
8 changes: 2 additions & 6 deletions src/components/ChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ interface ChatWindowProps extends HeaderProps {
showDonation: boolean;
fullscreen?: boolean;
scrollToBottom?: boolean;
isAgentStopped?: boolean;
}

const messageListId = "chat-window-message-list";
Expand All @@ -51,7 +50,6 @@ const ChatWindow = ({
onSave,
fullscreen,
scrollToBottom,
isAgentStopped,
}: ChatWindowProps) => {
const [t] = useTranslation();
const [hasUserScrolled, setHasUserScrolled] = useState(false);
Expand Down Expand Up @@ -99,7 +97,7 @@ const ChatWindow = ({

return (
<FadeIn key={`${index}-${message.type}`}>
<ChatMessage message={message} isAgentStopped={isAgentStopped} />
<ChatMessage message={message} />
</FadeIn>
);
})}
Expand Down Expand Up @@ -259,12 +257,10 @@ const MacWindowHeader = (props: HeaderProps) => {
};
const ChatMessage = ({
message,
isAgentStopped,
className,
}: {
message: Message;
className?: string;
isAgentStopped?: boolean;
}) => {
const [t] = useTranslation();
const [showCopy, setShowCopy] = useState(false);
Expand Down Expand Up @@ -299,7 +295,7 @@ const ChatMessage = ({
// Avoid for system messages as they do not have an icon and will cause a weird space
<>
<div className="mr-2 inline-block h-[0.9em]">
{getTaskStatusIcon(message, { isAgentStopped })}
{getTaskStatusIcon(message, {})}
</div>
<span className="mr-2 font-bold">{getMessagePrefix(message, t)}</span>
</>
Expand Down
16 changes: 6 additions & 10 deletions src/components/TaskWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import FadeIn from "./motions/FadeIn";
import Expand from "./motions/expand";
import { Task } from "../types/agentTypes";
import { getMessageContainerStyle, getTaskStatusIcon } from "./utils/helpers";
import { useMessageStore } from "../components/store";
import { useMessageStore } from "../components/stores";
import { FaListAlt } from "react-icons/fa";
import { useTranslation } from "react-i18next";
import { useAgentStore } from "../components/stores";

export const TaskWindow = ({ isAgentStopped }: { isAgentStopped: boolean }) => {
export const TaskWindow = () => {
const tasks = useMessageStore.use.tasks();
const [t] = useTranslation();
return (
Expand All @@ -18,21 +19,16 @@ export const TaskWindow = ({ isAgentStopped }: { isAgentStopped: boolean }) => {
<div className="window-heights mb-2 w-full px-1 ">
<div className="flex flex-col gap-2 overflow-y-auto overflow-x-hidden">
{tasks.map((task, i) => (
<Task key={i} task={task} isAgentStopped={isAgentStopped} />
<Task key={i} task={task} />
))}
</div>
</div>
</Expand>
);
};

const Task = ({
task,
isAgentStopped,
}: {
task: Task;
isAgentStopped: boolean;
}) => {
const Task = ({ task }: { task: Task }) => {
const isAgentStopped = useAgentStore.use.isAgentStopped();
return (
<FadeIn>
<div
Expand Down
38 changes: 38 additions & 0 deletions src/components/stores/agentStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createSelectors } from "./helpers";
import type { StateCreator } from "zustand";
import { create } from "zustand";
import type AutonomousAgent from "../AutonomousAgent";

const initialAgentState = {
agent: null,
isAgentStopped: true,
};

interface AgentSlice {
agent: AutonomousAgent | null;
isAgentStopped: boolean;
updateIsAgentStopped: () => void;
setAgent: (newAgent: AutonomousAgent | null) => void;
}

const createAgentSlice: StateCreator<AgentSlice> = (set, get) => {
return {
...initialAgentState,
updateIsAgentStopped: () => {
set((state) => ({
isAgentStopped: !state.agent?.isRunning,
}));
},
setAgent: (newAgent) => {
set(() => ({
agent: newAgent,
}));
},
};
};

export const useAgentStore = createSelectors(
create<AgentSlice>()((...a) => ({
...createAgentSlice(...a),
}))
);
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./messageStore";
export * from "./agentStore";
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,5 @@ export const useMessageStore = createSelectors(
}))
);

export const resetAllSlices = () => resetters.forEach((resetter) => resetter());
export const resetAllMessageSlices = () =>
resetters.forEach((resetter) => resetter());
46 changes: 23 additions & 23 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,33 @@ 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/stores";
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 updateIsAgentStopped = useAgentStore.use.updateIsAgentStopped();
const agent = useAgentStore.use.agent();

const { session, status } = useAuth();
const [name, setName] = React.useState<string>("");
const [goalInput, setGoalInput] = React.useState<string>("");
const [agent, setAgent] = React.useState<AutonomousAgent | null>(null);
const settingsModel = useSettings();
const [shouldAgentStop, setShouldAgentStop] = React.useState(false);

const [showHelpDialog, setShowHelpDialog] = React.useState(false);
const [showSettingsDialog, setShowSettingsDialog] = React.useState(false);
const [hasSaved, setHasSaved] = React.useState(false);
Expand All @@ -62,10 +70,8 @@ const Home: NextPage = () => {
}, []);

useEffect(() => {
if (agent == null) {
setShouldAgentStop(false);
}
}, [agent]);
updateIsAgentStopped();
}, [agent, updateIsAgentStopped]);

const handleAddMessage = (message: Message) => {
if (isTask(message)) {
Expand All @@ -78,21 +84,19 @@ const Home: NextPage = () => {
const disableDeployAgent =
agent != null || isEmptyOrBlank(name) || isEmptyOrBlank(goalInput);

const isAgentStopped = () => !agent?.isRunning || agent === null;

const handleNewGoal = () => {
const agent = new AutonomousAgent(
const newAgent = new AutonomousAgent(
name.trim(),
goalInput.trim(),
handleAddMessage,
() => setAgent(null),
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 = (
Expand All @@ -109,7 +113,6 @@ const Home: NextPage = () => {
};

const handleStopAgent = () => {
setShouldAgentStop(true);
agent?.stopAgent();
};

Expand All @@ -121,7 +124,7 @@ const Home: NextPage = () => {

const shouldShowSave =
status === "authenticated" &&
!agent?.isRunning &&
isAgentStopped &&
messages.length &&
!hasSaved;

Expand Down Expand Up @@ -194,11 +197,8 @@ const Home: NextPage = () => {
: undefined
}
scrollToBottom
isAgentStopped={isAgentStopped()}
/>
{tasks.length > 0 && (
<TaskWindow isAgentStopped={isAgentStopped()} />
)}
{tasks.length > 0 && <TaskWindow />}
</Expand>

<div className="flex w-full flex-col gap-2 sm:m-4 ">
Expand Down Expand Up @@ -248,11 +248,11 @@ const Home: NextPage = () => {
)}
</Button>
<Button
disabled={agent == null}
disabled={agent === null}
onClick={handleStopAgent}
enabledClassName={"bg-red-600 hover:bg-red-400"}
>
{shouldAgentStop ? (
{!isAgentStopped && agent === null ? (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this makes semantic sense. The above is saying:
"If the agent is not stopped and the agent exists then show stopping"
^ not sure that is right semantically

Copy link
Contributor Author

@Jshen123 Jshen123 Apr 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, I could be wrong but this might be outdated code and imo we should just remove this completely.

my understanding is that this is handling a state where agent has been shutdown (i.e. set to null) but the agent?.isRunning has not been set to false

In today's code, agent?.isRunning and setAgent(null) happen simultaneous. I don't think this state would happen

stopAgent() {
this.sendManualShutdownMessage();
this.isRunning = false;
this.shutdown();
return;
}

CC: @asim-shrestha

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah lets refactor this after

<>
<VscLoading className="animate-spin" size={20} />
<span className="ml-2">{t("Stopping")}</span>
Expand Down
15 changes: 15 additions & 0 deletions src/types/agentTypes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from "zod";

/* Message & Task Type */
export const [
MESSAGE_TYPE_GOAL,
MESSAGE_TYPE_THINKING,
Expand Down Expand Up @@ -65,6 +66,20 @@ export const messageSchema = z.union([taskSchema, nonTaskScehma]);
export type Task = z.infer<typeof taskSchema>;
export type Message = z.infer<typeof messageSchema>;

/* Agent Type */
export const [AGENT_STATUS_RUNNING, AGENT_STATUS_STOPPED] = [
"running" as const,
"stopped" as const,
];

const AgentStatusSchema = z.union([
z.literal(AGENT_STATUS_RUNNING),
z.literal(AGENT_STATUS_STOPPED),
z.literal(""),
]);

export type AgentStatus = z.infer<typeof AgentStatusSchema>;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a hard to work with. Any reason we cant do the below?

Suggested change
export const [AGENT_STATUS_RUNNING, AGENT_STATUS_STOPPED] = [
"running" as const,
"stopped" as const,
];
const AgentStatusSchema = z.union([
z.literal(AGENT_STATUS_RUNNING),
z.literal(AGENT_STATUS_STOPPED),
z.literal(""),
]);
export type AgentStatus = z.infer<typeof AgentStatusSchema>;
export type AgentStatus = "running" | "stopped"

Copy link
Contributor Author

@Jshen123 Jshen123 Apr 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made it more concise 🎉

export const [AGENT_STATUS_RUNNING, AGENT_STATUS_STOPPED] = [
"running" as const,
"stopped" as const,
];
export type AgentStatus =
| typeof AGENT_STATUS_RUNNING
| typeof AGENT_STATUS_STOPPED;

keepingAGENT_STATUS_RUNNING and AGENT_STATUS_STOPPED because the strings "running" and "stopped" are referenced in many places.

/* Type Predicates */
export const isTask = (value: unknown): value is Task => {
try {
Expand Down