[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

This commit is contained in:
haikdc
2026-04-18 04:35:33 -07:00
parent 650c42077a
commit a0312deaf5
6 changed files with 64 additions and 107 deletions
@@ -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<Props> = ({
const [modeAnchor, setModeAnchor] = useState<HTMLElement | null>(null);
const [modelAnchor, setModelAnchor] = useState<HTMLElement | null>(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) {
@@ -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;
+11 -11
View File
@@ -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,
@@ -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);
@@ -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<string, unknown>[]; builtin_defaults: Record<string, Record<string, unknown>> }> {
async function list_modes_function(): Promise<{ modes: Mode[]; builtin_defaults: Record<string, Mode> }> {
const res = await fetch(list_modes_endpoint, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});
const data = await res.json();
return data as { modes: Record<string, unknown>[]; builtin_defaults: Record<string, Record<string, unknown>> };
return data as { modes: Mode[]; builtin_defaults: Record<string, Mode> };
}
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<Record<string, unknown>> {
async function get_mode_function(modeId: string): Promise<Mode> {
const res = await fetch(`${MODES_API}/${modeId}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});
const data = await res.json();
return data as Record<string, unknown>;
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<string, unknown> }> {
}): Promise<Mode> {
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<string, unknown> };
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<string, unknown> }> {
}): Promise<Mode> {
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<string, unknown> };
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<string, unknown> }> {
async function reset_mode_function(modeId: string): Promise<Mode> {
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<string, unknown> };
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<string> {
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<Record<string, unknown> | null> {
async function get_mode_by_id_function(modeId: string): Promise<Mode | null> {
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<string, unknown> | null;
return data as Mode | null;
}
export const GET_MODE_BY_ID = createAsyncThunk(
get_mode_by_id_endpoint,
+19 -74
View File
@@ -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<string, Mode>;
@@ -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<string, Mode> };
},
{ condition: (_, { getState }) => !(getState() as { modes: ModesState }).modes.loading },
);
export const createMode = createAsyncThunk(
'modes/create',
async (body: Omit<Mode, 'id' | 'is_builtin'>) => {
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<Mode> & { 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]; });
},
});