fix: apply settings without explicit change (#541)

* fix: apply settings without explicit change
* Change default model to gpt-4-0125-preview and don't print settings selection on startup
This commit is contained in:
Jim Su
2024-04-02 09:49:43 -04:00
committed by GitHub
parent d64383a520
commit 0a8a857d00
7 changed files with 81 additions and 34 deletions

View File

@@ -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",

View File

@@ -1,4 +1,5 @@
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import {
Modal,
ModalContent,
@@ -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;
@@ -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,
);
@@ -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);
@@ -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
@@ -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}
@@ -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}

View File

@@ -1,4 +1,3 @@
import socket from "../socket/socket";
import { appendAssistantMessage } from "../state/chatSlice";
import { setInitialized } from "../state/taskSlice";
import store from "../store";
@@ -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}"`));
}
}
}

View File

@@ -3,6 +3,7 @@ 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;
@@ -10,6 +11,10 @@ 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) {

View File

@@ -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;

View File

@@ -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: {
@@ -12,6 +13,7 @@ const store = configureStore({
code: codeReducer,
task: taskReducer,
errors: errorsReducer,
settings: settingsReducer,
},
});

View File

@@ -40,7 +40,6 @@ class Session:
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.