cc-haha/desktop/src/stores/sessionStore.ts
Relakkes Yang 5d243b657a feat(desktop): Windows 自定义标题栏 & 服务端改进
- Windows 上隐藏原生菜单栏和窗口装饰,前端渲染自定义窗口控制按钮(最小化/最大化/关闭)
- macOS 保持原生菜单栏不变(条件编译)
- Sidebar 在 Windows 上不再添加 macOS 红绿灯的额外顶部间距
- 新增 Windows 构建脚本和 NSIS 安装钩子
- 服务端 CORS、会话服务、对话服务改进及测试补充

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 21:29:35 +08:00

103 lines
3.1 KiB
TypeScript

import { create } from 'zustand'
import { sessionsApi } from '../api/sessions'
import type { SessionListItem } from '../types/session'
type SessionStore = {
sessions: SessionListItem[]
activeSessionId: string | null
isLoading: boolean
error: string | null
selectedProjects: string[]
availableProjects: string[]
fetchSessions: (project?: string) => Promise<void>
createSession: (workDir?: string) => Promise<string>
deleteSession: (id: string) => Promise<void>
renameSession: (id: string, title: string) => Promise<void>
updateSessionTitle: (id: string, title: string) => void
setActiveSession: (id: string | null) => void
setSelectedProjects: (projects: string[]) => void
}
export const useSessionStore = create<SessionStore>((set, get) => ({
sessions: [],
activeSessionId: null,
isLoading: false,
error: null,
selectedProjects: [],
availableProjects: [],
fetchSessions: async (project?: string) => {
set({ isLoading: true, error: null })
try {
const { sessions: raw } = await sessionsApi.list({ project, limit: 100 })
// Deduplicate by session ID — keep the most recently modified entry
const byId = new Map<string, SessionListItem>()
for (const s of raw) {
const existing = byId.get(s.id)
if (!existing || new Date(s.modifiedAt) > new Date(existing.modifiedAt)) {
byId.set(s.id, s)
}
}
const sessions = [...byId.values()]
const availableProjects = [...new Set(sessions.map((s) => s.projectPath).filter(Boolean))].sort()
set({ sessions, availableProjects, isLoading: false })
} catch (err) {
set({ error: (err as Error).message, isLoading: false })
}
},
createSession: async (workDir?: string) => {
const { sessionId: id } = await sessionsApi.create(workDir || undefined)
const now = new Date().toISOString()
const optimisticSession: SessionListItem = {
id,
title: 'New Session',
createdAt: now,
modifiedAt: now,
messageCount: 0,
projectPath: '',
workDir: workDir ?? null,
workDirExists: true,
}
set((state) => ({
sessions: state.sessions.some((session) => session.id === id)
? state.sessions
: [optimisticSession, ...state.sessions],
activeSessionId: id,
}))
void get().fetchSessions()
return id
},
deleteSession: async (id: string) => {
await sessionsApi.delete(id)
set((s) => ({
sessions: s.sessions.filter((session) => session.id !== id),
activeSessionId: s.activeSessionId === id ? null : s.activeSessionId,
}))
},
renameSession: async (id: string, title: string) => {
await sessionsApi.rename(id, title)
set((s) => ({
sessions: s.sessions.map((session) =>
session.id === id ? { ...session, title } : session,
),
}))
},
updateSessionTitle: (id, title) => {
set((s) => ({
sessions: s.sessions.map((session) =>
session.id === id ? { ...session, title } : session,
),
}))
},
setActiveSession: (id) => set({ activeSessionId: id }),
setSelectedProjects: (projects) => set({ selectedProjects: projects }),
}))