let controllerFetchList = [];
const getSummary = async (data) => {
if (isStream.value) {
let controller = new AbortController();
controllerFetchList.push(controller);
const { signal } = controller;
const response = await apiFetch(`${interfaceUrl}/API-AIP/chat/analyze`, {
signal,
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
body: JSON.stringify({
stream: true,
conversation_id: chatId.value,
data: JSON.stringify(data),
}),
});
// 3. 从响应体中获取 ReadableStream 的读取器
const reader = response.body.getReader();
// 4. 创建 TextDecoder 用于将 Uint8Array 解码成字符串
const decoder = new TextDecoder('utf-8');
// 1. 创建一个缓冲区来存储不完整的数据
let buffer = '';
// 5. 循环读取流中的数据
// eslint-disable-next-line no-constant-condition
while (true) {
const { done, value } = await reader.read();
if (done) {
console.log('数据流接收完毕。');
getLastChat.value.summaryLoading = false;
isLoading.value = false;
getLastChat.value.loading = false;
//更新消息
getLastChat.value.noSummary = true;
await updateMsgApi(getLastChat.value.dataId, { content: JSON.stringify(getLastChat.value) });
scrollBottom();
break; // 退出循环
}
// 将接收到的 Uint8Array 数据块解码为字符串
const chunk = decoder.decode(value, { stream: true });
buffer += chunk;
while (buffer.includes('\n')) {
const separatorIndex = buffer.indexOf('\n');
// 提取一条完整的消息
const message = buffer.slice(0, separatorIndex);
// 从缓冲区中移除已处理的消息
buffer = buffer.slice(separatorIndex + 1);
const lines = message.split('\n');
for (const line of lines) {
if (line.startsWith('data:')) {
// 提取 'data:' 后面的内容,并去除前导空格
const jsonString = line.substring(5).trim();
if (jsonString) {
try {
// 解析 JSON 字符串
const dataObject = JSON.parse(jsonString);
if (dataObject.type === 'content') {
getLastChat.value.summary += dataObject.content;
}
// 成功取值!
console.log('成功解析对象:', dataObject);
} catch (error) {
console.error('解析JSON失败:', error, '原始JSON字符串:', jsonString);
}
}
}
}
}
}
}
};
缓冲区与消息边界处理 (最核心的逻辑)
JavaScript
let buffer = '';
// ...
buffer += chunk;
while (buffer.includes('\n\n')) {
const separatorIndex = buffer.indexOf('\n\n');
const message = buffer.slice(0, separatorIndex);
buffer = buffer.slice(separatorIndex + 2);
// ...
}
buffer的作用: 这是一个临时存储区,用来解决网络数据传输的不确定性。AI的流式输出可能很长,会被切成很多小的数据块(chunk)发送。buffer的任务就是将这些小块拼接起来,直到形成一个或多个完整的消息。while (buffer.includes('\n\n')): 这是整个流式处理的“规则引擎”。它基于一个约定:服务器每发完一条完整的消息(data: {...}),就会在后面跟上两个换行符\n\n作为分隔符。这个while循环不断检查缓冲区里是否出现了这个分隔符。- 提取与消耗 (
slice) : 一旦找到分隔符,代码就精确地提取出一条完整的消息 (message),然后立刻将这条消息连同分隔符从buffer的开头移除 (buffer.slice(separatorIndex + 2))。这一步至关重要,它确保了程序不会重复处理同一条消息,并让buffer为暂存下一条不完整的消息做好了准备。