| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- import { apiBaseUrl, apiFetch, buildAuthHeaders, refreshAccessToken, notifyAuthError, extractErrorMessage } from "./client.js";
- export async function sendChatStream(message, { conversationId, mode, onChunk, onSources, onStatus, onDone, onError, signal } = {}) {
- const body = JSON.stringify({ message, ...(conversationId ? { conversationId } : {}), ...(mode ? { mode } : {}) });
- const doFetch = () =>
- fetch(`${apiBaseUrl}/api/chat/stream`, {
- method: "POST",
- headers: buildAuthHeaders(),
- body,
- signal
- });
- let res;
- try {
- res = await doFetch();
- if (res.status === 401) {
- let newToken = null;
- try {
- newToken = await refreshAccessToken();
- } catch {
- notifyAuthError();
- onError?.(new Error("session_expired"));
- return;
- }
- if (newToken) res = await doFetch();
- }
- } catch (e) {
- if (e?.name === "AbortError") return;
- onError?.(e);
- return;
- }
- if (res.status === 401) {
- notifyAuthError();
- onError?.(new Error("session_expired"));
- return;
- }
- if (!res.ok) {
- const text = await res.text().catch(() => "");
- onError?.(new Error(text || `http_error:${res.status}`));
- return;
- }
- const reader = res.body.getReader();
- const decoder = new TextDecoder();
- let buffer = "";
- const STREAM_INACTIVITY_TIMEOUT_MS = 45_000;
- function readWithTimeout() {
- return new Promise((resolve, reject) => {
- const timer = setTimeout(() => reject(new Error("stream_inactivity_timeout")), STREAM_INACTIVITY_TIMEOUT_MS);
- reader.read().then(
- (result) => { clearTimeout(timer); resolve(result); },
- (err) => { clearTimeout(timer); reject(err); }
- );
- });
- }
- try {
- while (true) {
- const { done, value } = await readWithTimeout();
- if (done) break;
- buffer += decoder.decode(value, { stream: true });
- const lines = buffer.split("\n");
- buffer = lines.pop() ?? "";
- for (const line of lines) {
- if (!line.startsWith("data: ")) continue;
- const payload = line.slice(6);
- if (payload === "[DONE]") { onDone?.(); return; }
- try {
- const parsed = JSON.parse(payload);
- if (parsed.type === "delta") onChunk?.(parsed.delta);
- else if (parsed.type === "sources") onSources?.(parsed.sources);
- else if (parsed.type === "status") onStatus?.(parsed);
- else if (parsed.type === "error") onError?.(new Error(parsed.error));
- } catch {}
- }
- }
- } catch (e) {
- if (e?.name === "AbortError") return;
- if (e?.message === "stream_inactivity_timeout") {
- reader.cancel().catch(() => {});
- onError?.(new Error("A resposta demorou demais e foi interrompida."));
- return;
- }
- onError?.(e);
- }
- onDone?.();
- }
- export async function ingestDocuments(documents) {
- return apiFetch("/api/ingest", {
- method: "POST",
- body: { documents }
- });
- }
- function sendIngestFile(file, { source, onProgress } = {}) {
- return new Promise((resolve, reject) => {
- const form = new FormData();
- form.append("file", file);
- if (source) form.append("source", source);
- const xhr = new XMLHttpRequest();
- xhr.upload.onprogress = (e) => {
- if (e.lengthComputable) onProgress?.(Math.round((e.loaded / e.total) * 100));
- };
- xhr.onload = () => {
- if (xhr.status >= 200 && xhr.status < 300) {
- try {
- resolve(JSON.parse(xhr.responseText));
- } catch {
- resolve(xhr.responseText);
- }
- return;
- }
- let payload = null;
- try { payload = JSON.parse(xhr.responseText); } catch {}
- const err = new Error(extractErrorMessage(payload, xhr.status));
- err.status = xhr.status;
- reject(err);
- };
- xhr.onerror = () => reject(new Error("network_error"));
- xhr.ontimeout = () => reject(new Error("timeout"));
- xhr.open("POST", `${apiBaseUrl}/api/ingest/file`);
- const authHeaders = buildAuthHeaders();
- if (authHeaders["Authorization"]) xhr.setRequestHeader("Authorization", authHeaders["Authorization"]);
- xhr.send(form);
- });
- }
- export async function ingestFile(file, { source, onProgress } = {}) {
- try {
- return await sendIngestFile(file, { source, onProgress });
- } catch (err) {
- if (err.status !== 401) throw err;
- try {
- await refreshAccessToken();
- } catch {
- notifyAuthError();
- throw new Error("session_expired");
- }
- return sendIngestFile(file, { source, onProgress });
- }
- }
- export async function ingestUrl(url, { source } = {}) {
- return apiFetch("/api/ingest/url", {
- method: "POST",
- body: { url, ...(source ? { source } : {}) }
- });
- }
- export async function search(query) {
- return apiFetch("/api/search", {
- method: "POST",
- body: { query }
- });
- }
- export async function listDocuments({ limit = 50, offset = 0 } = {}) {
- const params = new URLSearchParams({ limit: String(limit), offset: String(offset) }).toString();
- return apiFetch(`/api/documents?${params}`);
- }
- export async function deleteDocumentsBySource(source) {
- return apiFetch(`/api/documents/source/${encodeURIComponent(source)}`, {
- method: "DELETE"
- });
- }
|