From a0312deaf541689ca041c2114c119924d5bb0906 Mon Sep 17 00:00:00 2001 From: haikdc Date: Sat, 18 Apr 2026 04:35:33 -0700 Subject: [PATCH] [hAIk]: integrate modes backend-bridge into frontend: add Mode type to bridge with proper typing, rewrite modesSlice to import thunks from backend-bridge (matching dashboards pattern), update useModes hook to use LIST_MODES/CREATE_MODE/UPDATE_MODE/RESET_MODE/DELETE_MODE, update useAgentChat/ModelModeSelector/useSettings to use LIST_MODES, remove legacy @/shared/config dependency from modes --- .../app/pages/AgentChat/ModelModeSelector.tsx | 4 +- .../app/pages/AgentChat/hooks/useAgentChat.ts | 4 +- .../src/app/pages/Modes/hooks/useModes.ts | 22 ++--- .../app/pages/Settings/hooks/useSettings.ts | 4 +- .../src/shared/backend-bridge/apps/modes.ts | 44 +++++---- frontend/src/shared/state/modesSlice.ts | 93 ++++--------------- 6 files changed, 64 insertions(+), 107 deletions(-) diff --git a/frontend/src/app/pages/AgentChat/ModelModeSelector.tsx b/frontend/src/app/pages/AgentChat/ModelModeSelector.tsx index e8d96734..21439cd9 100644 --- a/frontend/src/app/pages/AgentChat/ModelModeSelector.tsx +++ b/frontend/src/app/pages/AgentChat/ModelModeSelector.tsx @@ -20,7 +20,7 @@ import AttachFileIcon from '@mui/icons-material/AttachFile'; import AdsClickIcon from '@mui/icons-material/AdsClick'; import { useElementSelection } from '@/app/components/ElementSelectionContext'; import { useAppDispatch, useAppSelector } from '@/shared/hooks'; -import { fetchModes } from '@/shared/state/modesSlice'; +import { LIST_MODES } from '@/shared/state/modesSlice'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; import ContextRing from './ContextRing'; @@ -69,7 +69,7 @@ const ModelModeSelector: React.FC = ({ const [modeAnchor, setModeAnchor] = useState(null); const [modelAnchor, setModelAnchor] = useState(null); - useEffect(() => { if (modesArr.length === 0) dispatch(fetchModes()); }, [dispatch, modesArr.length]); + useEffect(() => { if (modesArr.length === 0) dispatch(LIST_MODES()); }, [dispatch, modesArr.length]); const allModelOptions = useMemo(() => { if (!modelsLoaded || Object.keys(modelsByProvider).length === 0) { diff --git a/frontend/src/app/pages/AgentChat/hooks/useAgentChat.ts b/frontend/src/app/pages/AgentChat/hooks/useAgentChat.ts index 35da5d25..2754a320 100644 --- a/frontend/src/app/pages/AgentChat/hooks/useAgentChat.ts +++ b/frontend/src/app/pages/AgentChat/hooks/useAgentChat.ts @@ -11,7 +11,7 @@ import { META_LAUNCH_AND_SEND } from '@/shared/backend-bridge/apps/agents'; import { updateSessionMode, updateSessionModel } from '@/shared/state/agentsSlice'; -import { fetchModes } from '@/shared/state/modesSlice'; +import { LIST_MODES } from '@/shared/state/modesSlice'; import { setGlowingBrowserCards, fadeGlowingBrowserCards, clearGlowingBrowserCards } from '@/shared/state/dashboardLayoutSlice'; export interface QueuedMessage { @@ -48,7 +48,7 @@ export function useAgentChat({ sessionId: sessionIdProp }: UseAgentChatParams) { dispatch(GET_SESSION(id)); }, [id, isDraft, dispatch]); - useEffect(() => { if (Object.keys(modesMap).length === 0) dispatch(fetchModes()); }, [dispatch, modesMap]); + useEffect(() => { if (Object.keys(modesMap).length === 0) dispatch(LIST_MODES()); }, [dispatch, modesMap]); const sessionSystemPrompt = session?.system_prompt; const sessionTargetDirectory = session?.target_directory; diff --git a/frontend/src/app/pages/Modes/hooks/useModes.ts b/frontend/src/app/pages/Modes/hooks/useModes.ts index 12b12dc9..e5713d4f 100644 --- a/frontend/src/app/pages/Modes/hooks/useModes.ts +++ b/frontend/src/app/pages/Modes/hooks/useModes.ts @@ -1,11 +1,11 @@ import { useEffect, useState, useMemo } from 'react'; import { useAppDispatch, useAppSelector } from '@/shared/hooks'; import { - fetchModes, - createMode, - updateMode, - deleteMode, - resetMode, + LIST_MODES, + CREATE_MODE, + UPDATE_MODE, + DELETE_MODE, + RESET_MODE, Mode, } from '@/shared/state/modesSlice'; import { fetchBuiltinTools, fetchTools } from '@/shared/state/toolsSlice'; @@ -30,7 +30,7 @@ export function useModes() { const [browseOpen, setBrowseOpen] = useState(false); useEffect(() => { - dispatch(fetchModes()); + dispatch(LIST_MODES()); dispatch(fetchBuiltinTools()); dispatch(fetchTools()); dispatch(fetchSkills()); @@ -71,15 +71,15 @@ export function useModes() { }; if (editingId) { - await dispatch(updateMode({ id: editingId, ...payload })); + await dispatch(UPDATE_MODE({ modeId: editingId, ...payload })); } else { - await dispatch(createMode(payload as any)); + await dispatch(CREATE_MODE(payload as any)); } setDialogOpen(false); }; const handleDelete = async (id: string) => { - await dispatch(deleteMode(id)); + await dispatch(DELETE_MODE(id)); }; const editingIsBuiltin = editingId ? items[editingId]?.is_builtin ?? false : false; @@ -104,8 +104,8 @@ export function useModes() { const handleReset = async () => { if (!editingId) return; - const action = await dispatch(resetMode(editingId)); - if (resetMode.fulfilled.match(action)) { + const action = await dispatch(RESET_MODE(editingId)); + if (RESET_MODE.fulfilled.match(action)) { const m = action.payload; setForm({ name: m.name, diff --git a/frontend/src/app/pages/Settings/hooks/useSettings.ts b/frontend/src/app/pages/Settings/hooks/useSettings.ts index 0ec20a7d..0e1a9a58 100644 --- a/frontend/src/app/pages/Settings/hooks/useSettings.ts +++ b/frontend/src/app/pages/Settings/hooks/useSettings.ts @@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from '@/shared/hooks'; import { updateSettings, closeSettingsModal, AppSettings } from '@/shared/state/settingsSlice'; import { fetchModels } from '@/shared/state/modelsSlice'; import { setChecking, setUpdateError } from '@/shared/state/updateSlice'; -import { fetchModes } from '@/shared/state/modesSlice'; +import { LIST_MODES } from '@/shared/state/modesSlice'; import { useClaudeTokens, useThemeMode } from '@/shared/styles/ThemeContext'; export function useSettings() { @@ -28,7 +28,7 @@ export function useSettings() { const [recordingShortcut, setRecordingShortcut] = useState(false); const [confirmDiscard, setConfirmDiscard] = useState(false); const [showApiHelp, setShowApiHelp] = useState(false); - useEffect(() => { dispatch(fetchModes()); }, [dispatch]); + useEffect(() => { dispatch(LIST_MODES()); }, [dispatch]); useEffect(() => { if (open) setActiveTab('general'); }, [open]); useEffect(() => { if (loaded) setForm({ ...settings }); }, [loaded, settings]); const hasChanges = JSON.stringify(form) !== JSON.stringify(settings); diff --git a/frontend/src/shared/backend-bridge/apps/modes.ts b/frontend/src/shared/backend-bridge/apps/modes.ts index 9bd4ebda..7ee9b4bc 100644 --- a/frontend/src/shared/backend-bridge/apps/modes.ts +++ b/frontend/src/shared/backend-bridge/apps/modes.ts @@ -3,19 +3,32 @@ import { API_BASE } from '@/shared/backend-bridge/base_routes'; const MODES_API: string = `${API_BASE}/modes`; +export interface Mode { + id: string; + name: string; + description: string; + system_prompt: string | null; + tools: string[] | null; + default_next_mode: string | null; + is_builtin: boolean; + icon: string; + color: string; + default_folder: string | null; +} + // --------------------------------------------------------------------------- // Mode CRUD // --------------------------------------------------------------------------- const list_modes_endpoint: string = `${MODES_API}/list`; -async function list_modes_function(): Promise<{ modes: Record[]; builtin_defaults: Record> }> { +async function list_modes_function(): Promise<{ modes: Mode[]; builtin_defaults: Record }> { const res = await fetch(list_modes_endpoint, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); const data = await res.json(); - return data as { modes: Record[]; builtin_defaults: Record> }; + return data as { modes: Mode[]; builtin_defaults: Record }; } export const LIST_MODES = createAsyncThunk( list_modes_endpoint, @@ -24,13 +37,13 @@ export const LIST_MODES = createAsyncThunk( const get_mode_endpoint: string = `${MODES_API}/get`; -async function get_mode_function(modeId: string): Promise> { +async function get_mode_function(modeId: string): Promise { const res = await fetch(`${MODES_API}/${modeId}`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); const data = await res.json(); - return data as Record; + return data as Mode; } export const GET_MODE = createAsyncThunk( get_mode_endpoint, @@ -48,14 +61,14 @@ async function create_mode_function(body: { icon?: string; color?: string; default_folder?: string | null; -}): Promise<{ ok: boolean; mode: Record }> { +}): Promise { const res = await fetch(create_mode_endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); const data = await res.json(); - return data as { ok: boolean; mode: Record }; + return data.mode as Mode; } export const CREATE_MODE = createAsyncThunk( create_mode_endpoint, @@ -74,7 +87,7 @@ async function update_mode_function(args: { icon?: string; color?: string; default_folder?: string | null; -}): Promise<{ ok: boolean; mode: Record }> { +}): Promise { const { modeId, ...updates } = args; const res = await fetch(`${MODES_API}/${modeId}`, { method: 'PUT', @@ -82,7 +95,7 @@ async function update_mode_function(args: { body: JSON.stringify(updates), }); const data = await res.json(); - return data as { ok: boolean; mode: Record }; + return data.mode as Mode; } export const UPDATE_MODE = createAsyncThunk( update_mode_endpoint, @@ -91,13 +104,13 @@ export const UPDATE_MODE = createAsyncThunk( const reset_mode_endpoint: string = `${MODES_API}/reset`; -async function reset_mode_function(modeId: string): Promise<{ ok: boolean; mode: Record }> { +async function reset_mode_function(modeId: string): Promise { const res = await fetch(`${MODES_API}/${modeId}/reset`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, }); const data = await res.json(); - return data as { ok: boolean; mode: Record }; + return data.mode as Mode; } export const RESET_MODE = createAsyncThunk( reset_mode_endpoint, @@ -106,13 +119,12 @@ export const RESET_MODE = createAsyncThunk( const delete_mode_endpoint: string = `${MODES_API}/delete`; -async function delete_mode_function(modeId: string): Promise<{ ok: boolean }> { - const res = await fetch(`${MODES_API}/${modeId}`, { +async function delete_mode_function(modeId: string): Promise { + await fetch(`${MODES_API}/${modeId}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, }); - const data = await res.json(); - return data as { ok: boolean }; + return modeId; } export const DELETE_MODE = createAsyncThunk( delete_mode_endpoint, @@ -121,14 +133,14 @@ export const DELETE_MODE = createAsyncThunk( const get_mode_by_id_endpoint: string = `${MODES_API}/get_mode_by_id`; -async function get_mode_by_id_function(modeId: string): Promise | null> { +async function get_mode_by_id_function(modeId: string): Promise { const res = await fetch(get_mode_by_id_endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode_id: modeId }), }); const data = await res.json(); - return data as Record | null; + return data as Mode | null; } export const GET_MODE_BY_ID = createAsyncThunk( get_mode_by_id_endpoint, diff --git a/frontend/src/shared/state/modesSlice.ts b/frontend/src/shared/state/modesSlice.ts index 9a294d64..bbca1255 100644 --- a/frontend/src/shared/state/modesSlice.ts +++ b/frontend/src/shared/state/modesSlice.ts @@ -1,20 +1,15 @@ -import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; -import { API_BASE } from '@/shared/config'; +import { createSlice } from '@reduxjs/toolkit'; +import { + LIST_MODES, + CREATE_MODE, + UPDATE_MODE, + RESET_MODE, + DELETE_MODE, +} from '@/shared/backend-bridge/apps/modes'; +import type { Mode } from '@/shared/backend-bridge/apps/modes'; -const MODES_API = `${API_BASE}/modes`; - -export interface Mode { - id: string; - name: string; - description: string; - system_prompt: string | null; - tools: string[] | null; - default_next_mode: string | null; - is_builtin: boolean; - icon: string; - color: string; - default_folder: string | null; -} +export type { Mode }; +export { LIST_MODES, CREATE_MODE, UPDATE_MODE, RESET_MODE, DELETE_MODE }; interface ModesState { items: Record; @@ -25,75 +20,25 @@ interface ModesState { const initialState: ModesState = { items: {}, builtinDefaults: {}, loading: false, loaded: false }; -export const fetchModes = createAsyncThunk( - 'modes/fetch', - async () => { - const res = await fetch(`${MODES_API}/list`); - const data = await res.json(); - return { modes: data.modes as Mode[], builtinDefaults: (data.builtin_defaults ?? {}) as Record }; - }, - { condition: (_, { getState }) => !(getState() as { modes: ModesState }).modes.loading }, -); - -export const createMode = createAsyncThunk( - 'modes/create', - async (body: Omit) => { - const res = await fetch(`${MODES_API}/create`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - }); - const data = await res.json(); - return data.mode as Mode; - } -); - -export const updateMode = createAsyncThunk( - 'modes/update', - async ({ id, ...updates }: Partial & { id: string }) => { - const res = await fetch(`${MODES_API}/${id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(updates), - }); - const data = await res.json(); - return data.mode as Mode; - } -); - -export const resetMode = createAsyncThunk( - 'modes/reset', - async (id: string) => { - const res = await fetch(`${MODES_API}/${id}/reset`, { method: 'POST' }); - const data = await res.json(); - return data.mode as Mode; - } -); - -export const deleteMode = createAsyncThunk('modes/delete', async (id: string) => { - await fetch(`${MODES_API}/${id}`, { method: 'DELETE' }); - return id; -}); - const modesSlice = createSlice({ name: 'modes', initialState, reducers: {}, extraReducers: (builder) => { builder - .addCase(fetchModes.pending, (state) => { state.loading = true; }) - .addCase(fetchModes.fulfilled, (state, action) => { + .addCase(LIST_MODES.pending, (state) => { state.loading = true; }) + .addCase(LIST_MODES.fulfilled, (state, action) => { state.loading = false; state.loaded = true; state.items = {}; for (const m of action.payload.modes) state.items[m.id] = m; - state.builtinDefaults = action.payload.builtinDefaults; + state.builtinDefaults = action.payload.builtin_defaults; }) - .addCase(fetchModes.rejected, (state) => { state.loading = false; state.loaded = true; }) - .addCase(createMode.fulfilled, (state, action) => { state.items[action.payload.id] = action.payload; }) - .addCase(updateMode.fulfilled, (state, action) => { state.items[action.payload.id] = action.payload; }) - .addCase(resetMode.fulfilled, (state, action) => { state.items[action.payload.id] = action.payload; }) - .addCase(deleteMode.fulfilled, (state, action) => { delete state.items[action.payload]; }); + .addCase(LIST_MODES.rejected, (state) => { state.loading = false; state.loaded = true; }) + .addCase(CREATE_MODE.fulfilled, (state, action) => { state.items[action.payload.id] = action.payload; }) + .addCase(UPDATE_MODE.fulfilled, (state, action) => { state.items[action.payload.id] = action.payload; }) + .addCase(RESET_MODE.fulfilled, (state, action) => { state.items[action.payload.id] = action.payload; }) + .addCase(DELETE_MODE.fulfilled, (state, action) => { delete state.items[action.payload]; }); }, });