mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Wire up frontend (#128)
This commit is contained in:
parent
0f4a11685e
commit
335a91610e
@ -10,7 +10,7 @@ PYTHONPATH=`pwd`:$PYTHONPATH python3 opendevin/main.py -d ./workspace -c CodeAct
|
||||
```
|
||||
|
||||
|
||||
Example: prompts `gpt-3.5-turbo-0125` to write a flask server, install `flask` library, and start the server.
|
||||
Example: prompts `gpt-4-0125-preview` to write a flask server, install `flask` library, and start the server.
|
||||
|
||||
<img width="951" alt="image" src="https://github.com/OpenDevin/OpenDevin/assets/38853559/325c3115-a343-4cc5-a92b-f1e5d552a077">
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
VITE_TERMINAL_WS_URL="ws://localhost:8080/ws"
|
||||
VITE_TERMINAL_WS_URL="ws://localhost:3000/ws"
|
||||
@ -25,6 +25,7 @@
|
||||
"state"
|
||||
]
|
||||
}],
|
||||
"import/prefer-default-export": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"jsx-a11y/no-static-element-interactions": "off",
|
||||
"jsx-a11y/click-events-have-key-events": "off",
|
||||
|
||||
@ -6,6 +6,7 @@ import Terminal from "./components/Terminal";
|
||||
import Planner from "./components/Planner";
|
||||
import CodeEditor from "./components/CodeEditor";
|
||||
import Browser from "./components/Browser";
|
||||
import Errors from "./components/Errors";
|
||||
|
||||
const TAB_OPTIONS = ["terminal", "planner", "code", "browser"] as const;
|
||||
type TabOption = (typeof TAB_OPTIONS)[number];
|
||||
@ -26,19 +27,19 @@ function Tab({ name, active, onClick }: TabProps): JSX.Element {
|
||||
const tabData = {
|
||||
terminal: {
|
||||
name: "Terminal",
|
||||
component: <Terminal />,
|
||||
component: null,
|
||||
},
|
||||
planner: {
|
||||
name: "Planner",
|
||||
component: <Planner />,
|
||||
component: <Planner key="planner" />,
|
||||
},
|
||||
code: {
|
||||
name: "Code Editor",
|
||||
component: <CodeEditor />,
|
||||
component: <CodeEditor key="code" />,
|
||||
},
|
||||
browser: {
|
||||
name: "Browser",
|
||||
component: <Browser />,
|
||||
component: <Browser key="browser" />,
|
||||
},
|
||||
};
|
||||
|
||||
@ -47,6 +48,7 @@ function App(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<Errors />
|
||||
<div className="left-pane">
|
||||
<ChatInterface />
|
||||
</div>
|
||||
@ -61,6 +63,8 @@ function App(): JSX.Element {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{/* Keep terminal permanently open - see component for more details */}
|
||||
<Terminal key="terminal" hidden={activeTab !== "terminal"} />
|
||||
<div className="tab-content">{tabData[activeTab].component}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,80 +1,91 @@
|
||||
.chat-interface {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 0px;
|
||||
background-color: #252526;
|
||||
}
|
||||
|
||||
.message-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.message-layout {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.message{
|
||||
display: flex;
|
||||
}
|
||||
.user-message{
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
margin:10px 10px 0 auto
|
||||
}
|
||||
.user-message .message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 0px;
|
||||
background-color: #252526;
|
||||
}
|
||||
|
||||
.initializing-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.message-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.message-layout {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.message{
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.user-message{
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
margin:10px 10px 0 auto
|
||||
}
|
||||
|
||||
.user-message .message-content {
|
||||
background-color: #007acc ;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin:0 10px;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
background-color: #333333;
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.input-container input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
margin: 0 10px;
|
||||
background-color: #3c3c3c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.input-container button {
|
||||
padding: 10px 20px;
|
||||
background-color: #007acc;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 16px;
|
||||
}
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin:0 10px;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
background-color: #333333;
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
.input-container svg {
|
||||
height: 16px;
|
||||
}
|
||||
.input-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: auto;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.input-container input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
margin: 0 10px;
|
||||
background-color: #3c3c3c;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.input-container button {
|
||||
padding: 10px 20px;
|
||||
background-color: #007acc;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
.input-container svg {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
|
||||
@ -1,41 +1,57 @@
|
||||
import React, { useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSelector } from "react-redux";
|
||||
import "./ChatInterface.css";
|
||||
import userAvatar from "../assets/user-avatar.png";
|
||||
import assistantAvatar from "../assets/assistant-avatar.png";
|
||||
import { sendMessage } from "../state/chatSlice";
|
||||
import { RootState } from "../store";
|
||||
import { sendChatMessage } from "../services/chatService";
|
||||
|
||||
function MessageList(): JSX.Element {
|
||||
const { messages } = useSelector((state: RootState) => state.chat);
|
||||
|
||||
return (
|
||||
<div className="message-list">
|
||||
{messages.map((msg, index) => (
|
||||
<div key={index} className="message-layout">
|
||||
<div
|
||||
className={`${msg.sender === "user" ? "user-message" : "message"}`}
|
||||
>
|
||||
<img
|
||||
src={msg.sender === "user" ? userAvatar : assistantAvatar}
|
||||
alt={`${msg.sender} avatar`}
|
||||
className="avatar"
|
||||
/>
|
||||
<div className="message-content">{msg.content}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function InitializingStatus(): JSX.Element {
|
||||
return (
|
||||
<div className="initializing-status">
|
||||
<img src={assistantAvatar} alt="assistant avatar" className="avatar" />
|
||||
<div>Initializing agent (may take up to 10 seconds)...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ChatInterface(): JSX.Element {
|
||||
const messages = useSelector((state: RootState) => state.chat.messages);
|
||||
const { initialized } = useSelector((state: RootState) => state.task);
|
||||
const [inputMessage, setInputMessage] = useState("");
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleSendMessage = () => {
|
||||
if (inputMessage.trim() !== "") {
|
||||
dispatch(sendMessage(inputMessage));
|
||||
sendChatMessage(inputMessage);
|
||||
setInputMessage("");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chat-interface">
|
||||
<div className="message-list">
|
||||
{messages.map((msg, index) => (
|
||||
<div key={index} className="message-layout">
|
||||
<div
|
||||
className={`${msg.sender === "user" ? "user-message" : "message"}`}
|
||||
>
|
||||
<img
|
||||
src={msg.sender === "user" ? userAvatar : assistantAvatar}
|
||||
alt={`${msg.sender} avatar`}
|
||||
className="avatar"
|
||||
/>
|
||||
<div className="message-content">{msg.content}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{initialized ? <MessageList /> : <InitializingStatus />}
|
||||
<div className="input-container">
|
||||
<button className="attach-button" type="button" aria-label="file">
|
||||
<svg
|
||||
@ -63,6 +79,7 @@ function ChatInterface(): JSX.Element {
|
||||
handleSendMessage();
|
||||
}
|
||||
}}
|
||||
disabled={!initialized}
|
||||
/>
|
||||
<button type="button" onClick={handleSendMessage}>
|
||||
<span className="button-text">Send</span>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import React from "react";
|
||||
import Editor from "@monaco-editor/react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../store";
|
||||
|
||||
function CodeEditor(): JSX.Element {
|
||||
const handleEditorChange = (value: string | undefined) => {
|
||||
console.log("Content changed:", value);
|
||||
};
|
||||
const code = useSelector((state: RootState) => state.code.code);
|
||||
|
||||
return (
|
||||
<Editor
|
||||
height="100%"
|
||||
theme="vs-dark"
|
||||
defaultLanguage="javascript"
|
||||
defaultValue="// Welcome to OpenDevin!"
|
||||
onChange={handleEditorChange}
|
||||
defaultLanguage="python"
|
||||
defaultValue="# Welcome to OpenDevin!"
|
||||
value={code}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
15
frontend/src/components/Errors.css
Normal file
15
frontend/src/components/Errors.css
Normal file
@ -0,0 +1,15 @@
|
||||
.errors {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
top: 1rem;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: #B00020;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
20
frontend/src/components/Errors.tsx
Normal file
20
frontend/src/components/Errors.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../store";
|
||||
import "./Errors.css";
|
||||
|
||||
function Errors(): JSX.Element {
|
||||
const errors = useSelector((state: RootState) => state.errors.errors);
|
||||
|
||||
return (
|
||||
<div className="errors">
|
||||
{errors.map((error, index) => (
|
||||
<div key={index} className="error">
|
||||
ERROR: {error}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Errors;
|
||||
@ -22,9 +22,15 @@ class JsonWebsocketAddon {
|
||||
}),
|
||||
);
|
||||
this._socket.addEventListener("message", (event) => {
|
||||
const { message } = JSON.parse(event.data);
|
||||
if (message.action === "terminal") {
|
||||
terminal.write(message.data);
|
||||
const { action, args } = JSON.parse(event.data);
|
||||
if (action === "run") {
|
||||
terminal.writeln(args.command);
|
||||
}
|
||||
if (action === "output") {
|
||||
args.output.split("\n").forEach((line: string) => {
|
||||
terminal.writeln(line);
|
||||
});
|
||||
terminal.write("\n$ ");
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -35,7 +41,16 @@ class JsonWebsocketAddon {
|
||||
}
|
||||
}
|
||||
|
||||
function Terminal(): JSX.Element {
|
||||
type TerminalProps = {
|
||||
hidden: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* The terminal's content is set by write messages. To avoid complicated state logic,
|
||||
* we keep the terminal persistently open as a child of <App /> and hidden when not in use.
|
||||
*/
|
||||
|
||||
function Terminal({ hidden }: TerminalProps): JSX.Element {
|
||||
const terminalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@ -49,6 +64,7 @@ function Terminal(): JSX.Element {
|
||||
fontFamily: "Menlo, Monaco, 'Courier New', monospace",
|
||||
fontSize: 14,
|
||||
});
|
||||
terminal.write("$ ");
|
||||
|
||||
const fitAddon = new FitAddon();
|
||||
terminal.loadAddon(fitAddon);
|
||||
@ -69,7 +85,16 @@ function Terminal(): JSX.Element {
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <div ref={terminalRef} style={{ width: "100%", height: "100%" }} />;
|
||||
return (
|
||||
<div
|
||||
ref={terminalRef}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: hidden ? "none" : "block",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Terminal;
|
||||
|
||||
10
frontend/src/services/chatService.ts
Normal file
10
frontend/src/services/chatService.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { appendUserMessage } from "../state/chatSlice";
|
||||
import socket from "../state/socket";
|
||||
import store from "../store";
|
||||
|
||||
export function sendChatMessage(message: string): void {
|
||||
store.dispatch(appendUserMessage(message));
|
||||
const event = { action: "start", args: { task: message } };
|
||||
const eventString = JSON.stringify(event);
|
||||
socket.send(eventString);
|
||||
}
|
||||
@ -5,29 +5,7 @@ type Message = {
|
||||
sender: "user" | "assistant";
|
||||
};
|
||||
|
||||
const initialMessages: Message[] = [
|
||||
{
|
||||
content:
|
||||
"I want you to setup this project: https://github.com/mckaywrigley/assistant-ui",
|
||||
sender: "user",
|
||||
},
|
||||
{
|
||||
content:
|
||||
"Got it, I'll get started on setting up the assistant UI project from the GitHub link you provided. I'll update you on my progress.",
|
||||
sender: "assistant",
|
||||
},
|
||||
{ content: "Cloned repo from GitHub.", sender: "assistant" },
|
||||
{ content: "You're doing great! Keep it up :)", sender: "user" },
|
||||
{
|
||||
content:
|
||||
"Thanks! I've cloned the repo and am currently going through the README to make sure we get everything set up right. There's a detailed guide for local setup as well as instructions for hosting it. I'll follow the steps and keep you posted on the progress! If there are any specific configurations or features you want to prioritize, just let me know.",
|
||||
sender: "assistant",
|
||||
},
|
||||
{
|
||||
content: "Installed project dependencies using npm.",
|
||||
sender: "assistant",
|
||||
},
|
||||
];
|
||||
const initialMessages: Message[] = [];
|
||||
|
||||
export const chatSlice = createSlice({
|
||||
name: "chat",
|
||||
@ -35,12 +13,15 @@ export const chatSlice = createSlice({
|
||||
messages: initialMessages,
|
||||
},
|
||||
reducers: {
|
||||
sendMessage: (state, action) => {
|
||||
appendUserMessage: (state, action) => {
|
||||
state.messages.push({ content: action.payload, sender: "user" });
|
||||
},
|
||||
appendAssistantMessage: (state, action) => {
|
||||
state.messages.push({ content: action.payload, sender: "assistant" });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { sendMessage } = chatSlice.actions;
|
||||
export const { appendUserMessage, appendAssistantMessage } = chatSlice.actions;
|
||||
|
||||
export default chatSlice.reducer;
|
||||
|
||||
17
frontend/src/state/codeSlice.ts
Normal file
17
frontend/src/state/codeSlice.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
export const codeSlice = createSlice({
|
||||
name: "code",
|
||||
initialState: {
|
||||
code: "# Welcome to OpenDevin!",
|
||||
},
|
||||
reducers: {
|
||||
setCode: (state, action) => {
|
||||
state.code = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setCode } = codeSlice.actions;
|
||||
|
||||
export default codeSlice.reducer;
|
||||
19
frontend/src/state/errorsSlice.ts
Normal file
19
frontend/src/state/errorsSlice.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const initialErrors: string[] = [];
|
||||
|
||||
export const errorsSlice = createSlice({
|
||||
name: "errors",
|
||||
initialState: {
|
||||
errors: initialErrors,
|
||||
},
|
||||
reducers: {
|
||||
appendError: (state, action) => {
|
||||
state.errors.push(action.payload);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { appendError } = errorsSlice.actions;
|
||||
|
||||
export default errorsSlice.reducer;
|
||||
@ -1,23 +1,42 @@
|
||||
import store from "../store";
|
||||
import { setScreenshotSrc, setUrl } from "./browserSlice";
|
||||
import { appendAssistantMessage } from "./chatSlice";
|
||||
import { setCode } from "./codeSlice";
|
||||
import { appendError } from "./errorsSlice";
|
||||
import { setInitialized } from "./taskSlice";
|
||||
|
||||
const MESSAGE_ACTIONS = ["terminal", "planner", "code", "browser"] as const;
|
||||
type MessageAction = (typeof MESSAGE_ACTIONS)[number];
|
||||
|
||||
type SocketMessage = {
|
||||
action: MessageAction;
|
||||
data: Record<string, unknown>;
|
||||
message: string;
|
||||
args: Record<string, unknown>;
|
||||
};
|
||||
|
||||
const messageActions = {
|
||||
browser: (message: SocketMessage) => {
|
||||
const { url, screenshotSrc } = message.data;
|
||||
initialize: () => {
|
||||
store.dispatch(setInitialized(true));
|
||||
store.dispatch(
|
||||
appendAssistantMessage(
|
||||
"Hello, I am OpenDevin, an AI Software Engineer. What would you like me to build you today?",
|
||||
),
|
||||
);
|
||||
},
|
||||
browse: (message: SocketMessage) => {
|
||||
const { url, screenshotSrc } = message.args;
|
||||
store.dispatch(setUrl(url));
|
||||
store.dispatch(setScreenshotSrc(screenshotSrc));
|
||||
},
|
||||
terminal: () => {},
|
||||
planner: () => {},
|
||||
code: () => {},
|
||||
write: (message: SocketMessage) => {
|
||||
store.dispatch(setCode(message.args.contents));
|
||||
},
|
||||
think: (message: SocketMessage) => {
|
||||
store.dispatch(appendAssistantMessage(message.args.thought));
|
||||
},
|
||||
finish: (message: SocketMessage) => {
|
||||
store.dispatch(appendAssistantMessage(message.message));
|
||||
},
|
||||
};
|
||||
|
||||
const WS_URL = import.meta.env.VITE_TERMINAL_WS_URL;
|
||||
@ -30,13 +49,22 @@ if (!WS_URL) {
|
||||
const socket = new WebSocket(WS_URL);
|
||||
|
||||
socket.addEventListener("message", (event) => {
|
||||
const { message } = JSON.parse(event.data);
|
||||
console.log("Received message:", message);
|
||||
const socketMessage = JSON.parse(event.data) as SocketMessage;
|
||||
|
||||
if (message.action in messageActions) {
|
||||
const action = messageActions[message.action as MessageAction];
|
||||
action(message);
|
||||
if (socketMessage.action in messageActions) {
|
||||
const actionFn =
|
||||
messageActions[socketMessage.action as keyof typeof messageActions];
|
||||
actionFn(socketMessage);
|
||||
} else if (!socketMessage.action) {
|
||||
store.dispatch(appendAssistantMessage(socketMessage.message));
|
||||
}
|
||||
});
|
||||
socket.addEventListener("error", () => {
|
||||
store.dispatch(
|
||||
appendError(
|
||||
`Failed connection to server. Please ensure the server is reachable at ${WS_URL}.`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
export default socket;
|
||||
|
||||
21
frontend/src/state/taskSlice.ts
Normal file
21
frontend/src/state/taskSlice.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
export const taskSlice = createSlice({
|
||||
name: "task",
|
||||
initialState: {
|
||||
initialized: false,
|
||||
completed: false,
|
||||
},
|
||||
reducers: {
|
||||
setInitialized: (state, action) => {
|
||||
state.initialized = action.payload;
|
||||
},
|
||||
setCompleted: (state, action) => {
|
||||
state.completed = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setInitialized, setCompleted } = taskSlice.actions;
|
||||
|
||||
export default taskSlice.reducer;
|
||||
@ -1,11 +1,17 @@
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import browserReducer from "./state/browserSlice";
|
||||
import chatReducer from "./state/chatSlice";
|
||||
import codeReducer from "./state/codeSlice";
|
||||
import taskReducer from "./state/taskSlice";
|
||||
import errorsReducer from "./state/errorsSlice";
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
browser: browserReducer,
|
||||
chat: chatReducer,
|
||||
code: codeReducer,
|
||||
task: taskReducer,
|
||||
errors: errorsReducer,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -88,7 +88,7 @@ class Session:
|
||||
model_name=model,
|
||||
)
|
||||
self.controller = AgentController(self.agent, directory, callbacks=[self.on_agent_event])
|
||||
await self.send_message("Control loop started")
|
||||
await self.send({"action": "initialize", "message": "Control loop started."})
|
||||
|
||||
async def start_task(self, start_event):
|
||||
if "task" not in start_event.args:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user