构建支持流式输出的AI聊天应用:React与DeepSeek集成实践

在AI聊天应用开发中,流式输出 已成为提升用户体验的关键特性。传统的一次性响应往往导致用户等待时间过长,而流式输出允许模型生成的文本逐 token 实时传输,像打字机一样逐步显示内容。这不仅使交互更自然,还能让用户感知到应用的响应速度更快。本文将基于一个实际项目,分享如何使用 React 前端结合自定义 Hook ,以及 Mock 后端集成DeepSeek模型,实现支持流式输出的聊天机器人。项目强调前后端分离、响应式设计和HTTP协议优化,适用于开发者快速构建原型或学习AI集成。

流式输出的本质在于LLM(Large Language Model)生成token的过程:模型通过自回归方式,基于已有序列预测下一个token,而不是等待全部内容生成后再返回。在AI场景中,这意味着前端可以边接收边渲染,提升互动性。技术栈上,前端采用React与shadcn/ui组件库,实现优雅UI;后端使用Mock.js模拟API,调用DeepSeek的chat completions接口,并处理SSE(Server-Sent Events)流。

通过这个项目,读者可以掌握HTTP chunked传输、Node.js原生请求体解析,以及React Hook在聊天业务中的应用。接下来,我们逐步剖析架构、代码和知识点。

项目架构概述

项目采用前后端分离模式,前端负责UI渲染和用户交互,后端(Mock)处理AI调用和流式响应。

  • 前端:React应用,使用自定义Hook如useChatBot封装聊天逻辑,包括消息管理、输入处理和提交。UI组件来自shadcn/ui,如ScrollArea优化滚动体验,Input和Button构建表单。
  • 后端:通过Mock.js模拟API路由 /api/ai/chat,接收POST请求,调用DeepSeek API(stream: true),并转发流式数据。使用dotenv管理API密钥,确保安全。
  • AI集成:DeepSeek-chat模型,提供chat completions接口,支持流式输出。响应通过SSE格式处理,逐chunk发送token。

这种架构便于调试:前端可独立运行,后端Mock模拟真实服务。实际部署时,可替换Mock为Express或Next.js API路由。

前端实现详解

前端的核心是Chat组件,构建一个全屏聊天界面,支持消息显示、输入和加载动画。使用React Hook剥离业务逻辑,确保组件专注UI。

chat.tsx:聊天组件

组件代码如下:

typescriptreact

javascript 复制代码
import {
  useEffect,
} from 'react';
import Header from '@/components/Header'
import {
  useChatBot
} from '@/hooks/useChatBot'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'

export default function Chat() {
  const {
    messages,
    input,
    handleInputChange,
    handleSubmit,
    isLoading,
  } = useChatBot();

  const onSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (!input.trim()) return;
    handleSubmit(e);
  }

  return (
    <div className="flex flex-col h-screen max-w-4xl mx-auto p-4 pb-2">
      <Header title="DeepSeek Chat" showBackBtn={true} />
      {/* html 原生滚动条不太好看,体验不好
        shadcn ScrollArea 样式和体验上优化
      */}
      <ScrollArea className="flex-1 border rounded-lg p-4 mb-4 bg-background">
        {
          messages.length === 0 ? (
            <div className="text-center text-muted-foreground py-8">
              Start a conversation with DeepSeek
            </div>
          ) : (
            <div className="space-y-4">
              {
                messages.map((m, idx) => (
                  <div
                    key={idx}
                    className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}
                  >
                    <div
                      className={`max-w-[80%] rounded-lg px-4 py-2 ${m.role === 'user'
                          ? 'bg-primary text-primary-foreground'
                          : 'bg-muted'
                        }`}
                    >
                      {m.content}
                    </div>
                  </div>
                ))
              }
              {isLoading && (
                <div className="flex justify-start">
                  <div className="bg-muted rounded-lg px-4 py-2">
                    <span className="animate-pulse">...</span>
                  </div>
                </div>
              )}
            </div>
          )
        }
      </ScrollArea>
      <form onSubmit={onSubmit} className="flex gap-2">
        <Input value={input} onChange={handleInputChange}
          placeholder="Type your message..."
          disabled={isLoading}
          className="flex-1 "
        />
        <Button type="submit" disabled={isLoading || !input.trim()}>
          Send
        </Button>
      </form>
    </div>
  )
}

组件使用useChatBot Hook获取状态:messages(消息数组)、input(输入值)、handleInputChange(输入变更)、handleSubmit(提交处理)、isLoading(加载中)。onSubmit函数防止空输入提交。

UI设计:Header提供标题和返回按钮。ScrollArea包裹消息区,优化滚动条样式和体验。如果无消息,显示提示;否则,映射messages渲染泡泡式对话,用户消息右对齐,AI左对齐。isLoading时,显示动画"..."表示思考中。

表单部分:Input绑定input和handleInputChange,禁用加载中;Button提交,禁用无输入或加载。整体布局flex-col h-screen,确保全屏适配,max-w-4xl居中。

这种设计提升用户体验:实时反馈、禁用防止重复提交,shadcn组件确保美观一致。

useChatBot Hook:业务逻辑封装

虽然代码未提供完整Hook,但从组件可见,它管理聊天状态。典型实现中使用useState存储messages和input,useEffect可能监听流式更新。handleSubmit发送请求到 /api/ai/chat,追加用户消息,然后处理流式响应,逐token追加AI消息。isLoading控制UI状态。

Hook的优势:剥离响应式业务(如API调用、状态更新),让组件单一。类似vercel ai-sdk/react的useChat,但自定义更灵活,可集成流式监听。

后端(Mock)实现详解

后端使用Mock.js定义路由,支持流式输出。代码处理原始HTTP响应,实现chunked传输。

mock/chat.js:流式API模拟

代码如下:

JavaScript

javascript 复制代码
// 流式输出本质是变算(llm token 生成)边给,而不是等全部结果生成再一次性返回
// AI场景中,模型生成文本是逐个token 产生的(模型每次基于已生成的token 序列)
// 通过自回归方式预测下一个最可能的方式预测下一个最可能的token
// streaming:true
// http chunked 数据块来传 不用res.end()
// res.write(chunk) 
// SSE 服务器发送事件(Server-Sent Events)
// text/event-stream 模式去发送token

import { config } from 'dotenv';
config();

export default [
  {
    url: "/api/ai/chat",
    method: "post",
    // rawResponse 用于自定义原始的 HTTP 响应(如流式输出)
    // 而 response 通常指封装后的结构化响应
    rawResponse: async (req, res) => {
      // node 原生地去拿到请求体
      // console.log("/////[][][]/////");
      // chunk 数据块(buffer)
      // tcp/ip tcp:可靠的传输协议
      // 按顺序组装,失败重传  html
      // on data
      let body = '';
      // chunk 二进制流 buffer
      // 最后把 buffer 转成字符串
      req.on('data', (chunk) => { body += chunk })
      // 数据接收完成
      req.on('end', async () => {
        // 都到位了
        console.log(body);
        try {
          const { messages } = JSON.parse(body);
          // console.log(messages);
          res.setHeader('Content-Type', 'text/plain;charset=utf-8');
          // 响应头先告诉浏览器 这是流式的 数据会分块传输
          res.setHeader('Transfer-Encoding', 'chunked');
          // vercel ai sdk 特制头
          res.setHeader('x-vercel-ai-data-stream', 'v1');
          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: messages,
              stream: true  // 流式输出
            })
          })
          // console.log(process.env.VITE_DEEPSEEK_API_KEY, "[][][]{}{}{}{[][][]")
          if (!response.body) throw new Error("No response body");
          // SSE:二进制流  有个reader 对象 接根管子一样
          const reader = response.body.getReader();
          // 用于将ArrayBuffer 或 TypedArray(如 Uint8Array) 转换为字符串
          const decoder = new TextDecoder();
          while(true) {
            const { done, value } = await reader.read(0);
            // console.log(done, value, '--------------');
            if (done) break;
            const chunk = decoder.decode(value);
            // console.log(chunk, "------")
            const lines = chunk.split('\n');
            for (let line of lines) {
              if (line.startsWith('data:') && line !== 'data: [DONE]') {
                try {
                  const data = JSON.parse(line.slice(6));
                  const content = data.choices[0]?.delta?.content || '';
                  if (content) {
                    res.write(`0:${JSON.stringify(content)}\n`);
                  }
                } catch (err) {

                }
              }
            }
          }
          res.end();
        } catch (err) {

        }
      })
    }
  }
]

使用dotenv加载环境变量,如API密钥。rawResponse异步处理原始响应:通过req.on('data')和req.on('end')原生解析请求体(body),避免中间件依赖。

解析后,提取messages,设置响应头:Content-Type为text/plain,Transfer-Encoding: chunked表示分块传输,x-vercel-ai-data-stream兼容SDK。

fetch调用DeepSeek API,stream: true启用流。获取response.body的ReadableStream,使用getReader()读取chunk。TextDecoder解码二进制为字符串,拆分行,过滤data:开头,解析JSON提取delta.content,res.write发送格式化chunk(如"0:"content"\n")。

循环直到done,res.end()结束响应。这种方式实现SSE-like流,支持前端事件监听。

关键知识点:流式输出与HTTP协议

流式输出的核心是LLM token生成:模型自回归预测下一个token,前端边收边显,提升感知速度。

HTTP方面:使用Keep-Alive连接持久化,Transfer-Encoding: chunked分块传输,无需Content-Length。res.write(chunk)逐块发送,不用一次性end。

SSE(Server-Sent Events):text/event-stream模式,浏览器EventSource监听message事件。代码中虽用chunked,但格式兼容SSE(data: JSON),前端可解析。

TCP/IP基础:请求体chunk是Buffer,按序组装,确保可靠传输。

前端集成:useChatBot可能用EventSource或fetch stream监听,逐token追加messages。

关键知识点:Mock.js与AI SDK

Mock.js的rawResponse自定义响应,适合模拟流式API,便于开发测试。

vercel ai-sdk/react提供useChat等Hook,封装流式处理,但自定义实现更理解底层:如reader.read()循环处理chunk,忽略[DONE]结束信号。

安全考虑:dotenv管理密钥,避免硬编码。try-catch容错,防止崩溃。

结语

通过这个支持流式输出的AI聊天应用,我们看到React Hook和HTTP chunked的强大结合。流式设计不仅优化体验,还深化对AI生成过程的理解。

相关推荐
mCell8 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell9 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭9 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清9 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
银烛木9 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076609 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声9 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易9 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
0思必得010 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化
青云计划10 小时前
知光项目知文发布模块
java·后端·spring·mybatis