import { create } from 'zustand'; import { hostApiFetch } from '@/lib/host-api'; import { makeCodingProjectIndependentCopy, resolveCodingProjectIdentity, } from '@/lib/coding-projects'; import type { CodingProjectConfig, CodingProjectConfigSnapshot } from '@/types/coding-project'; import type { ProjectIdentityChoice } from '../../shared/coding-project-contracts'; type ProjectConfigResponse = { status: 'valid' | 'missing' | 'invalid'; config?: CodingProjectConfig; error?: string; knowledgeFiles: string[]; }; type ProjectConfigState = { configsByProjectId: Record; knowledgeByProjectId: Record; loadingProjectId: string | null; errorsByProjectId: Record; load: (projectId: string) => Promise; save: (projectId: string, config: CodingProjectConfig) => Promise; resolveIdentity: (projectId: string, identity: ProjectIdentityChoice) => Promise; makeIndependentCopy: (projectId: string) => 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<{ snapshot: CodingProjectConfigSnapshot }>( `/api/coding/projects/config?projectId=${encodeURIComponent(projectId)}`, ); const result: ProjectConfigResponse = { status: 'valid', config: response.snapshot.config, knowledgeFiles: response.snapshot.knowledgeFiles, }; set((state) => ({ loadingProjectId: null, configsByProjectId: { ...state.configsByProjectId, [projectId]: response.snapshot.config }, knowledgeByProjectId: { ...state.knowledgeByProjectId, [projectId]: response.snapshot.knowledgeFiles }, errorsByProjectId: Object.fromEntries(Object.entries(state.errorsByProjectId).filter(([id]) => id !== projectId)), })); return result; } 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<{ snapshot: CodingProjectConfigSnapshot }>('/api/coding/projects/config', { method: 'PUT', body: JSON.stringify({ projectId, config }), }); set((state) => ({ configsByProjectId: { ...state.configsByProjectId, [projectId]: response.snapshot.config }, knowledgeByProjectId: { ...state.knowledgeByProjectId, [projectId]: response.snapshot.knowledgeFiles }, errorsByProjectId: Object.fromEntries(Object.entries(state.errorsByProjectId).filter(([id]) => id !== projectId)), })); return response.snapshot.config; }, async resolveIdentity(projectId, identity) { const snapshot = await resolveCodingProjectIdentity(projectId, identity); set((state) => ({ configsByProjectId: { ...state.configsByProjectId, [projectId]: snapshot.config }, knowledgeByProjectId: { ...state.knowledgeByProjectId, [projectId]: snapshot.knowledgeFiles }, errorsByProjectId: Object.fromEntries(Object.entries(state.errorsByProjectId).filter(([id]) => id !== projectId)), })); return snapshot.config; }, async makeIndependentCopy(projectId) { const snapshot = await makeCodingProjectIndependentCopy(projectId); set((state) => ({ configsByProjectId: { ...state.configsByProjectId, [projectId]: snapshot.config }, knowledgeByProjectId: { ...state.knowledgeByProjectId, [projectId]: snapshot.knowledgeFiles }, errorsByProjectId: Object.fromEntries(Object.entries(state.errorsByProjectId).filter(([id]) => id !== projectId)), })); return snapshot.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<{ knowledgeFiles: string[] }>('/api/coding/projects/knowledge', { method: 'POST', body: JSON.stringify({ projectId, fileName: file.name, contentBase64: btoa(binary) }), }); 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)), })); }, }));