李慕婉-仙逆-造相Z-Turbo JavaScript前端交互:实现实时AI对话与内容生成

张开发
2026/4/18 17:59:15 15 分钟阅读

分享文章

李慕婉-仙逆-造相Z-Turbo JavaScript前端交互:实现实时AI对话与内容生成
李慕婉-仙逆-造相Z-Turbo JavaScript前端交互实现实时AI对话与内容生成最近在折腾一个智能客服的原型后端用星图GPU平台部署了李慕婉-仙逆-造相Z-Turbo模型效果挺不错。但问题来了怎么让用户在前端网页上也能丝滑地跟这个AI对话还能实时看到它“思考”和生成内容的过程呢总不能每次都让用户去刷新页面或者等半天吧。这其实就是很多想在前端集成大模型能力的开发者会遇到的一个典型痛点后端模型能力很强但前端交互体验跟不上。要么是请求响应慢用户等得没耐心要么是交互生硬没有那种实时对话的感觉。所以我花了一些时间把调用模型、处理流式响应、构建交互界面这一套流程给跑通了。今天就跟大家分享一下怎么用最基础的JavaScript技术结合现代的Fetch API和WebSocket在前端实现一个既实时又流畅的AI对话与内容生成界面。整个过程不依赖复杂的框架理解了核心思路用Vue或React来封装也会非常顺手。1. 核心思路与准备工作在开始写代码之前我们先理清楚要做什么。我们的目标是在网页上用户输入问题AI模型能像真人聊天一样一个字一个字地把回答“流”出来而不是等全部生成完再一次性显示。1.1 技术方案选型要实现这个效果核心在于“流式响应”。传统的AJAX请求是等服务器把所有数据都准备好了一次性返回。而流式响应允许服务器一边生成结果一边通过网络流式地推送给前端前端收到一点就显示一点。对于前端来说处理这种流式数据主要有两种主流方式Fetch API ReadableStream这是比较现代且标准的方式。我们向模型服务发送一个HTTP请求服务端返回一个“流”Stream前端通过JavaScript逐步读取这个流中的数据。这种方式兼容性好实现起来也直观。WebSocket这是一种全双工通信协议更适合需要服务器主动、频繁向客户端推送数据的场景如聊天室、实时通知。对于单纯的“一问一答”式AI对话用Fetch API处理流式响应通常更简单直接。但如果你的应用场景是AI持续主动汇报进度或者有多用户协作WebSocket会更合适。我们这个例子就以更通用的Fetch API ReadableStream方案为主来展开。理解了它WebSocket的方案也大同小异。1.2 环境与模型服务确认在写前端代码前你需要确保后端模型服务已经就绪并正确部署。这里假设你已经通过星图GPU平台成功部署了“李慕婉-仙逆-造相Z-Turbo”的镜像并且获得了模型的API访问端点Endpoint和必要的鉴权信息如API Key。关键要确认一点你的模型服务是否支持流式输出。大多数现代的大模型服务包括基于类似架构的部署都会提供流式输出的接口通常会在请求参数中有一个stream: true之类的开关。你需要查阅你所部署模型服务的API文档来确认具体的参数。准备工作就这些接下来我们进入实战环节。2. 使用Fetch API实现基础对话我们先从最简单的开始实现一个非流式的对话。这样可以帮助我们理解基本的请求结构为后面的流式处理打下基础。假设我们的模型服务部署在https://your-model-service.com/v1/chat/completions并且使用标准的OpenAI兼容的API格式很多开源模型部署工具都支持这种格式。// 定义模型服务的API地址和密钥 const API_ENDPOINT https://your-model-service.com/v1/chat/completions; const API_KEY your-secret-api-key-here; // 注意在实际生产中密钥不应硬编码在前端应由后端代理或通过安全方式管理 async function sendMessage(userInput) { // 构建请求的消息历史通常包含一个系统角色和用户的历史对话 const messages [ { role: system, content: 你是一个乐于助人的AI助手。 }, { role: user, content: userInput } ]; const requestBody { model: limuwan-xianni-zaoxiang-z-turbo, // 替换为你的实际模型名称 messages: messages, max_tokens: 500, // 控制生成的最大长度 temperature: 0.7, // 控制生成内容的随机性 stream: false // 非流式模式 }; try { const response await fetch(API_ENDPOINT, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${API_KEY} }, body: JSON.stringify(requestBody) }); if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } const data await response.json(); // 通常响应结构中的 data.choices[0].message.content 包含AI的回复 const aiReply data.choices[0]?.message?.content || 模型未返回有效内容; return aiReply; } catch (error) { console.error(请求模型API失败:, error); return 抱歉请求出错${error.message}; } } // 使用示例 const userQuestion JavaScript中如何用Fetch API发送POST请求; sendMessage(userQuestion).then(reply { console.log(AI回复:, reply); // 这里可以将回复显示到网页的某个DOM元素中 // document.getElementById(response-area).textContent reply; });这段代码完成了最基础的交互发送用户消息等待模型完全生成回复后一次性获取并显示。但用户需要等待整个生成过程结束体验不够好。3. 升级体验实现流式响应与实时显示现在我们来改造上面的代码开启流式模式并实现逐字显示的效果。这才是提升用户体验的关键。3.1 处理流式响应当设置stream: true后服务器返回的不再是一个JSON对象而是一个ReadableStream。我们需要逐步读取这个流。async function sendMessageStream(userInput, onChunkReceived, onComplete) { const messages [ { role: system, content: 你是一个乐于助人的AI助手。 }, { role: user, content: userInput } ]; const requestBody { model: limuwan-xianni-zaoxiang-z-turbo, messages: messages, max_tokens: 500, temperature: 0.7, stream: true // 关键开启流式输出 }; try { const response await fetch(API_ENDPOINT, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${API_KEY} }, body: JSON.stringify(requestBody) }); if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } const reader response.body.getReader(); const decoder new TextDecoder(utf-8); let accumulatedText ; while (true) { const { done, value } await reader.read(); if (done) { onComplete?.(accumulatedText); // 流读取完毕回调最终完整文本 break; } // 解码当前数据块 const chunk decoder.decode(value, { stream: true }); // 流式响应通常以 data: 开头每行是一个独立的JSON片段或 [DONE] const lines chunk.split(\n).filter(line line.trim() ! ); for (const line of lines) { if (line.startsWith(data: )) { const dataStr line.slice(6); // 去掉 data: 前缀 if (dataStr [DONE]) { continue; // 流结束标志 } try { const parsedData JSON.parse(dataStr); // 解析出的文本通常在 choices[0].delta.content 中 const textChunk parsedData.choices[0]?.delta?.content || ; if (textChunk) { accumulatedText textChunk; // 每收到一个文本块就回调一次用于实时更新UI onChunkReceived?.(textChunk, accumulatedText); } } catch (e) { console.warn(解析流数据失败:, e, 原始数据:, dataStr); } } } } } catch (error) { console.error(流式请求失败:, error); onChunkReceived?.(\n[请求出错: ${error.message}], ); onComplete?.(); } }3.2 构建一个简单的交互界面有了处理流的核心函数我们就可以把它和一个简单的HTML界面结合起来了。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleAI对话演示 - 李慕婉模型/title style body { font-family: sans-serif; max-width: 800px; margin: 20px auto; padding: 20px; } #chat-container { border: 1px solid #ccc; border-radius: 8px; padding: 15px; height: 500px; overflow-y: auto; margin-bottom: 15px; } .message { margin-bottom: 12px; padding: 10px; border-radius: 5px; } .user-message { background-color: #e3f2fd; text-align: right; } .ai-message { background-color: #f5f5f5; } #input-area { display: flex; gap: 10px; } #user-input { flex-grow: 1; padding: 10px; border: 1px solid #ccc; border-radius: 4px; } #send-btn { padding: 10px 20px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } #send-btn:disabled { background-color: #cccccc; cursor: not-allowed; } .typing-indicator::after { content: ▋; animation: blink 1s infinite; } keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } /style /head body h2与李慕婉-仙逆-造相Z-Turbo对话/h2 div idchat-container/div div idinput-area input typetext iduser-input placeholder输入您的问题... / button idsend-btn onclicksendUserMessage()发送/button /div script const API_ENDPOINT https://your-model-service.com/v1/chat/completions; const API_KEY your-secret-api-key-here; // 警告仅用于演示生产环境必须通过后端代理 const chatContainer document.getElementById(chat-container); const userInput document.getElementById(user-input); const sendBtn document.getElementById(send-btn); // 用于存储当前AI回复的DOM元素引用 let currentAiMessageElement null; function appendMessage(content, isUser false) { const messageDiv document.createElement(div); messageDiv.className message ${isUser ? user-message : ai-message}; messageDiv.textContent content; chatContainer.appendChild(messageDiv); chatContainer.scrollTop chatContainer.scrollHeight; // 滚动到底部 return messageDiv; } function updateAiMessage(content) { if (currentAiMessageElement) { currentAiMessageElement.textContent content; chatContainer.scrollTop chatContainer.scrollHeight; } } function showTypingIndicator() { const indicator document.createElement(div); indicator.className message ai-message typing-indicator; indicator.textContent 思考中; chatContainer.appendChild(indicator); chatContainer.scrollTop chatContainer.scrollHeight; return indicator; } function removeTypingIndicator(indicatorElement) { if (indicatorElement indicatorElement.parentNode) { indicatorElement.parentNode.removeChild(indicatorElement); } } async function sendUserMessage() { const question userInput.value.trim(); if (!question) return; // 1. 禁用输入和按钮防止重复发送 userInput.value ; userInput.disabled true; sendBtn.disabled true; // 2. 在聊天框显示用户问题 appendMessage(question, true); // 3. 显示“思考中”指示器 const typingIndicator showTypingIndicator(); // 4. 创建AI消息的占位元素并移除指示器 currentAiMessageElement appendMessage(); removeTypingIndicator(typingIndicator); // 5. 调用流式函数 await sendMessageStream( question, // 每收到一个文本块的回调 (chunk, fullText) { updateAiMessage(fullText); }, // 流结束的回调 (fullText) { console.log(对话完成最终内容:, fullText); currentAiMessageElement null; // 重置引用 // 6. 重新启用输入 userInput.disabled false; sendBtn.disabled false; userInput.focus(); } ); } // 为输入框添加回车发送支持 userInput.addEventListener(keypress, (e) { if (e.key Enter !sendBtn.disabled) { sendUserMessage(); } }); // 这里是上面定义的 sendMessageStream 函数需要复制过来 async function sendMessageStream(userInput, onChunkReceived, onComplete) { // ... 将前面章节的 sendMessageStream 函数完整代码复制到这里 ... } /script /body /html把这段代码保存为HTML文件替换里面的API_ENDPOINT和API_KEY为你的实际信息用浏览器打开你就能得到一个具有实时流式对话效果的简单聊天界面了。AI的回复会逐字显示出来体验瞬间就上了一个档次。4. 进阶在Vue或React框架中集成上面的例子是纯原生JavaScript理解了原理在Vue或React等框架中集成就是组织代码和状态管理的问题了。这里以Vue 3的Composition API为例提供一个核心逻辑的写法。!-- AIChat.vue -- template div classchat-demo div classmessage-list div v-for(msg, index) in messages :keyindex :class[message, msg.role] {{ msg.content }} /div div v-ifisLoading classmessage ai typing-indicator思考中.../div /div div classinput-area input v-modeluserInput keyup.entersendMessage :disabledisLoading placeholder输入消息... / button clicksendMessage :disabledisLoading || !userInput.trim()发送/button /div /div /template script setup import { ref } from vue; const API_ENDPOINT https://your-model-service.com/v1/chat/completions; const API_KEY your-api-key; // 生产环境务必使用环境变量或通过后端接口 const messages ref([]); // 存储所有消息 {role: user|ai, content: string} const userInput ref(); const isLoading ref(false); const currentAiResponse ref(); // 当前正在流式接收的AI回复内容 async function sendMessage() { const question userInput.value.trim(); if (!question || isLoading.value) return; // 添加用户消息 messages.value.push({ role: user, content: question }); userInput.value ; isLoading.value true; // 添加一个初始为空的AI消息占位 messages.value.push({ role: ai, content: }); const aiMessageIndex messages.value.length - 1; try { const response await fetch(API_ENDPOINT, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${API_KEY} }, body: JSON.stringify({ model: limuwan-xianni-zaoxiang-z-turbo, messages: [...messages.value.slice(0, -1).map(m ({ role: m.role, content: m.content })), { role: user, content: question }], stream: true, max_tokens: 500, }) }); if (!response.ok) throw new Error(请求失败: ${response.status}); const reader response.body.getReader(); const decoder new TextDecoder(); let accumulatedText ; while (true) { const { done, value } await reader.read(); if (done) break; const chunk decoder.decode(value); const lines chunk.split(\n).filter(l l.trim()); for (const line of lines) { if (line.startsWith(data: )) { const data line.slice(6); if (data [DONE]) continue; try { const parsed JSON.parse(data); const textDelta parsed.choices[0]?.delta?.content || ; if (textDelta) { accumulatedText textDelta; // 更新Vue响应式数据触发视图更新 messages.value[aiMessageIndex].content accumulatedText; } } catch (e) { console.error(解析错误:, e); } } } } } catch (error) { console.error(对话出错:, error); messages.value[aiMessageIndex].content 抱歉出错了: ${error.message}; } finally { isLoading.value false; currentAiResponse.value ; } } /script style scoped /* 样式与原生示例类似此处省略 */ /style在React中思路类似使用useState来管理消息列表和加载状态在fetch流式响应的回调中更新状态即可。核心的流式读取逻辑是完全通用的。5. 实际应用中的优化与考量跑通基本功能只是第一步要真正用在项目里还有一些实际问题需要考虑。1. 安全性API密钥不能放在前端这是最重要的一点。上面的示例为了清晰把API密钥写在了前端代码里这是极其危险的做法任何人查看网页源代码都能拿到你的密钥。正确的做法是后端代理搭建一个简单的后端服务可以用Node.js、Python Flask等。前端只请求你自己的后端后端负责添加API密钥再去请求真正的模型服务并转发流式响应。环境变量与配置管理即使在后端密钥也应通过环境变量或安全的配置中心管理不要写入代码。2. 错误处理与用户体验网络超时为fetch请求设置合理的超时时间使用AbortController。服务降级如果流式请求失败可以尝试 fallback 到非流式请求或者给出友好的错误提示。中断生成提供“停止生成”按钮其原理是使用AbortController中断正在进行的fetch请求。3. 对话历史管理上面的简单例子只发送了当前消息。一个完整的聊天应用需要维护对话历史上下文。你需要将之前的对话记录可能截取最近N轮也放入messages数组中发送给模型这样AI才能理解上下文。注意管理上下文长度避免超出模型限制。4. 性能与优化防抖与节流如果输入框有实时补全功能需要对请求做防抖处理。虚拟列表如果聊天记录非常长考虑使用虚拟滚动技术来优化渲染性能。缓存策略对于一些常见的、回答固定的问题可以考虑在前端或后端做缓存。6. 总结把像“李慕婉-仙逆-造相Z-Turbo”这样的大模型能力通过JavaScript集成到前端核心就是处理好流式通信。用Fetch API读取ReadableStream然后逐步将数据块更新到UI上这个模式能极大提升交互的实时感和流畅度。从简单的原生JS实现到Vue/React框架的集成思路都是一脉相承的。在实际项目中关键是要解决好安全性问题务必通过后端代理API并完善错误处理、上下文管理这些细节这样才能打造出既强大又用户体验良好的AI前端应用。整个过程试下来感觉技术门槛并没有想象中那么高主要是思路要清晰。希望这个分享能帮你快速上手把你的AI想法变成用户可以直观感受和交互的产品。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章