From a7f0ea520fee8e08ef37fce1e904dcfe22cd8cc9 Mon Sep 17 00:00:00 2001 From: haikdc Date: Sat, 18 Apr 2026 05:15:16 -0700 Subject: [PATCH] =?UTF-8?q?[hAIk]:=20integrate=20settings=20backend-bridge?= =?UTF-8?q?=20into=20frontend:=20move=20AppSettings=20and=20CustomProvider?= =?UTF-8?q?=20interfaces=20into=20settings=20bridge=20with=20proper=20retu?= =?UTF-8?q?rn=20types,=20switch=20Main.tsx=20to=20GET=5FSETTINGS,=20useSet?= =?UTF-8?q?tings.ts=20to=20UPDATE=5FSETTINGS,=20GeneralTab.tsx=20to=20RESE?= =?UTF-8?q?T=5FSYSTEM=5FPROMPT=20=E2=80=94=20all=20importing=20directly=20?= =?UTF-8?q?from=20the=20bridge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/Main.tsx | 4 +- .../src/app/pages/Settings/GeneralTab.tsx | 5 +- .../app/pages/Settings/hooks/useSettings.ts | 7 +- .../shared/backend-bridge/apps/settings.ts | 59 +++++++++----- frontend/src/shared/state/settingsSlice.ts | 80 ++++--------------- 5 files changed, 61 insertions(+), 94 deletions(-) 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; }); }, });