谷歌SEO

谷歌SEO

Products

当前位置:首页 > 谷歌SEO >

打字机是如何制造出来的?

96SEO 2026-08-09 19:21 2


打字机是怎么炼成的:Chat 流式输出详细说明

面试官看了一眼你的项目:"AI 对话为什么能像打字机一样逐字输出?"

这个问题像剥洋葱——每剥一层都有追问。你说用了 SSE,他问为什么不用 WebSocket。你说 HTTP 分块传输,他问半个中文字被截断了怎么办。话说回来,你说状态管理,他问 LLM 每秒出 个 token,React 跟得上吗。

打字机是如何制造出来的?

要回答好这个问题,比较合适的方法就是沿着代码的执行方法走一遍。数据从使用者敲下回车到屏幕上跳出第一个字,到底经过了哪些代码?每一行代码解决了什么问题?其实,

再看第一站。使用者按下发送按钮

打开 Chat.tsx使用者输入文字后点击"发送"按钮。按钮的 type="submit" 触发表单的 onSubmit 事件:

// Chat.tsx
const onSubmit = => {
e.preventDefault;if ) return,怎么说呢,handleSubmit;// 把事件交给 AI SDK 处理
};

handleSubmituseChatbot 返回的,而 useChatbot 来自 @ai-sdk/react

// hooks/useChatBot.ts
export const useChatbot = => {
return useChat({
再看api。'/api/ai/chat',onError: => console.log
})
}

User Pain Point:

  • "为什么不直接在前端调用 DeepSeek API?"
  • "我担心把 API key 暴露给浏览器"
  • "怎样确保请求顺序不乱"

就这几行代码。useChat 帮我们做了三件事:

  1. w往 messages{ role: "user",content: "你好" }{ },使用者的消息立刻显示来。
  2. d发一个 POST 请求到
  3. w准备好接收一个SSE流式响应——不是等响应全部到达再处理,而是边收边更新。

User sees ir message appear instantly;AI is still thinking—three dots spin.

再看第二站,mock 层的翻译工作

  • "为什么需要 mock 层而不是直接调用 DeepSeek?"
  • "我听说 DeepSeek 接口返回的是 JSON,不是 SSE;怎么能流式呢,"
  • "参数写错会导致什么后果?"

'翻译 + 中继'-左手从 DeepSeek 拿数据,右手转成前端能识别的格式发出去。

B先看它是怎么把请求转发给 DeepSeek 的:

// mock/chat.js
const response = await fetch('https://api.deepseek.com/v1/chat/completions',{
method的观点是,'POST'。headers: {
'Content-Type': 'application/json','Authorization': `Bearer ${process.env.VITE_DEEPSEEK_API_KEY}`
说到},body,JSON.stringify({
从model来看,'deepseek-chat',messages,stream: true // ← 关键:告诉 DeepSeek 我要流式输出
})
});其实,User Pain Point:`stream:true` 决定返回方式。不加它,DeepSeek 会返回完整 JSON;加上它会拆成碎片,用 SSE 推送。B如果写成 `streaming:true` 就会被忽略,从而导致没有打字机效果——前端等待几秒才一次性显示文本。拿到 DeepSeek 的流式响应后接下来是实时翻译。DeepSeek 返回的是 SSE 格式,每一片数据长这样:
至于data,{id:"xxx",choices:} 至于data。{id:"xxx",choices:} data这方面,前端用的是 @ai-sdk/react,它不认识 SSE,它有自己的“”——Vercel AI SDK Data Stream 协议。再看格式是这样的,
// Vercel AI SDK Data Stream 示例
从:"你"来看。"好"
mock 要做的是把 SSE 的 `data.choices.delta.content` 取出来并包上一层 `:` 的外壳:
// mock/chat.js — 主要转换
const data = JSON.parse);// 去掉 "data: " 前缀
const content = data?.choices,.?.delta,说起来,.content;// 取出这次产生的字
if {
res.write}
`);// 翻译成 AI SDK
}
`res.write` 在收到每行时立即执行,没有任何蓄水。

至于第三站,前端的流式消费

B数据被逐片推送到浏览器后AI SDK 的 `processDataStream` 函数开始工作。老实说,主要原因简化后是这样:

// @ai-sdk/ui-utils — processDataStream
async function processDataStream {
const reader = stream.getReader;// 拿到流的“遥控器”
const decoder = new TextDecoder;// 字节 → 文本解码器
let buffer = '';while {
const { done,value } = await reader.read;// 读一块数据
if break;
buffer += decoder.decode;// 字节转文字
const lines = buffer.split;按理说,// 按换行切分
buffer = lines.pop || '';// 最终一段留着,下次拼
for {
const { type。value } = parseDataStreamPart;if { // line=':"你"' → type='text',value='你'
onTextPart;// 回调:把新字追加到消息里
}
}
}

}

onTextPart 被调用时AI SDK 把新收到的字拼接到 assistant 消息里的 content 并触发一次 React 的