Skip to content

Commit

Permalink
fix: apply settings without explicit change (All-Hands-AI#541)
Browse files Browse the repository at this point in the history
* fix: apply settings without explicit change
* Change default model to gpt-4-0125-preview and don't print settings selection on startup
  • Loading branch information
yimothysu committed Apr 2, 2024
1 parent d64383a commit 0a8a857
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 34 deletions.
2 changes: 2 additions & 0 deletions frontend/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"state"
]
}],
// For https://stackoverflow.com/questions/55844608/stuck-with-eslint-error-i-e-separately-loops-should-be-avoided-in-favor-of-arra
"no-restricted-syntax": "off",
"import/prefer-default-export": "off",
"no-underscore-dangle": "off",
"jsx-a11y/no-static-element-interactions": "off",
Expand Down
37 changes: 20 additions & 17 deletions frontend/src/components/SettingModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import {
Modal,
ModalContent,
Expand All @@ -13,13 +14,18 @@ import {
import { KeyboardEvent } from "@react-types/shared/src/events";
import {
INITIAL_AGENTS,
changeAgent,
changeDirectory as sendChangeDirectorySocketMessage,
changeModel,
fetchModels,
fetchAgents,
INITIAL_MODELS,
sendSettings,
} from "../services/settingsService";
import {
setModel,
setAgent,
setWorkspaceDirectory,
} from "../state/settingsSlice";
import store, { RootState } from "../store";
import socket from "../socket/socket";

interface Props {
isOpen: boolean;
Expand All @@ -34,18 +40,15 @@ const cachedAgents = JSON.parse(
);

function SettingModal({ isOpen, onClose }: Props): JSX.Element {
const [workspaceDirectory, setWorkspaceDirectory] = useState(
localStorage.getItem("workspaceDirectory") || "./workspace",
);
const [model, setModel] = useState(
localStorage.getItem("model") || "gpt-3.5-turbo-1106",
const model = useSelector((state: RootState) => state.settings.model);
const agent = useSelector((state: RootState) => state.settings.agent);
const workspaceDirectory = useSelector(
(state: RootState) => state.settings.workspaceDirectory,
);

const [supportedModels, setSupportedModels] = useState(
cachedModels.length > 0 ? cachedModels : INITIAL_MODELS,
);
const [agent, setAgent] = useState(
localStorage.getItem("agent") || "MonologueAgent",
);
const [supportedAgents, setSupportedAgents] = useState(
cachedAgents.length > 0 ? cachedAgents : INITIAL_AGENTS,
);
Expand All @@ -62,9 +65,7 @@ function SettingModal({ isOpen, onClose }: Props): JSX.Element {
}, []);

const handleSaveCfg = () => {
sendChangeDirectorySocketMessage(workspaceDirectory);
changeModel(model);
changeAgent(agent);
sendSettings(socket, { model, agent, workspaceDirectory });
localStorage.setItem("model", model);
localStorage.setItem("workspaceDirectory", workspaceDirectory);
localStorage.setItem("agent", agent);
Expand All @@ -87,7 +88,9 @@ function SettingModal({ isOpen, onClose }: Props): JSX.Element {
label="OpenDevin Workspace Directory"
defaultValue={workspaceDirectory}
placeholder="Default: ./workspace"
onChange={(e) => setWorkspaceDirectory(e.target.value)}
onChange={(e) =>
store.dispatch(setWorkspaceDirectory(e.target.value))
}
/>

<Autocomplete
Expand All @@ -100,7 +103,7 @@ function SettingModal({ isOpen, onClose }: Props): JSX.Element {
defaultSelectedKey={model}
// className="max-w-xs"
onSelectionChange={(key) => {
setModel(key as string);
store.dispatch(setModel(key as string));
}}
onKeyDown={(e: KeyboardEvent) => e.continuePropagation()}
defaultFilter={customFilter}
Expand All @@ -122,7 +125,7 @@ function SettingModal({ isOpen, onClose }: Props): JSX.Element {
defaultSelectedKey={agent}
// className="max-w-xs"
onSelectionChange={(key) => {
setAgent(key as string);
store.dispatch(setAgent(key as string));
}}
onKeyDown={(e: KeyboardEvent) => e.continuePropagation()}
defaultFilter={customFilter}
Expand Down
41 changes: 25 additions & 16 deletions frontend/src/services/settingsService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import socket from "../socket/socket";
import { appendAssistantMessage } from "../state/chatSlice";
import { setInitialized } from "../state/taskSlice";
import store from "../store";
Expand Down Expand Up @@ -27,22 +26,32 @@ export const INITIAL_AGENTS = ["MonologueAgent", "CodeActAgent"];

export type Agent = (typeof INITIAL_AGENTS)[number];

function changeSetting(setting: string, value: string): void {
const event = { action: "initialize", args: { [setting]: value } };
// Map Redux settings to socket event arguments
const SETTINGS_MAP = new Map<string, string>([
["model", "model"],
["agent", "agent_cls"],
["workspaceDirectory", "directory"],
]);

// Send settings to the server
export function sendSettings(
socket: WebSocket,
reduxSettings: { [id: string]: string },
appendMessages: boolean = true,
): void {
const socketSettings = Object.fromEntries(
Object.entries(reduxSettings).map(([setting, value]) => [
SETTINGS_MAP.get(setting) || setting,
value,
]),
);
const event = { action: "initialize", args: socketSettings };
const eventString = JSON.stringify(event);
socket.send(eventString);
store.dispatch(setInitialized(false));
store.dispatch(appendAssistantMessage(`Changed ${setting} to "${value}"`));
}

export function changeModel(model: Model): void {
changeSetting("model", model);
}

export function changeAgent(agent: Agent): void {
changeSetting("agent_cls", agent);
}

export function changeDirectory(directory: string): void {
changeSetting("directory", directory);
if (appendMessages) {
for (const [setting, value] of Object.entries(reduxSettings)) {
store.dispatch(appendAssistantMessage(`Set ${setting} to "${value}"`));
}
}
}
5 changes: 5 additions & 0 deletions frontend/src/socket/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import { ActionMessage, ObservationMessage } from "../types/Message";
import { appendError } from "../state/errorsSlice";
import { handleActionMessage } from "./actions";
import { handleObservationMessage } from "./observations";
import { sendSettings } from "../services/settingsService";

type SocketMessage = ActionMessage | ObservationMessage;

const WS_URL = `ws://${window.location.host}/ws`;

const socket = new WebSocket(WS_URL);

socket.addEventListener("open", () => {
const { settings } = store.getState();
sendSettings(socket, settings, false);
});
socket.addEventListener("message", (event) => {
const socketMessage = JSON.parse(event.data) as SocketMessage;
if ("action" in socketMessage) {
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/state/settingsSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createSlice } from "@reduxjs/toolkit";

export const settingsSlice = createSlice({
name: "settings",
initialState: {
model: localStorage.getItem("model") || "gpt-4-0125-preview",
agent: localStorage.getItem("agent") || "MonologueAgent",
workspaceDirectory:
localStorage.getItem("workspaceDirectory") || "./workspace",
},
reducers: {
setModel: (state, action) => {
state.model = action.payload;
},
setAgent: (state, action) => {
state.agent = action.payload;
},
setWorkspaceDirectory: (state, action) => {
state.workspaceDirectory = action.payload;
},
},
});

export const { setModel, setAgent, setWorkspaceDirectory } =
settingsSlice.actions;

export default settingsSlice.reducer;
2 changes: 2 additions & 0 deletions frontend/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import chatReducer from "./state/chatSlice";
import codeReducer from "./state/codeSlice";
import taskReducer from "./state/taskSlice";
import errorsReducer from "./state/errorsSlice";
import settingsReducer from "./state/settingsSlice";

const store = configureStore({
reducer: {
Expand All @@ -12,6 +13,7 @@ const store = configureStore({
code: codeReducer,
task: taskReducer,
errors: errorsReducer,
settings: settingsReducer,
},
});

Expand Down
1 change: 0 additions & 1 deletion opendevin/server/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def __init__(self, websocket):
self.controller: Optional[AgentController] = None
self.agent: Optional[Agent] = None
self.agent_task = None
asyncio.create_task(self.create_controller(), name="create controller") # FIXME: starting the docker container synchronously causes a websocket error...

async def send_error(self, message):
"""Sends an error message to the client.
Expand Down

0 comments on commit 0a8a857

Please sign in to comment.