创建自定义 TransformStream。分割字符串文本,移除 data: 前缀,并转换为 JSON 对象。这个转换可能并不稳定,如果响应在某一行中间中断了,那么转换就会出现问题。但经过实测绝大部分场景是可用的。
如果需要稳定的转换,建议增加缓冲区。
/**
* 将 SSE 文本流转换为 JSON 数据
*/
export class EventSourceStream extends TransformStream {
constructor() {
super({
transform(chunk, controller) {
if (typeof chunk !== "string") return;
const lines = chunk.split("\n").map((line) => line.trim());
for (const line of lines) {
if (!line.startsWith("data:")) continue;
const payload = line.slice(5).trim();
if (payload === "[DONE]") return;
try {
const data = JSON.parse(payload);
controller.enqueue(data);
} catch (error) {
console.warn("Error parsing JSON:", payload, error);
continue;
}
}
},
});
}
}
使用 fetch 调用。
// 调用AI API
const response = await fetch("https://example.com/you-model/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
model: selectedModel,
// 过滤掉 assistant 消息中的 reasoning 字段
messages: messages.value.map((item) => {
if (item.role === "assistant") return omit(item, ["reasoning"]);
return item;
}),
stream: true,
}),
});
// 创建流式读取器
const reader = response.body
?.pipeThrough(new TextDecoderStream())
.pipeThrough(new EventSourceStream())
.getReader();
if (!reader) return;
// 创建AI响应消息对象
const assistantMessage: AssistantMessage = reactive({
id: v7(),
role: "assistant",
content: "",
});
// 添加到历史记录中以便实时显示
messages.value.push(assistantMessage);
// 流式读取AI响应
while (true) {
const { value, done } = await reader.read();
if (done) break;
const { choices } = value;
if (!Array.isArray(choices) || choices.length === 0) continue;
const [{ delta }] = choices;
if (!delta) continue;
const { content, reasoning_content } = delta;
// 累加响应内容
if (content) assistantMessage.content += content;
// 累加推理过程
if (reasoning_content) {
const { reasoning } = assistantMessage;
assistantMessage.reasoning = reasoning ? reasoning + reasoning_content : reasoning_content;
}
}