pagepiper/web/src/api.ts
pyr0ball 6fc8e7faa6 fix: wire bm25_score through Citation so Natural 20 easter egg fires
Citation dataclass gains bm25_score field populated from the retrieved
chunk. chat.py serializes it. api.ts interface updated to include it.
ChatView passes :bm25-score to CitationPanel so the Nat20 threshold
check in onMounted actually has data to evaluate.
2026-05-04 20:01:20 -07:00

101 lines
3.1 KiB
TypeScript

// 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 ChatMessage {
role: string
content: string
}
export const api = {
async getLibrary(): Promise<Document[]> {
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 reingestDocument(docId: string): Promise<{ task_id: string }> {
const r = await fetch(`${BASE}/api/library/${docId}/reingest`, { method: "POST" })
if (!r.ok) throw new Error(await r.text())
return r.json()
},
async deleteDocument(docId: string): Promise<void> {
const r = await fetch(`${BASE}/api/library/${docId}`, { method: "DELETE" })
if (!r.ok) throw new Error(await r.text())
},
async getTaskStatus(taskId: string): Promise<TaskStatus> {
const r = await fetch(`${BASE}/api/ingest/${taskId}`)
if (!r.ok) throw new Error(await r.text())
return r.json()
},
async search(query: string, topK = 10, docIds?: string[]): Promise<SearchResult[]> {
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<ChatResponse> {
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()
},
}