(function exposeApiClient(global) { "use strict"; let csrfTokenSupplier = () => ""; let unauthorizedHandler = () => {}; class ApiError extends Error { constructor(message, status = 0, payload = null) { super(message); this.name = "ApiError"; this.status = status; this.payload = payload; } } function configure(options = {}) { if (typeof options.csrfToken === "function") csrfTokenSupplier = options.csrfToken; if (typeof options.onUnauthorized === "function") unauthorizedHandler = options.onUnauthorized; } function requestOptions(method, body, signal) { const normalizedMethod = String(method || "GET").toUpperCase(); const options = { method: normalizedMethod, headers: {}, signal }; const csrfToken = csrfTokenSupplier(); if (!["GET", "HEAD", "OPTIONS"].includes(normalizedMethod) && csrfToken) { options.headers["X-CSRF-Token"] = csrfToken; } if (body !== null && body !== undefined) { options.headers["Content-Type"] = "application/json"; options.body = JSON.stringify(body); } return options; } async function parseJson(response) { try { return await response.json(); } catch (_error) { return {}; } } function handleUnauthorized(response, url) { if (response.status === 401 && !String(url).startsWith("/api/auth/")) { unauthorizedHandler({ response, url }); } } async function request(url, method = "GET", body = null, options = {}) { const response = await fetch(url, requestOptions(method, body, options.signal)); const payload = await parseJson(response); handleUnauthorized(response, url); if (!response.ok || payload.error) { throw new ApiError(payload.error || "请求失败", response.status, payload); } return payload; } async function streamNdjson(url, options = {}) { const response = await fetch( url, requestOptions(options.method || "POST", options.body, options.signal), ); if (!response.ok) { const payload = await parseJson(response); handleUnauthorized(response, url); throw new ApiError( payload.error || options.errorMessage || "流式请求暂不可用", response.status, payload, ); } if (!response.body) throw new ApiError("当前浏览器不支持流式回答", response.status); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; const consume = (line) => { if (!line.trim()) return; let event; try { event = JSON.parse(line); } catch (_error) { throw new ApiError("流式响应格式错误", response.status); } if (event.type === "error") { throw new ApiError(event.error || options.errorMessage || "流式请求失败", response.status, event); } options.onEvent?.(event); }; while (true) { const { value, done } = await reader.read(); buffer += decoder.decode(value || new Uint8Array(), { stream: !done }); const lines = buffer.split("\n"); buffer = lines.pop() || ""; lines.forEach(consume); if (done) break; } if (buffer.trim()) consume(buffer); } global.XiaobaiAPI = Object.freeze({ ApiError, configure, request, streamNdjson }); })(window);