anhkhoiphan's picture
deploy: 2026-06-04 13:41:54
f104cdd
Raw
History Blame Contribute Delete
2.87 kB
import type {
ChatResponse,
DeleteResponse,
DocumentsResponse,
IngestResponse,
} from "./types";
// FastAPI backend base URL for the admin endpoints (called directly from the
// browser; the backend enables CORS for this origin). Exposed to the client,
// so it must be prefixed with NEXT_PUBLIC_.
const API_BASE = (
process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:8080"
).replace(/\/+$/, "");
const configuredIngestTimeoutMs = Number(
process.env.NEXT_PUBLIC_INGEST_TIMEOUT_MS || 600_000,
);
const INGEST_TIMEOUT_MS =
Number.isFinite(configuredIngestTimeoutMs) && configuredIngestTimeoutMs > 0
? configuredIngestTimeoutMs
: 600_000;
/** Parse a JSON response, throwing a readable error on failure. */
async function jsonOrThrow<T>(res: Response): Promise<T> {
if (!res.ok) {
let message = `Yêu cầu thất bại (mã ${res.status})`;
try {
const body = await res.json();
// Next.js routes use { error }, FastAPI uses { detail }.
message = body?.error || body?.detail || message;
} catch {
// non-JSON error body — keep the status message
}
throw new Error(message);
}
return res.json() as Promise<T>;
}
/** Chat goes through the Next.js proxy (`/api/chat` → backend `POST /chat`).
* Accepts an optional AbortSignal so the store can cancel an in-flight request
* when its session is deleted. */
export async function sendChat(
input: {
user_id: string;
session_id: string;
query: string;
},
signal?: AbortSignal,
): Promise<ChatResponse> {
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(input),
signal,
});
return jsonOrThrow<ChatResponse>(res);
}
export async function listDocuments(): Promise<DocumentsResponse> {
const res = await fetch(`${API_BASE}/documents`, { cache: "no-store" });
return jsonOrThrow<DocumentsResponse>(res);
}
export async function ingestPdf(file: File): Promise<IngestResponse> {
const form = new FormData();
form.append("file", file);
try {
const res = await fetch(`${API_BASE}/ingest`, {
method: "POST",
body: form,
signal: AbortSignal.timeout(INGEST_TIMEOUT_MS),
});
return jsonOrThrow<IngestResponse>(res);
} catch (err) {
if (err instanceof Error && err.name === "TimeoutError") {
const minutes = Math.round(INGEST_TIMEOUT_MS / 60_000);
throw new Error(
`Backend xử lý upload quá lâu (quá ${minutes} phút). Thử tăng NEXT_PUBLIC_INGEST_TIMEOUT_MS nếu tệp lớn hoặc Qdrant Cloud chậm.`,
);
}
throw err;
}
}
export async function deleteDocument(id: string): Promise<DeleteResponse> {
const res = await fetch(`${API_BASE}/documents/${encodeURIComponent(id)}`, {
method: "DELETE",
});
return jsonOrThrow<DeleteResponse>(res);
}