Spaces:
Sleeping
Sleeping
File size: 4,423 Bytes
f65e025 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | import type {
Classification,
DocumentDetail,
DocumentMeta,
ExtractionResult,
HealthInfo,
} from "./types";
// All requests go through Next's rewrite proxy to the FastAPI backend.
const BASE = "";
async function json<T>(res: Response): Promise<T> {
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`${res.status} ${res.statusText}: ${text}`);
}
return res.json() as Promise<T>;
}
export const api = {
health: () => fetch(`${BASE}/api/health`).then((r) => json<HealthInfo>(r)),
listDocuments: () =>
fetch(`${BASE}/api/documents`).then((r) => json<DocumentMeta[]>(r)),
getDocument: (id: string) =>
fetch(`${BASE}/api/documents/${id}`).then((r) => json<DocumentDetail>(r)),
upload: (file: File) => {
const fd = new FormData();
fd.append("file", file);
return fetch(`${BASE}/api/documents`, { method: "POST", body: fd }).then(
(r) => json<DocumentMeta>(r),
);
},
deleteDocument: (id: string) =>
fetch(`${BASE}/api/documents/${id}`, { method: "DELETE" }).then((r) =>
json<{ ok: boolean }>(r),
),
reExtract: (id: string, schema?: string, provider?: string) => {
const q = new URLSearchParams();
if (schema) q.set("schema", schema);
if (provider) q.set("provider", provider);
return fetch(`${BASE}/api/documents/${id}/extract?${q}`, {
method: "POST",
}).then((r) => json<ExtractionResult>(r));
},
reClassify: (id: string, provider?: string) => {
const q = new URLSearchParams();
if (provider) q.set("provider", provider);
return fetch(`${BASE}/api/documents/${id}/classify?${q}`, {
method: "POST",
}).then((r) => json<Classification>(r));
},
reSummarize: (id: string, provider?: string) => {
const q = new URLSearchParams();
if (provider) q.set("provider", provider);
return fetch(`${BASE}/api/documents/${id}/summarize?${q}`, {
method: "POST",
}).then((r) => json<{ summary: string }>(r));
},
exportUrl: (id: string, format: string) =>
`${BASE}/api/documents/${id}/export?format=${format}`,
schemas: () =>
fetch(`${BASE}/api/documents/-/schemas`).then((r) =>
json<{ schemas: string[] }>(r),
),
};
// --------------------------- streaming chat ---------------------------
export interface ChatStreamHandlers {
onToken: (text: string) => void;
onCitations: (citations: import("./types").Citation[]) => void;
onDone: () => void;
onError: (msg: string) => void;
}
/**
* POST to /api/chat and parse the SSE stream manually (EventSource only
* supports GET, so we read the response body as a stream ourselves).
*/
export async function streamChat(
body: {
document_id?: string | null;
messages: { role: string; content: string }[];
provider?: string | null;
},
handlers: ChatStreamHandlers,
signal?: AbortSignal,
) {
try {
const res = await fetch(`${BASE}/api/chat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
signal,
});
if (!res.ok || !res.body) {
throw new Error(`Chat request failed: ${res.status}`);
}
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// SSE frames are separated by a blank line
const frames = buffer.split("\n\n");
buffer = frames.pop() ?? "";
for (const frame of frames) {
let event = "message";
let data = "";
for (const line of frame.split("\n")) {
if (line.startsWith("event:")) event = line.slice(6).trim();
else if (line.startsWith("data:")) data += line.slice(5).trim();
}
if (!data) continue;
let parsed: any;
try {
parsed = JSON.parse(data);
} catch {
continue;
}
if (event === "token") handlers.onToken(parsed.text ?? "");
else if (event === "citations")
handlers.onCitations(parsed.citations ?? []);
else if (event === "done") handlers.onDone();
else if (event === "error") handlers.onError(parsed.message ?? "error");
}
}
handlers.onDone();
} catch (e: any) {
if (e?.name !== "AbortError") handlers.onError(e?.message ?? String(e));
}
}
|