cc-haha/desktop/src/stores/memoryStore.ts
程序员阿江(Relakkes) d11748d61a fix(desktop): make memory files preview-first (#533)
Tested: cd desktop && bun run test src/__tests__/memorySettings.test.tsx
Tested: bun run check:desktop
Scope-risk: narrow
Confidence: high
2026-06-09 16:39:17 +08:00

141 lines
4.1 KiB
TypeScript

import { create } from 'zustand'
import { memoryApi } from '../api/memory'
import type { MemoryFile, MemoryFileDetail, MemoryProject } from '../types/memory'
type MemoryStore = {
projects: MemoryProject[]
files: MemoryFile[]
selectedProjectId: string | null
selectedFile: MemoryFileDetail | null
draftContent: string
isLoadingProjects: boolean
isLoadingFiles: boolean
isLoadingFile: boolean
isSaving: boolean
error: string | null
lastSavedAt: string | null
fetchProjects: (cwd?: string) => Promise<void>
selectProject: (projectId: string) => void
fetchFiles: (projectId: string) => Promise<void>
openFile: (projectId: string, path: string) => Promise<void>
updateDraft: (content: string) => void
saveFile: () => Promise<boolean>
createFile: (projectId: string, path: string, content: string) => Promise<void>
}
export const useMemoryStore = create<MemoryStore>((set, get) => ({
projects: [],
files: [],
selectedProjectId: null,
selectedFile: null,
draftContent: '',
isLoadingProjects: false,
isLoadingFiles: false,
isLoadingFile: false,
isSaving: false,
error: null,
lastSavedAt: null,
fetchProjects: async (cwd) => {
set({ isLoadingProjects: true, error: null })
try {
const { projects } = await memoryApi.listProjects(cwd)
const current = projects.find((project) => project.isCurrent)
const selectedProjectId =
get().selectedProjectId && projects.some((project) => project.id === get().selectedProjectId)
? get().selectedProjectId
: current?.id ?? projects[0]?.id ?? null
set({ projects, selectedProjectId, isLoadingProjects: false })
} catch (err) {
set({ error: (err as Error).message, isLoadingProjects: false })
}
},
selectProject: (projectId) => {
set({
selectedProjectId: projectId,
files: [],
selectedFile: null,
draftContent: '',
error: null,
lastSavedAt: null,
})
},
fetchFiles: async (projectId) => {
set({ isLoadingFiles: true, error: null })
try {
const { files } = await memoryApi.listFiles(projectId)
set((state) => {
const stillSelected = state.selectedFile && files.some((file) => file.path === state.selectedFile?.path)
return {
files,
selectedFile: stillSelected ? state.selectedFile : null,
draftContent: stillSelected ? state.draftContent : '',
isLoadingFiles: false,
}
})
} catch (err) {
set({ error: (err as Error).message, isLoadingFiles: false })
}
},
openFile: async (projectId, path) => {
set({ isLoadingFile: true, error: null })
try {
const { file } = await memoryApi.readFile(projectId, path)
set({
selectedFile: file,
draftContent: file.content,
isLoadingFile: false,
lastSavedAt: null,
})
} catch (err) {
set({ error: (err as Error).message, isLoadingFile: false })
}
},
updateDraft: (content) => set({ draftContent: content }),
saveFile: async () => {
const { selectedProjectId, selectedFile, draftContent } = get()
if (!selectedProjectId || !selectedFile) return false
set({ isSaving: true, error: null })
try {
const { file } = await memoryApi.saveFile({
projectId: selectedProjectId,
path: selectedFile.path,
content: draftContent,
})
set({
selectedFile: {
...selectedFile,
updatedAt: file.updatedAt,
bytes: file.bytes,
content: draftContent,
},
isSaving: false,
lastSavedAt: file.updatedAt,
})
await get().fetchFiles(selectedProjectId)
return true
} catch (err) {
set({ error: (err as Error).message, isSaving: false })
return false
}
},
createFile: async (projectId, path, content) => {
set({ isSaving: true, error: null })
try {
await memoryApi.saveFile({ projectId, path, content })
set({ isSaving: false })
await get().fetchFiles(projectId)
await get().openFile(projectId, path)
} catch (err) {
set({ error: (err as Error).message, isSaving: false })
}
},
}))