// web/src/api.ts const BASE = import.meta.env.VITE_API_BASE ?? "" export interface Document { id: string title: string file_path: string status: "pending" | "processing" | "ready" | "error" task_id: string | null page_count: number | null created_at: string } export interface SearchResult { chunk_id: string doc_id: string page_number: number text_snippet: string bm25_score: number } export interface Citation { doc_id: string page_number: number snippet: string bm25_score: number | null } export interface ChatResponse { answer: string citations: Citation[] } export interface TaskStatus { status: string progress?: number error?: string } export interface DocumentStatus { id: string status: "pending" | "processing" | "ready" | "error" task_id: string | null page_count: number | null vec_count: number error_msg: string | null } export interface ChatMessage { role: string content: string } export const api = { async getLibrary(): Promise { const r = await fetch(`${BASE}/api/library`) if (!r.ok) throw new Error(await r.text()) return r.json() }, async scanLibrary(): Promise<{ discovered: number; queued: number; tasks: { doc_id: string; task_id: string }[] }> { const r = await fetch(`${BASE}/api/library/scan`, { method: "POST" }) if (!r.ok) throw new Error(await r.text()) return r.json() }, async reshelveDocument(docId: string): Promise<{ task_id: string }> { const r = await fetch(`${BASE}/api/library/${docId}/reshelve`, { method: "POST" }) if (!r.ok) throw new Error(await r.text()) return r.json() }, async deleteDocument(docId: string): Promise { const r = await fetch(`${BASE}/api/library/${docId}`, { method: "DELETE" }) if (!r.ok) throw new Error(await r.text()) }, async uploadDocument(file: File): Promise<{ doc_id: string; task_id: string | null; filename: string; status: string }> { const form = new FormData() form.append("file", file) const r = await fetch(`${BASE}/api/library/upload`, { method: "POST", body: form }) if (!r.ok) throw new Error(await r.text()) return r.json() }, async getTaskStatus(taskId: string): Promise { const r = await fetch(`${BASE}/api/shelve/${taskId}`) if (!r.ok) throw new Error(await r.text()) return r.json() }, async getDocumentStatus(docId: string): Promise { const r = await fetch(`${BASE}/api/library/${docId}/status`) if (!r.ok) throw new Error(await r.text()) return r.json() }, async search(query: string, topK = 10, docIds?: string[]): Promise { const r = await fetch(`${BASE}/api/search`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query, top_k: topK, doc_ids: docIds ?? null }), }) if (!r.ok) throw new Error(await r.text()) return r.json() }, async chat( message: string, history: ChatMessage[], docIds?: string[], topK = 5, ): Promise { const r = await fetch(`${BASE}/api/chat`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message, history, doc_ids: docIds ?? null, top_k: topK }), }) if (!r.ok) { const body = await r.json().catch(() => ({})) const err: Error & { status?: number; detail?: unknown } = new Error( (body as { detail?: { message?: string } }).detail?.message ?? "Request failed" ) err.status = r.status err.detail = (body as { detail?: unknown }).detail throw err } return r.json() }, async chatFeedbackStatus(): Promise<{ enabled: boolean }> { const r = await fetch(`${BASE}/api/chat/feedback/status`) if (!r.ok) return { enabled: false } return r.json() }, async submitChatFeedback( rating: 1 | -1, question: string, answer: string, docIds: string[], ): Promise { await fetch(`${BASE}/api/chat/feedback`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ rating, question, answer, doc_ids: docIds }), }) }, }