2025-08-14 23:26:49 +08:00
|
|
|
import { getTokenKeySessionStorage } from "@/utils/auth";
|
2025-08-14 15:43:24 +08:00
|
|
|
|
|
|
|
// 使用环境变量配置基础URL
|
2025-08-14 23:26:49 +08:00
|
|
|
const BASE_URL = process.env.VUE_APP_API_BASE_URL || "http://localhost:8088";
|
2025-08-14 15:43:24 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 创建聊天流式连接
|
|
|
|
* @param {Object} params 请求参数
|
|
|
|
* @param {string} params.prompt 用户输入
|
|
|
|
* @param {string} params.userId 用户ID
|
|
|
|
* @param {string} params.userName 用户名
|
|
|
|
* @param {string} [params.conversationId] 会话ID
|
|
|
|
* @returns {Object} 包含stream和cancel方法的对象
|
|
|
|
*/
|
|
|
|
export function createChatStream(params) {
|
2025-08-14 23:26:49 +08:00
|
|
|
const requestId = `req-${Date.now()}-${Math.random()
|
|
|
|
.toString(36)
|
|
|
|
.slice(2, 8)}`;
|
|
|
|
const url = `${BASE_URL}/aitutor/aichat/stream`;
|
|
|
|
const token = getTokenKeySessionStorage();
|
2025-08-14 15:43:24 +08:00
|
|
|
|
2025-08-14 23:26:49 +08:00
|
|
|
if (!token) {
|
|
|
|
throw new Error("请先登录");
|
|
|
|
}
|
2025-08-14 15:43:24 +08:00
|
|
|
|
2025-08-14 23:26:49 +08:00
|
|
|
const controller = new AbortController();
|
2025-08-14 15:43:24 +08:00
|
|
|
|
2025-08-14 23:26:49 +08:00
|
|
|
const fetchPromise = fetch(url, {
|
|
|
|
method: "POST",
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
"X-Request-ID": requestId,
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
query: params.prompt,
|
|
|
|
user_id: params.userId,
|
|
|
|
user_name: params.userName,
|
|
|
|
user_token: params.user_token || "123",
|
|
|
|
user_role: "student",
|
|
|
|
conversation_id: params.conversationId || null,
|
|
|
|
}),
|
|
|
|
signal: controller.signal,
|
|
|
|
}).then((response) => {
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error(`HTTP ${response.status}`);
|
|
|
|
}
|
|
|
|
if (!response.body) {
|
|
|
|
throw new Error("Response body is null");
|
2025-08-14 15:43:24 +08:00
|
|
|
}
|
2025-08-14 23:26:49 +08:00
|
|
|
return {
|
|
|
|
reader: response.body.getReader(),
|
|
|
|
decoder: new TextDecoder("utf-8"),
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
stream: fetchPromise,
|
|
|
|
cancel: (reason) => {
|
|
|
|
if (!controller.signal.aborted) {
|
|
|
|
controller.abort(reason);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
2025-08-14 15:43:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 处理流式响应
|
|
|
|
* @param {ReadableStreamDefaultReader} reader 读取器
|
|
|
|
* @param {TextDecoder} decoder 文本解码器
|
|
|
|
* @param {Function} onMessage 消息回调
|
|
|
|
* @param {Function} onError 错误回调
|
|
|
|
* @param {Function} onComplete 完成回调
|
|
|
|
*/
|
2025-08-14 23:26:49 +08:00
|
|
|
export async function processStream(
|
|
|
|
reader,
|
|
|
|
decoder,
|
|
|
|
{ onMessage, onError, onComplete }
|
|
|
|
) {
|
|
|
|
let buffer = "";
|
2025-08-14 15:43:24 +08:00
|
|
|
|
2025-08-14 23:26:49 +08:00
|
|
|
try {
|
|
|
|
while (true) {
|
|
|
|
const { done, value } = await reader.read();
|
|
|
|
if (done) break;
|
2025-08-14 15:43:24 +08:00
|
|
|
|
2025-08-14 23:26:49 +08:00
|
|
|
buffer += decoder.decode(value, { stream: true });
|
|
|
|
const lines = buffer.split("\n");
|
|
|
|
buffer = lines.pop() || "";
|
2025-08-14 15:43:24 +08:00
|
|
|
|
2025-08-14 23:26:49 +08:00
|
|
|
for (const line of lines) {
|
|
|
|
if (!line.trim()) continue;
|
2025-08-14 15:43:24 +08:00
|
|
|
|
2025-08-14 23:26:49 +08:00
|
|
|
try {
|
|
|
|
const data = JSON.parse(line);
|
|
|
|
if (typeof onMessage === "function") {
|
|
|
|
onMessage(data);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.warn("解析消息失败:", line, e);
|
2025-08-14 15:43:24 +08:00
|
|
|
}
|
2025-08-14 23:26:49 +08:00
|
|
|
}
|
|
|
|
}
|
2025-08-14 15:43:24 +08:00
|
|
|
|
2025-08-14 23:26:49 +08:00
|
|
|
if (typeof onComplete === "function") {
|
|
|
|
onComplete();
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
if (error.name !== "AbortError" && typeof onError === "function") {
|
|
|
|
onError(error);
|
2025-08-14 15:43:24 +08:00
|
|
|
}
|
2025-08-14 23:26:49 +08:00
|
|
|
} finally {
|
|
|
|
reader.releaseLock();
|
|
|
|
}
|
|
|
|
}
|