diff --git a/frontend/src/app/Main.tsx b/frontend/src/app/Main.tsx
index 595eae16..383ce02a 100644
--- a/frontend/src/app/Main.tsx
+++ b/frontend/src/app/Main.tsx
@@ -4,7 +4,7 @@ import { HashRouter, Routes, Route } from 'react-router-dom';
import { ThemeProvider as MuiThemeProvider, CssBaseline } from '@mui/material';
import { store } from '../shared/state/store';
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
-import { fetchSettings } from '@/shared/state/settingsSlice';
+import { GET_SETTINGS } from '@/shared/backend-bridge/apps/settings';
import { fetchModels } from '@/shared/state/modelsSlice';
import {
setAppVersion,
@@ -40,7 +40,7 @@ const SettingsLoader: React.FC<{ children: React.ReactNode }> = ({ children }) =
const theme = useAppSelector((s) => s.settings.data.theme);
const loaded = useAppSelector((s) => s.settings.loaded);
useEffect(() => {
- dispatch(fetchSettings());
+ dispatch(GET_SETTINGS());
dispatch(fetchModels());
}, [dispatch]);
useEffect(() => {
diff --git a/frontend/src/app/pages/Settings/GeneralTab.tsx b/frontend/src/app/pages/Settings/GeneralTab.tsx
index d154d65a..4202860e 100644
--- a/frontend/src/app/pages/Settings/GeneralTab.tsx
+++ b/frontend/src/app/pages/Settings/GeneralTab.tsx
@@ -10,7 +10,8 @@ import Switch from '@mui/material/Switch';
import FolderOpenIcon from '@mui/icons-material/FolderOpen';
import LanguageIcon from '@mui/icons-material/Language';
import RestartAltIcon from '@mui/icons-material/RestartAlt';
-import { DEFAULT_SYSTEM_PROMPT, resetSystemPrompt } from '@/shared/state/settingsSlice';
+import { RESET_SYSTEM_PROMPT } from '@/shared/backend-bridge/apps/settings';
+import { DEFAULT_SYSTEM_PROMPT } from '@/shared/state/settingsSlice';
import InterfaceSection from './InterfaceSection';
import AboutSection from './AboutSection';
import type { UseSettingsReturn } from './hooks/useSettings';
@@ -29,7 +30,7 @@ const GeneralTab: React.FC<{ s: UseSettingsReturn }> = ({ s }) => {
size="small"
startIcon={}
onClick={async () => {
- await dispatch(resetSystemPrompt());
+ await dispatch(RESET_SYSTEM_PROMPT());
setForm((prev) => ({ ...prev, default_system_prompt: DEFAULT_SYSTEM_PROMPT }));
}}
sx={{
diff --git a/frontend/src/app/pages/Settings/hooks/useSettings.ts b/frontend/src/app/pages/Settings/hooks/useSettings.ts
index 0e1a9a58..2ffe778f 100644
--- a/frontend/src/app/pages/Settings/hooks/useSettings.ts
+++ b/frontend/src/app/pages/Settings/hooks/useSettings.ts
@@ -1,6 +1,7 @@
import { useState, useEffect, useMemo, useCallback } from 'react';
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
-import { updateSettings, closeSettingsModal, AppSettings } from '@/shared/state/settingsSlice';
+import { UPDATE_SETTINGS, AppSettings } from '@/shared/backend-bridge/apps/settings';
+import { closeSettingsModal } from '@/shared/state/settingsSlice';
import { fetchModels } from '@/shared/state/modelsSlice';
import { setChecking, setUpdateError } from '@/shared/state/updateSlice';
import { LIST_MODES } from '@/shared/state/modesSlice';
@@ -33,7 +34,7 @@ export function useSettings() {
useEffect(() => { if (loaded) setForm({ ...settings }); }, [loaded, settings]);
const hasChanges = JSON.stringify(form) !== JSON.stringify(settings);
const handleSave = async () => {
- await dispatch(updateSettings(form));
+ await dispatch(UPDATE_SETTINGS(form));
if (form.theme !== settings.theme) setThemeMode(form.theme);
dispatch(fetchModels());
setSaved(true);
@@ -48,7 +49,7 @@ export function useSettings() {
dispatch(closeSettingsModal());
}, [settings, dispatch]);
const handleSaveAndClose = useCallback(async () => {
- await dispatch(updateSettings(form));
+ await dispatch(UPDATE_SETTINGS(form));
if (form.theme !== settings.theme) setThemeMode(form.theme);
dispatch(fetchModels());
setSaved(true);
diff --git a/frontend/src/shared/backend-bridge/apps/settings.ts b/frontend/src/shared/backend-bridge/apps/settings.ts
index 62ba96dd..afcdc022 100644
--- a/frontend/src/shared/backend-bridge/apps/settings.ts
+++ b/frontend/src/shared/backend-bridge/apps/settings.ts
@@ -3,19 +3,51 @@ import { API_BASE } from '@/shared/backend-bridge/base_routes';
const SETTINGS_API: string = `${API_BASE}/settings`;
+// ---------------------------------------------------------------------------
+// Types
+// ---------------------------------------------------------------------------
+
+export interface CustomProvider {
+ name: string;
+ base_url: string;
+ api_key: string;
+ models: Array<{ value: string; label: string; context_window?: number }>;
+}
+
+export interface AppSettings {
+ default_system_prompt: string | null;
+ default_folder: string | null;
+ default_model: string;
+ default_mode: string;
+ default_max_turns: number | null;
+ zoom_sensitivity: number;
+ theme: 'light' | 'dark';
+ new_agent_shortcut: string;
+ anthropic_api_key: string | null;
+ openai_api_key?: string | null;
+ google_api_key?: string | null;
+ openrouter_api_key?: string | null;
+ custom_providers?: CustomProvider[];
+ browser_homepage: string;
+ auto_select_mode_on_new_agent: boolean;
+ expand_new_chats_in_dashboard: boolean;
+ auto_reveal_sub_agents: boolean;
+ dev_mode: boolean;
+}
+
// ---------------------------------------------------------------------------
// Settings
// ---------------------------------------------------------------------------
const get_settings_endpoint: string = SETTINGS_API;
-async function get_settings_function(): Promise> {
+async function get_settings_function(): Promise {
const res = await fetch(get_settings_endpoint, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});
const data = await res.json();
- return data as Record;
+ return data as AppSettings;
}
export const GET_SETTINGS = createAsyncThunk(
get_settings_endpoint,
@@ -24,29 +56,14 @@ export const GET_SETTINGS = createAsyncThunk(
const update_settings_endpoint: string = SETTINGS_API;
-async function update_settings_function(body: {
- default_system_prompt?: string;
- default_folder?: string | null;
- default_model?: string;
- default_mode?: string;
- default_max_turns?: number | null;
- anthropic_api_key?: string | null;
- zoom_sensitivity?: number;
- theme?: string;
- new_agent_shortcut?: string;
- browser_homepage?: string;
- auto_select_mode_on_new_agent?: boolean;
- expand_new_chats_in_dashboard?: boolean;
- auto_reveal_sub_agents?: boolean;
- dev_mode?: boolean;
-}): Promise<{ ok: boolean; settings: Record }> {
+async function update_settings_function(body: Partial): Promise<{ ok: boolean; settings: AppSettings }> {
const res = await fetch(update_settings_endpoint, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const data = await res.json();
- return data as { ok: boolean; settings: Record };
+ return data as { ok: boolean; settings: AppSettings };
}
export const UPDATE_SETTINGS = createAsyncThunk(
update_settings_endpoint,
@@ -55,13 +72,13 @@ export const UPDATE_SETTINGS = createAsyncThunk(
const reset_system_prompt_endpoint: string = `${SETTINGS_API}/reset-system-prompt`;
-async function reset_system_prompt_function(): Promise<{ ok: boolean; settings: Record }> {
+async function reset_system_prompt_function(): Promise<{ ok: boolean; settings: AppSettings }> {
const res = await fetch(reset_system_prompt_endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
const data = await res.json();
- return data as { ok: boolean; settings: Record };
+ return data as { ok: boolean; settings: AppSettings };
}
export const RESET_SYSTEM_PROMPT = createAsyncThunk(
reset_system_prompt_endpoint,
diff --git a/frontend/src/shared/state/settingsSlice.ts b/frontend/src/shared/state/settingsSlice.ts
index 28ebad27..b01800dd 100644
--- a/frontend/src/shared/state/settingsSlice.ts
+++ b/frontend/src/shared/state/settingsSlice.ts
@@ -1,7 +1,10 @@
-import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
-import { API_BASE } from '@/shared/config';
-
-const SETTINGS_API = `${API_BASE}/settings`;
+import { createSlice } from '@reduxjs/toolkit';
+import {
+ GET_SETTINGS,
+ UPDATE_SETTINGS,
+ RESET_SYSTEM_PROMPT,
+} from '@/shared/backend-bridge/apps/settings';
+import type { AppSettings } from '@/shared/backend-bridge/apps/settings';
export const DEFAULT_SYSTEM_PROMPT =
`You are a personal AI assistant running inside OpenSwarm.\n\n` +
@@ -20,34 +23,6 @@ export const DEFAULT_SYSTEM_PROMPT =
`make reasonable assumptions and act. If you need to ask, use the AskUserQuestion tool.\n` +
`Do not over-explain what you are about to do. Just do it and show the results.`;
-export interface CustomProvider {
- name: string;
- base_url: string;
- api_key: string;
- models: Array<{ value: string; label: string; context_window?: number }>;
-}
-
-export interface AppSettings {
- default_system_prompt: string | null;
- default_folder: string | null;
- default_model: string;
- default_mode: string;
- default_max_turns: number | null;
- zoom_sensitivity: number;
- theme: 'light' | 'dark';
- new_agent_shortcut: string;
- anthropic_api_key: string | null;
- openai_api_key?: string | null;
- google_api_key?: string | null;
- openrouter_api_key?: string | null;
- custom_providers?: CustomProvider[];
- browser_homepage: string;
- auto_select_mode_on_new_agent: boolean;
- expand_new_chats_in_dashboard: boolean;
- auto_reveal_sub_agents: boolean;
- dev_mode: boolean;
-}
-
export interface BrowseResult {
current: string;
parent: string | null;
@@ -84,33 +59,6 @@ const initialState: SettingsState = {
modalOpen: false,
};
-export const fetchSettings = createAsyncThunk('settings/fetch', async () => {
- const res = await fetch(SETTINGS_API);
- return (await res.json()) as AppSettings;
-});
-
-export const updateSettings = createAsyncThunk(
- 'settings/update',
- async (settings: AppSettings) => {
- const res = await fetch(SETTINGS_API, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(settings),
- });
- const data = await res.json();
- return data.settings as AppSettings;
- }
-);
-
-export const resetSystemPrompt = createAsyncThunk(
- 'settings/resetSystemPrompt',
- async () => {
- const res = await fetch(`${SETTINGS_API}/reset-system-prompt`, { method: 'POST' });
- const data = await res.json();
- return data.settings as AppSettings;
- }
-);
-
const settingsSlice = createSlice({
name: 'settings',
initialState,
@@ -124,23 +72,23 @@ const settingsSlice = createSlice({
},
extraReducers: (builder) => {
builder
- .addCase(fetchSettings.pending, (state) => {
+ .addCase(GET_SETTINGS.pending, (state) => {
state.loading = true;
})
- .addCase(fetchSettings.fulfilled, (state, action) => {
+ .addCase(GET_SETTINGS.fulfilled, (state, action) => {
state.loading = false;
state.loaded = true;
state.data = action.payload;
})
- .addCase(fetchSettings.rejected, (state) => {
+ .addCase(GET_SETTINGS.rejected, (state) => {
state.loading = false;
state.loaded = true;
})
- .addCase(updateSettings.fulfilled, (state, action) => {
- state.data = action.payload;
+ .addCase(UPDATE_SETTINGS.fulfilled, (state, action) => {
+ state.data = action.payload.settings;
})
- .addCase(resetSystemPrompt.fulfilled, (state, action) => {
- state.data = action.payload;
+ .addCase(RESET_SYSTEM_PROMPT.fulfilled, (state, action) => {
+ state.data = action.payload.settings;
});
},
});