mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 05:37:20 +08:00
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:
@@ -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",
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}"`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
27
frontend/src/state/settingsSlice.ts
Normal file
27
frontend/src/state/settingsSlice.ts
Normal 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;
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user