export const API_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://127.0.0.1:8000"; export type Subject = { id: number; name: string; slug: string; created_at: string }; export type Week = { id: number; subject_id: number; number: number; title?: string | null; created_at: string }; export type Source = { id: number; week_id: number; original_name: string; stored_path: string; source_type: string; status: string; file_size?: number | null; sha256?: string | null; mime_type?: string | null; processor?: string | null; page_ranges?: string | null; processed_at?: string | null; document_id?: number | null; document_title?: string | null; document_markdown_path?: string | null; document_created_at?: string | null; created_at: string; }; export type Job = { id: number; status: string; message: string; source_id?: number | null; document_id?: number | null; created_at: string; updated_at: string }; export type DocumentItem = { id: number; source_id: number; title: string; markdown_path: string; source_original_name?: string | null; source_type?: string | null; source_status?: string | null; source_processor?: string | null; source_page_ranges?: string | null; created_at: string; }; export type SettingsStatus = { mistral_configured: boolean; deepgram_configured: boolean; data_dir: string; database_path: string }; async function parseResponse(response: Response): Promise { if (!response.ok) { const detail = await response.json().catch(() => null); throw new Error(detail?.detail || `Error ${response.status}`); } return response.json() as Promise; } export const api = { async health() { return parseResponse<{ status: string }>(await fetch(`${API_URL}/health`, { cache: "no-store" })); }, async settings() { return parseResponse(await fetch(`${API_URL}/settings/status`, { cache: "no-store" })); }, async subjects() { return parseResponse(await fetch(`${API_URL}/subjects`, { cache: "no-store" })); }, async subject(id: number) { return parseResponse(await fetch(`${API_URL}/subjects/${id}`, { cache: "no-store" })); }, async createSubject(name: string) { return parseResponse(await fetch(`${API_URL}/subjects`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }), })); }, async weeks(subjectId: number) { return parseResponse(await fetch(`${API_URL}/subjects/${subjectId}/weeks`, { cache: "no-store" })); }, async week(subjectId: number, weekNumber: number) { return parseResponse(await fetch(`${API_URL}/subjects/${subjectId}/weeks/${weekNumber}`, { cache: "no-store" })); }, async createWeek(subjectId: number, number: number, title: string) { return parseResponse(await fetch(`${API_URL}/subjects/${subjectId}/weeks`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ number, title: title || null }), })); }, async updateWeek(subjectId: number, currentNumber: number, number: number, title: string) { return parseResponse(await fetch(`${API_URL}/subjects/${subjectId}/weeks/${currentNumber}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ number, title: title || null }), })); }, async sources(subjectId: number, weekNumber: number) { return parseResponse(await fetch(`${API_URL}/subjects/${subjectId}/weeks/${weekNumber}/sources`, { cache: "no-store" })); }, async jobs(subjectId: number, weekNumber: number) { return parseResponse(await fetch(`${API_URL}/subjects/${subjectId}/weeks/${weekNumber}/jobs`, { cache: "no-store" })); }, async documents(subjectId: number, weekNumber: number) { return parseResponse(await fetch(`${API_URL}/subjects/${subjectId}/weeks/${weekNumber}/documents`, { cache: "no-store" })); }, async deleteSource(sourceId: number) { return parseResponse<{ status: string }>(await fetch(`${API_URL}/sources/${sourceId}`, { method: "DELETE" })); }, async deleteDocument(documentId: number) { return parseResponse<{ status: string }>(await fetch(`${API_URL}/documents/${documentId}`, { method: "DELETE" })); }, async reprocessSource(sourceId: number, useOcr: boolean, pageRanges?: string) { const params = new URLSearchParams({ use_ocr: String(useOcr) }); if (pageRanges?.trim()) params.set("page_ranges", pageRanges.trim()); return parseResponse<{ source: Source; job: Job }>(await fetch(`${API_URL}/sources/${sourceId}/reprocess?${params.toString()}`, { method: "POST" })); }, async upload(subjectId: number, weekNumber: number, file: File, useOcr: boolean, pageRanges?: string) { const form = new FormData(); form.append("file", file); const params = new URLSearchParams({ use_ocr: String(useOcr) }); if (pageRanges?.trim()) params.set("page_ranges", pageRanges.trim()); return parseResponse<{ source: Source; job: Job }>(await fetch(`${API_URL}/subjects/${subjectId}/weeks/${weekNumber}/files?${params.toString()}`, { method: "POST", body: form, })); }, async markdown(documentId: number) { const response = await fetch(`${API_URL}/documents/${documentId}/markdown`, { cache: "no-store" }); if (!response.ok) throw new Error(`Error ${response.status}`); return response.text(); }, async document(documentId: number) { return parseResponse(await fetch(`${API_URL}/documents/${documentId}`, { cache: "no-store" })); }, async saveMarkdown(documentId: number, content: string) { return parseResponse(await fetch(`${API_URL}/documents/${documentId}/markdown`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ content }), })); }, downloadDocumentUrl(documentId: number) { return `${API_URL}/documents/${documentId}/download`; }, exportWeekUrl(subjectId: number, weekNumber: number) { return `${API_URL}/subjects/${subjectId}/weeks/${weekNumber}/export`; }, exportWeekZipUrl(subjectId: number, weekNumber: number) { return `${API_URL}/subjects/${subjectId}/weeks/${weekNumber}/export.zip`; }, };