import { create } from 'zustand'; import { hostApiFetch } from '@/lib/host-api'; import type { ProjectConfig } from '../../shared/project-config'; type ProjectConfigResponse = { status: 'valid' | 'missing' | 'invalid'; config?: ProjectConfig; error?: string; knowledgeFiles: string[]; }; type ProjectConfigState = { configsByProjectId: Record; knowledgeByProjectId: Record; loadingProjectId: string | null; errorsByProjectId: Record; load: (projectId: string) => Promise; save: (projectId: string, config: ProjectConfig) => Promise; uploadKnowledge: (projectId: string, file: File) => Promise; remove: (projectId: string) => void; }; export const useProjectConfigStore = create((set) => ({ configsByProjectId: {}, knowledgeByProjectId: {}, loadingProjectId: null, errorsByProjectId: {}, async load(projectId) { set({ loadingProjectId: projectId }); try { const response = await hostApiFetch(`/api/opencode/projects/config?projectId=${encodeURIComponent(projectId)}`); set((state) => ({ loadingProjectId: null, configsByProjectId: response.status === 'valid' && response.config ? { ...state.configsByProjectId, [projectId]: response.config } : state.configsByProjectId, knowledgeByProjectId: { ...state.knowledgeByProjectId, [projectId]: response.knowledgeFiles ?? [] }, errorsByProjectId: response.status === 'invalid' ? { ...state.errorsByProjectId, [projectId]: response.error ?? '项目配置无效' } : Object.fromEntries(Object.entries(state.errorsByProjectId).filter(([id]) => id !== projectId)), })); return response; } catch (error) { const message = error instanceof Error ? error.message : String(error); set((state) => ({ loadingProjectId: null, errorsByProjectId: { ...state.errorsByProjectId, [projectId]: message } })); throw error; } }, async save(projectId, config) { const response = await hostApiFetch<{ success: boolean; config?: ProjectConfig; knowledgeFiles?: string[]; error?: string }>('/api/opencode/projects/config', { method: 'PUT', body: JSON.stringify({ projectId, config }), }); if (!response.success || !response.config) throw new Error(response.error || '保存项目配置失败'); set((state) => ({ configsByProjectId: { ...state.configsByProjectId, [projectId]: response.config as ProjectConfig }, knowledgeByProjectId: { ...state.knowledgeByProjectId, [projectId]: response.knowledgeFiles ?? [] }, errorsByProjectId: Object.fromEntries(Object.entries(state.errorsByProjectId).filter(([id]) => id !== projectId)), })); return response.config; }, async uploadKnowledge(projectId, file) { const bytes = new Uint8Array(await file.arrayBuffer()); let binary = ''; for (let offset = 0; offset < bytes.length; offset += 0x8000) { binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000)); } const response = await hostApiFetch<{ success: boolean; knowledgeFiles?: string[]; error?: string }>('/api/opencode/projects/knowledge', { method: 'POST', body: JSON.stringify({ projectId, fileName: file.name, contentBase64: btoa(binary) }), }); if (!response.success) throw new Error(response.error || '知识文件上传失败'); set((state) => ({ knowledgeByProjectId: { ...state.knowledgeByProjectId, [projectId]: response.knowledgeFiles ?? [] } })); }, remove(projectId) { set((state) => ({ configsByProjectId: Object.fromEntries(Object.entries(state.configsByProjectId).filter(([id]) => id !== projectId)), knowledgeByProjectId: Object.fromEntries(Object.entries(state.knowledgeByProjectId).filter(([id]) => id !== projectId)), errorsByProjectId: Object.fromEntries(Object.entries(state.errorsByProjectId).filter(([id]) => id !== projectId)), })); }, }));