mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
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
121 lines
3.3 KiB
TypeScript
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) })),
|
|
}))
|