Files
zhimingge/lib/ai/client-stream.ts
T
dekun 206673fd90 Fix AI output showing UTF-8 bytes as hex escapes instead of Chinese.
Decode <0xE5><0xA7><0xA4> style model output to proper characters; add prompt rule to use normal Chinese text.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 23:10:09 +08:00

59 lines
1.5 KiB
TypeScript

import type { AiRequestBody } from "@/lib/ai/types";
import { decodeHexByteEscapes } from "@/lib/ai/decode-text";
function emitText(text: string, onUpdate: (text: string) => void) {
onUpdate(decodeHexByteEscapes(text));
}
function parseApiError(text: string, status: number): string {
const trimmed = text.trim();
if (
trimmed.startsWith("<!DOCTYPE") ||
trimmed.startsWith("<html") ||
trimmed.includes("<title>404")
) {
return `AI 接口未到达后端 (${status})。请确认 Nginx 反代到 3130 且包含 /api/ 路径。`;
}
return trimmed.slice(0, 800) || `AI 请求失败 (${status})`;
}
export async function streamAiCompletion(
body: AiRequestBody,
onUpdate: (text: string) => void,
): Promise<void> {
const res = await fetch("/api/ai", {
method: "POST",
cache: "no-store",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error(parseApiError(await res.text(), res.status));
}
if (!res.body) {
throw new Error("AI 响应为空");
}
const reader = res.body.getReader();
const decoder = new TextDecoder();
let text = "";
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
text += decoder.decode(value, { stream: true });
emitText(text, onUpdate);
}
text += decoder.decode();
emitText(text, onUpdate);
if (!text.trim()) {
throw new Error("AI 返回内容为空,请检查模型配置或稍后重试");
}
}