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.
101 lines
3.1 KiB
TypeScript
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()
|
|
},
|
|
}
|