模仿雷同 DeepSeek 的对话

[复制链接]
发表于 2025-10-15 04:11:01 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
以下是一个完备的 JavaScript 数据流式获取实现方案,模仿雷同 DeepSeek 的对话式逐段返回效果。包罗前端实现、后端模仿和详细解释:
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <title>流式对话演示</title>
  6.     <style>
  7.         #output {
  8.             border: 1px solid #ccc;
  9.             padding: 20px;
  10.             margin: 20px;
  11.             min-height: 200px;
  12.             font-family: monospace;
  13.             white-space: pre-wrap;
  14.         }
  15.         .loading {
  16.             display: none;
  17.             color: #666;
  18.         }
  19.     </style>
  20. </head>
  21. <body>
  22.     <button onclick="startStream()">开始对话</button>
  23.     <div id="output"></div>
  24.     <div id="loading" class="loading">思考中...</div>
  25.     <script>
  26.         // 流式对话核心逻辑
  27.         class StreamDialog {
  28.             constructor() {
  29.                 this.output = document.getElementById('output');
  30.                 this.loading = document.getElementById('loading');
  31.                 this.controller = null;  // 用于中止请求
  32.             }
  33.             // 开始流式对话
  34.             async start() {
  35.                 try {
  36.                     this.showLoading();
  37.                     this.output.textContent = '';
  38.                     
  39.                     // 创建中止控制器
  40.                     this.controller = new AbortController();
  41.                     
  42.                     // 发起 fetch 请求
  43.                     const response = await fetch('/stream', {
  44.                         signal: this.controller.signal,
  45.                         headers: {
  46.                             'Content-Type': 'application/json'
  47.                         }
  48.                     });
  49.                     if (!response.ok) throw new Error(response.statusText);
  50.                     if (!response.body) throw new Error("ReadableStream not supported");
  51.                     // 创建读取器
  52.                     const reader = response.body.getReader();
  53.                     const decoder = new TextDecoder();
  54.                     
  55.                     // 持续读取流数据
  56.                     while (true) {
  57.                         const { done, value } = await reader.read();
  58.                         if (done) break;
  59.                         
  60.                         // 解析数据块
  61.                         const chunk = decoder.decode(value);
  62.                         const lines = chunk.split('\n').filter(line => line.trim());
  63.                         
  64.                         for (const line of lines) {
  65.                             try {
  66.                                 const event = JSON.parse(line.replace('data: ', ''));
  67.                                 this.appendContent(event.content);
  68.                             } catch (e) {
  69.                                 console.error('解析错误:', e);
  70.                             }
  71.                         }
  72.                     }
  73.                 } catch (error) {
  74.                     if (error.name !== 'AbortError') {
  75.                         this.appendContent('\n[对话异常终止]');
  76.                     }
  77.                 } finally {
  78.                     this.hideLoading();
  79.                     this.controller = null;
  80.                 }
  81.             }
  82.             // 中止对话
  83.             abort() {
  84.                 if (this.controller) {
  85.                     this.controller.abort();
  86.                 }
  87.             }
  88.             // 添加内容到输出区
  89.             appendContent(text) {
  90.                 // 模拟打字机效果
  91.                 let currentIndex = 0;
  92.                 const animate = () => {
  93.                     if (currentIndex < text.length) {
  94.                         this.output.textContent += text[currentIndex];
  95.                         currentIndex++;
  96.                         requestAnimationFrame(animate);
  97.                         this.output.scrollTop = this.output.scrollHeight; // 自动滚动
  98.                     }
  99.                 };
  100.                 animate();
  101.             }
  102.             showLoading() {
  103.                 this.loading.style.display = 'block';
  104.             }
  105.             hideLoading() {
  106.                 this.loading.style.display = 'none';
  107.             }
  108.         }
  109.         // 初始化对话实例
  110.         const dialog = new StreamDialog();
  111.         // 启动对话
  112.         function startStream() {
  113.             dialog.start();
  114.         }
  115.         // 中止对话
  116.         function stopStream() {
  117.             dialog.abort();
  118.         }
  119.     </script>
  120. </body>
  121. </html>
复制代码
后端 Node.js 实现(Express):
  1. const express = require('express');
  2. const app = express();
  3. const port = 3000;
  4. // 允许跨域
  5. app.use((req, res, next) => {
  6.     res.header('Access-Control-Allow-Origin', '*');
  7.     next();
  8. });
  9. // 流式端点
  10. app.get('/stream', (req, res) => {
  11.     // 设置流式响应头
  12.     res.writeHead(200, {
  13.         'Content-Type': 'text/event-stream',
  14.         'Cache-Control': 'no-cache',
  15.         'Connection': 'keep-alive'
  16.     });
  17.     // 模拟的对话数据
  18.     const responseText = [
  19.         "你好!我是 DeepSeek 的智能助手。",
  20.         "我正在分析您的问题...",
  21.         "根据现有数据,建议如下:\n",
  22.         "1. 首先检查网络连接\n",
  23.         "2. 验证 API 密钥有效性\n",
  24.         "3. 查看服务状态面板\n",
  25.         "\n需要更详细的帮助吗?"
  26.     ];
  27.     // 发送流式数据
  28.     let index = 0;
  29.     const sendChunk = () => {
  30.         if (index < responseText.length) {
  31.             const chunk = {
  32.                 content: responseText[index],
  33.                 finished: index === responseText.length - 1
  34.             };
  35.             
  36.             // SSE 格式要求
  37.             res.write(`data: ${JSON.stringify(chunk)}\n\n`);
  38.             
  39.             index++;
  40.             setTimeout(sendChunk, 500); // 控制发送速度
  41.         } else {
  42.             res.end();
  43.         }
  44.     };
  45.     sendChunk();
  46.     // 处理客户端断开连接
  47.     req.on('close', () => {
  48.         console.log('客户端断开连接');
  49.         res.end();
  50.     });
  51. });
  52. app.listen(port, () => {
  53.     console.log(`服务运行在 http://localhost:${port}`);
  54. });
复制代码
焦点实现原理:

  • 前端实现:


  • 利用 Fetch API + ReadableStream 处理处罚流式数据
  • 支持哀求停止(AbortController)
  • 实现打字机动画效果
  • 自动滚动保持内容可见
  • 加载状态指示

  • 后端实现:


  • 利用 Server-Sent Events (SSE) 协议
  • 分块发送 JSON 格式数据
  • 模仿真实相应延长(500ms/块)
  • 支持客户端断开检测

  • 数据格式:
  1. {
  2.   "content": "当前内容片段",
  3.   "finished": false
  4. }
复制代码
优化扩展发起:
  1. // 添加节流控制
  2. const throttle = (func, limit) => {
  3.   let lastFunc;
  4.   let lastRan;
  5.   return function(...args) {
  6.     if (!lastRan) {
  7.       func.apply(this, args);
  8.       lastRan = Date.now();
  9.     } else {
  10.       clearTimeout(lastFunc);
  11.       lastFunc = setTimeout(() => {
  12.         if ((Date.now() - lastRan) >= limit) {
  13.           func.apply(this, args);
  14.           lastRan = Date.now();
  15.         }
  16.       }, limit - (Date.now() - lastRan));
  17.     }
  18.   };
  19. };
  20. // 在 appendContent 中使用
  21. this.appendContent = throttle(this.appendContent, 50);
复制代码

  • 错误处理处罚增强:
  1. // 前端添加错误处理
  2. async start() {
  3.     try {
  4.         // ...原有逻辑...
  5.     } catch (error) {
  6.         if (error.name === 'AbortError') {
  7.             this.appendContent('\n[对话已中止]');
  8.         } else {
  9.             this.appendContent(`\n[错误: ${error.message}]`);
  10.             console.error('Stream Error:', error);
  11.         }
  12.     }
  13. }
  14. // 后端添加错误模拟
  15. app.get('/stream', (req, res) => {
  16.     // 10% 概率模拟错误
  17.     if (Math.random() < 0.1) {
  18.         res.writeHead(500);
  19.         res.end();
  20.         return;
  21.     }
  22.     // ...原有逻辑...
  23. });
复制代码
  1. // 添加 Markdown 支持
  2. appendContent(text) {
  3.     // 简单 Markdown 解析
  4.     const parsed = text
  5.         .replace(/#{3}/g, '<h3>')
  6.         .replace(/#{2}/g, '<h2>')
  7.         .replace(/#{1}/g, '<h1>')
  8.         .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
  9.    
  10.     // 使用 DocumentFragment 优化渲染
  11.     const fragment = document.createDocumentFragment();
  12.     const span = document.createElement('span');
  13.     span.innerHTML = parsed;
  14.     fragment.appendChild(span);
  15.    
  16.     this.output.appendChild(fragment);
  17. }
复制代码
这个实现方案完备展示了:

  • 前后端流式通讯的全流程
  • 实时内容渲染优化本领
  • 完备的错误处理处罚机制
  • 可扩展的架构筹划
  • 用户体验优化细节
实际摆设时,发起:

  • 利用 WebSocket 更换 SSE 实现双向通讯
  • 添加 JWT 鉴权
  • 实现速率限定(Rate Limiting)
  • 摆设到支持 HTTP/2 的服务器
  • 添加前端缓存战略
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表