cc-haha/desktop/src/stores/uiStore.ts
程序员阿江(Relakkes) d9ce2f09e1 refactor(desktop): move trace entry into Settings
Trace is a low-frequency diagnostic feature, so its entry points no
longer sit in primary chrome:

- Remove the Trace nav item above the session list in the sidebar
- Remove the trace open/open-window buttons from the session header
- Add a Trace tab in Settings between Token usage and Diagnostics,
  embedding the existing TraceList page full-bleed
- Add settings.tab.trace and drop unused sidebar.traces across locales
2026-06-11 01:42:40 +08:00

121 lines
3.3 KiB
TypeScript

import { create } from 'zustand'
import { isThemeMode, THEME_MODES, type ThemeMode } from '../types/settings'
const THEME_STORAGE_KEY = 'cc-haha-theme'
function getStoredTheme(): ThemeMode {
try {
const stored = localStorage.getItem(THEME_STORAGE_KEY)
if (isThemeMode(stored)) return stored
} catch { /* localStorage unavailable */ }
return 'white'
}
export function applyTheme(theme: ThemeMode) {
if (typeof document === 'undefined') return
document.documentElement.setAttribute('data-theme', theme)
document.documentElement.style.colorScheme = theme === 'dark' ? 'dark' : 'light'
}
export function initializeTheme() {
applyTheme(getStoredTheme())
}
export type Toast = {
id: string
type: 'success' | 'error' | 'warning' | 'info'
message: string
duration?: number
}
export type SettingsTab =
| 'providers'
| 'activity'
| 'general'
| 'h5Access'
| 'adapters'
| 'terminal'
| 'mcp'
| 'agents'
| 'skills'
| 'memory'
| 'plugins'
| 'computerUse'
| 'trace'
| 'diagnostics'
| 'about'
type ActiveView = 'code' | 'scheduled' | 'terminal' | 'history' | 'settings'
type UIStore = {
theme: ThemeMode
sidebarOpen: boolean
activeView: ActiveView
pendingSettingsTab: SettingsTab | null
pendingMemoryPath: string | null
activeModal: string | null
toasts: Toast[]
setTheme: (theme: ThemeMode) => void
toggleTheme: () => void
toggleSidebar: () => void
setSidebarOpen: (open: boolean) => void
setActiveView: (view: ActiveView) => void
setPendingSettingsTab: (tab: SettingsTab | null) => void
setPendingMemoryPath: (path: string | null) => void
openModal: (id: string) => void
closeModal: () => void
addToast: (toast: Omit<Toast, 'id'>) => void
removeToast: (id: string) => void
}
let toastCounter = 0
export const useUIStore = create<UIStore>((set) => ({
theme: getStoredTheme(),
sidebarOpen: true,
activeView: 'code',
pendingSettingsTab: null,
pendingMemoryPath: null,
activeModal: null,
toasts: [],
setTheme: (theme) => {
applyTheme(theme)
try { localStorage.setItem(THEME_STORAGE_KEY, theme) } catch { /* noop */ }
set({ theme })
},
toggleTheme: () => {
set((state) => {
const currentIndex = THEME_MODES.indexOf(state.theme)
const next = THEME_MODES[(currentIndex + 1) % THEME_MODES.length] ?? 'white'
applyTheme(next)
try { localStorage.setItem(THEME_STORAGE_KEY, next) } catch { /* noop */ }
return { theme: next }
})
},
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
setSidebarOpen: (open) => set({ sidebarOpen: open }),
setActiveView: (view) => set({ activeView: view }),
setPendingSettingsTab: (tab) => set({ pendingSettingsTab: tab }),
setPendingMemoryPath: (path) => set({ pendingMemoryPath: path }),
openModal: (id) => set({ activeModal: id }),
closeModal: () => set({ activeModal: null }),
addToast: (toast) => {
const id = `toast-${++toastCounter}`
set((s) => ({ toasts: [...s.toasts, { ...toast, id }] }))
// Auto-remove after duration
const duration = toast.duration ?? 4000
if (duration > 0) {
setTimeout(() => {
set((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) }))
}, duration)
}
},
removeToast: (id) => set((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) })),
}))