"use client"; export class ApiError extends Error { status: number; code: string; detail: unknown; constructor(status: number, message: string, code = "ERROR", detail?: unknown) { super(message); this.status = status; this.code = code; this.detail = detail; } } async function request(method: string, path: string, body?: unknown, init?: RequestInit): Promise { const headers: Record = { Accept: "application/json" }; if (method !== "GET" && method !== "HEAD") headers["X-LLM-CSRF"] = "1"; if (body !== undefined) headers["Content-Type"] = "application/json"; const res = await fetch(path, { method, headers, credentials: "same-origin", body: body !== undefined ? JSON.stringify(body) : undefined, ...init, }); const text = await res.text(); let data: unknown = null; try { data = text ? JSON.parse(text) : null; } catch { data = text; } if (!res.ok) { const err = (data as { error?: { message?: string; code?: string } })?.error; if (res.status === 401 && typeof window !== "undefined" && !location.pathname.startsWith("/login")) { location.href = `/login?next=${encodeURIComponent(location.pathname)}`; } throw new ApiError(res.status, err?.message || `HTTP ${res.status}`, err?.code || "ERROR", data); } return data as T; } export const api = { get: (path: string) => request("GET", path), post: (path: string, body?: unknown) => request("POST", path, body), put: (path: string, body?: unknown) => request("PUT", path, body), patch: (path: string, body?: unknown) => request("PATCH", path, body), del: (path: string, body?: unknown) => request("DELETE", path, body), }; /** Stream an OpenAI chat completion from the local API using the admin session. */ export async function* streamChat(body: Record, signal?: AbortSignal): AsyncGenerator> { const res = await fetch("/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", "X-LLM-CSRF": "1" }, credentials: "same-origin", body: JSON.stringify({ ...body, stream: true }), signal, }); if (!res.ok || !res.body) { const t = await res.text(); let msg = t; try { msg = JSON.parse(t).error?.message || t; } catch {} throw new ApiError(res.status, msg); } const reader = res.body.getReader(); const dec = new TextDecoder(); let buf = ""; while (true) { const { value, done } = await reader.read(); if (done) break; buf += dec.decode(value, { stream: true }); let idx: number; while ((idx = buf.indexOf("\n\n")) >= 0) { const chunk = buf.slice(0, idx); buf = buf.slice(idx + 2); for (const line of chunk.split("\n")) { if (!line.startsWith("data: ")) continue; const payload = line.slice(6).trim(); if (payload === "[DONE]") return; try { yield JSON.parse(payload); } catch {} } } } }