Files
ln-bi/src/modules/assets/assistant/client.ts
T

65 lines
1.9 KiB
TypeScript

import { fetchAuthenticated } from "../../../auth/api-client";
import type {
AssistantEvent,
AssistantRequest,
} from "../../../shared/assets/assistant";
export class AssistantRequestError extends Error {
constructor(
message: string,
public status: number,
) {
super(message);
this.name = "AssistantRequestError";
}
}
export async function askAssetAssistant(
input: AssistantRequest,
signal: AbortSignal,
onEvent: (event: AssistantEvent) => void,
) {
const response = await fetchAuthenticated("/api/asset-assistant/ask", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(input),
signal,
});
if (!response.ok) {
const body = await response.json().catch(() => null);
throw new AssistantRequestError(
body?.error || "查询服务暂时不可用。",
response.status,
);
}
if (!response.body) throw new Error("当前浏览器无法读取查询进度。");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "",
completed = false;
try {
while (true) {
const { value, done } = await reader.read();
buffer += decoder.decode(value, { stream: !done });
const frames = buffer.split(/\r?\n\r?\n/);
buffer = frames.pop() || "";
for (const frame of frames) {
const data = frame
.split(/\r?\n/)
.filter((line) => line.startsWith("data:"))
.map((line) => line.slice(5).trim())
.join("\n");
if (!data) continue;
const event = JSON.parse(data) as AssistantEvent;
if (event.type === "result" || event.type === "error") completed = true;
onEvent(event);
}
if (done) break;
}
if (!completed && !signal.aborted)
throw new Error("连接中断,请重新提问。");
} finally {
reader.releaseLock();
}
}