115 lines
5.1 KiB
TypeScript
115 lines
5.1 KiB
TypeScript
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<string, CodingProjectConfig>;
|
|
knowledgeByProjectId: Record<string, string[]>;
|
|
loadingProjectId: string | null;
|
|
errorsByProjectId: Record<string, string>;
|
|
load: (projectId: string) => Promise<ProjectConfigResponse>;
|
|
save: (projectId: string, config: CodingProjectConfig) => Promise<CodingProjectConfig>;
|
|
resolveIdentity: (projectId: string, identity: ProjectIdentityChoice) => Promise<CodingProjectConfig>;
|
|
makeIndependentCopy: (projectId: string) => Promise<CodingProjectConfig>;
|
|
uploadKnowledge: (projectId: string, file: File) => Promise<void>;
|
|
remove: (projectId: string) => void;
|
|
};
|
|
|
|
export const useProjectConfigStore = create<ProjectConfigState>((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)),
|
|
}));
|
|
},
|
|
}));
|