ToB企服应用市场:ToB评测及商务社交产业平台

标题: 纯前端:极速构建一个私有ChatGPT!AI套壳居然云云简朴! [打印本页]

作者: 美食家大橙子    时间: 2024-12-9 01:49
标题: 纯前端:极速构建一个私有ChatGPT!AI套壳居然云云简朴!
注:CSDN不支持超过10M的动图显示,以是文中会有部门动图展示不出来。
  如果想拥有更好地阅读体验,可以去我的文章首发地点:https://juejin.cn/post/7441774623833260070

  
随着人工智能Chatgpt的出现,各种国产的AI大模子应用而出,如豆包文心一言通义千问kimi等等,同时,我们也发现有各种各样的套壳APP在割韭菜!
作为一个程序员,我们不能助长这种套壳App的嚣张气势,我们要用邪术打败邪术,实现一个只属于我们自己的ChatGpt!
在这篇文章中,我将介绍怎样利用 Node.jsWebSocket 实现一个基于kimi(Moonshot 月之暗大模子)的AI工具,让我们快速拥有属于自己的ChatGpt。大致实现的效果:

基于本文demo代码,你将能快速构建比较完备,且专属于你的ChatGpt,赶快收藏,让我们一起学习套壳应用的奥秘!
技术栈简介


前端界面比较容易,只需要简朴的额css + js即可,本文利用vue作为作为demo。

   有人可能问,你怎么不自己开辟大语言模子?可笑,我要套壳实现白嫖!
  开辟前准备

要利用Moonshot的 AI语言模子能力,我们起首要登录其官网,申请属于自己的API Key,通过这个key,我们就可以实现接口调用,完成自己的AI助手搭建。
注册账号

起首,我们需要登录kimi官网,注册账号:

创建API Key

登录后台后,选择【API Key管理】面板,点击【创建】按钮,即可创建自己的密钥。这个密钥就是我们需要利用的API Key。创建好后,把它复制生存起来。

关于费用

作为一个白嫖用户,充值是不可能充值的。注册后,系统免费赠予15,用不完,根本用不完!

技术方案

核心项目搭建

服务端搭建

搭建后端服务,其实就是调用Kimi API 获取问答信息返回给前端。
Kimi API 兼容了 OpenAI 的接口规范,因此,我们可以直接利用 OpenAI 提供的NodeJS(opens in a new tab) SDK 来调用和利用 Kimi 大模子:
  1. npm i koa koa-websocket openai
复制代码
项目初始化完毕后,在根目录创建app.js文件
  1. const Koa = require("koa");
  2. const websocket = require("koa-websocket");
  3. const OpenAI = require("openai");
  4. const app = websocket(new Koa());
  5. // 配置 Moonshot AI 客户端
  6. const client = new OpenAI({
  7.   apiKey: "你自己在kimi后台创建的API key",
  8.   baseURL: "https://api.moonshot.cn/v1", // Moonshot API 的基础路径
  9. });
  10. // WebSocket 路由
  11. app.ws.use((ctx) => {
  12.   // .....
  13. });
  14. // 启动服务器
  15. app.listen(3000, () => {
  16.   console.log("服务已启动,监听 ws://localhost:3000");
  17. });
复制代码
上述代码中,WebSocket 路由内部的逻辑也非常简朴,它的逻辑流程如下:

  1. // WebSocket 路由
  2. app.ws.use((ctx) => {
  3.   console.log("WebSocket connected");
  4.   // 1.监听前端发送的消息
  5.   ctx.websocket.on("message", async (message) => {
  6.     const { content } = JSON.parse(message); // 从前端接收的 JSON 消息中解析用户输入
  7.     try {
  8.       // 2.调用 Moonshot AI 的聊天接口
  9.       const completion = await client.chat.completions.create({
  10.         model: "moonshot-v1-8k",
  11.         messages: [
  12.           { role: "user", content },
  13.         ],
  14.         temperature: 0.3, // 控制回答的随机性
  15.       });
  16.       // 3.获取 Kimi 的回答内容
  17.       const reply = completion.choices[0]?.message?.content
  18.       // 4.将回答发送到前端
  19.       ctx.websocket.send(JSON.stringify({ reply }));
  20.     } catch (error) {
  21.       ctx.websocket.send(
  22.         JSON.stringify({ reply: "Kimi 暂时无法回答您的问题,请稍后再试。" })
  23.       );
  24.     }
  25.   });
  26.   ctx.websocket.on("close", () => {
  27.     console.log("WebSocket connection closed");
  28.   });
  29. });
复制代码
  接口中的temperature值用于控制回答的随机性,Kimi API 的 <font style="color:rgb(51, 65, 85);">temperature</font> 参数的取值范围是 <font style="color:rgb(51, 65, 85);">[0, 1]</font>,官方推荐取值为0.3
  至此,后端服务就搭建完毕了,我们执行下面的命令启动服务
  1. node app.js
复制代码
前端搭建

参考其他的AI助手,前端的界面一般都非常简朴,我们直接参考微信聊天界面,做一个简易的对话框即可。
  1. <template>
  2.     <div class="chat-container">
  3.         <div class="chat-box">
  4.             <div class="messages">
  5.                 <!-- 显示聊天记录 -->
  6.                 <div v-for="(message, index) in messages" :key="index" class="message-wrapper"
  7.                     :class="message.role === 'user' ? 'user-message' : 'ai-message'">
  8.                     <div class="message">
  9.                         <p>{{ message.content }}</p>
  10.                     </div>
  11.                 </div>
  12.             </div>
  13.         </div>
  14.         <div class="input-box">
  15.             <textarea v-model="userInput" placeholder="请输入您的问题..." @keyup.enter="sendMessage"></textarea>
  16.             <button @click="sendMessage">发送</button>
  17.         </div>
  18.     </div>
  19. </template>
  20. <script setup>
  21. import { ref } from 'vue';
  22. const messages = ref([]);
  23. const userInput = ref('');
  24. const socket = new WebSocket('ws://localhost:3000');
  25. // 监听服务端消息
  26. socket.onmessage = (event) => {
  27.     const data = JSON.parse(event.data);
  28.     messages.value.push({ role: 'ai', content: data.reply });
  29. };
  30. // 发送用户消息
  31. const sendMessage = () => {
  32.     if (!userInput.value.trim()) return;
  33.     // 添加用户输入到消息列表
  34.     messages.value.push({ role: 'user', content: userInput.value });
  35.     // 通过 WebSocket 发送到后端
  36.     socket.send(JSON.stringify({ content: userInput.value }));
  37.     userInput.value = ''; // 清空输入框
  38. };
  39. </script>
复制代码
  1. <style scoped lang="less">
  2. .chat-container {
  3.   height: 100vh;
  4.   background-color: #f6f7f9;
  5.   overflow: hidden;
  6.   .chat-box {
  7.     height: calc(100% - 60px);
  8.     box-sizing: border-box;
  9.     padding: 16px;
  10.     overflow-y: auto;
  11.     background-color: #ffffff;
  12.     .messages {
  13.       display: flex;
  14.       flex-direction: column;
  15.       gap: 12px;
  16.     }
  17.     .message-wrapper {
  18.       display: flex;
  19.       .message {
  20.         max-width: 70%;
  21.         padding: 5px 16px;
  22.         border-radius: 18px;
  23.         font-size: 14px;
  24.         line-height: 1.5;
  25.         white-space: pre-wrap;
  26.         word-wrap: break-word;
  27.         box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
  28.       }
  29.     }
  30.     .user-message {
  31.       justify-content: flex-end;
  32.       .message {
  33.         background-color: #0084ff;
  34.         color: #ffffff;
  35.         text-align: right;
  36.         border-bottom-right-radius: 4px;
  37.       }
  38.     }
  39.     .ai-message {
  40.       justify-content: flex-start;
  41.       .message {
  42.         background-color: #f1f0f0;
  43.         color: #333333;
  44.         text-align: left;
  45.         border-bottom-left-radius: 4px;
  46.       }
  47.     }
  48.   }
  49.   .input-box {
  50.     height: 60px;
  51.     display: flex;
  52.     align-items: center;
  53.     gap: 8px;
  54.     background-color: #e5e5e5;
  55.     border-top: 1px solid #e5e5e5;
  56.     padding: 0 10px;
  57.     button {
  58.       padding: 5px 20px;
  59.       background-color: #0084ff;
  60.       color: #ffffff;
  61.       border: none;
  62.       border-radius: 10px;
  63.       font-size: 14px;
  64.       cursor: pointer;
  65.       box-shadow: 0 2px 4px rgba(0, 132, 255, 0.3);
  66.       transition: background-color 0.3s ease;
  67.     }
  68.     button:hover {
  69.       background-color: #006bbf;
  70.     }
  71.     button:active {
  72.       background-color: #0056a3;
  73.     }
  74.     textarea {
  75.       flex: 1;
  76.       padding: 10px;
  77.       border: 1px solid #d5d5d5;
  78.       border-radius: 15px;
  79.       resize: none;
  80.       font-size: 14px;
  81.       background-color: #ffffff;
  82.       box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
  83.       outline: none;
  84.       height: 20px;
  85.     }
  86.     textarea:focus {
  87.       border-color: #0084ff;
  88.       box-shadow: inset 0 1px 4px rgba(0, 132, 255, 0.2);
  89.     }
  90.   }
  91. }
  92. </style>
复制代码
上述代码实现了一个简朴的聊天界面,利用 WebSocket 实现前后端通信,大致代码逻辑如下:
响应式数据
  1. - `messages`:存储所有聊天记录的数组,role字段储信息来自用户还是AI。
  2. - `userInput`:用户输入框的内容,绑定到 `textarea`。
复制代码
WebSocket 通信
  1. - **连接服务端**:通过 `new WebSocket('ws://localhost:3000')` 创建连接。ws://localhost:3000是我们后端服务的运行地址。
  2. - **接收消息**:监听 `onmessage` 事件,将服务端返回的数据解析后追加到 `messages` 中。
  3. - **发送消息**:在 `sendMessage` 方法中:
  4.     * 验证输入框是否为空。
  5.     * 将用户消息推送到 `messages`。
  6.     * 使用 `socket.send` 将输入内容以 JSON 格式发送到服务端。
复制代码
现在,我们运行前端项目是还效果:

非常nice啊兄弟们!固然交互可能不是很好,但是根本功能实现了!
   不外眼睛尖的同砚们可能也看见了,末了一次输入666的时候,接口报错了。嗐,毕竟我们是白嫖用户,有用量限制:一分钟内只支持连续3次问题:
  

  进行多轮对话

现在,你可能会发现,我们的AI助手并不能实现多轮对话,对话是不连续的。这很正常,API 本身不具有记忆功能,它是无状态的,当我们多次请求 API 时,Kimi 大模子并不知道我们前一次请求的内容。
要解决这个问题,我们可以手动维护每次请求的上下文,把上一次请求过的内容手动参加到下一次请求中,让 Kimi 大模子能正确知道我们之前都聊了什么。
现在,我们维护 一个messages 列表让 Kimi 大模子拥有记忆,并实现多轮对话功能。我们轻微改造下后端代码:
  1. const Koa = require("koa");
  2. const websocket = require("koa-websocket");
  3. const OpenAI = require("openai");
  4. require("dotenv").config();
  5. const app = websocket(new Koa());
  6. // 配置 Moonshot AI 客户端
  7. const client = new OpenAI({
  8.   apiKey: "你自己在kimi后台创建的API key",
  9.   baseURL: "https://api.moonshot.cn/v1",
  10. });
  11. // 定义全局 messages 对象,按 WebSocket 连接 ID 记录会话上下文
  12. const sessions = {};
  13. app.ws.use((ctx) => {
  14.   // 分配唯一会话 ID(以 WebSocket 对象为标识)
  15.   const sessionId = ctx.websocket;
  16.   // 初始化当前会话的历史记录
  17.   sessions[sessionId] = [];
  18.   // 监听前端发送的消息
  19.   ctx.websocket.on("message", async (message) => {
  20.     const { content } = JSON.parse(message);
  21.     try {
  22.       // 将用户问题追加到历史消息
  23.       const userMessage = { role: "user", content };
  24.       sessions[sessionId].push(userMessage);
  25.       // 调用 Moonshot API
  26.       const completion = await client.chat.completions.create({
  27.         model: "moonshot-v1-8k",
  28.         messages: sessions[sessionId],
  29.         temperature: 0.3,
  30.       });
  31.       // 记录 AI 回复并发送给前端
  32.       const assistantMessage = completion.choices[0]?.message || {
  33.         role: "assistant",
  34.         content: "抱歉,我暂时无法回答您的问题。",
  35.       };
  36.       sessions[sessionId].push(assistantMessage);
  37.       ctx.websocket.send(JSON.stringify({ reply: assistantMessage.content }));
  38.     } catch (error) {
  39.       ctx.websocket.send(
  40.         JSON.stringify({ reply: "Kimi 暂时无法回答您的问题,请稍后再试。" })
  41.       );
  42.     }
  43.   });
  44.   // 关闭 WebSocket 时清除会话记录
  45.   ctx.websocket.on("close", () => {
  46.     console.log("WebSocket connection closed");
  47.     delete sessions[sessionId];
  48.   });
  49. });
  50. // 启动服务器
  51. app.listen(3000, () => {
  52.   console.log("服务已启动,监听 ws://localhost:3000");
  53. });
复制代码
上述代码的实现其实非常简朴,我们维护一个 **sessions** 对象,用于给每一个用户连接有一个专属记录(通过 WebSocket 的 sessionId 区分)。当用户发送消息时,值生存到对应的 sessionId 的记录里(包括用户的消息和 AI 的回复)。然后每次对话,我们都可以把完整的对话汗青传给 AI,AI 就能“记住”之前聊过的内容。
我们看看代码实现的效果:

通过上图,我们可以看出多轮对话的效果已经根本实现了!
自动选择Kimi 大模子

上述的代码中,我们利用的是kimi固定的 <font style="color:rgb(51, 65, 85);">moonshot-v1-8k</font> 模子 ,当对话的轮次越来越多时,继承调用 <font style="color:rgb(51, 65, 85);">chat</font> 函数会得到一个 <font style="color:rgb(51, 65, 85);">Your request exceeded model token limit</font> 错误。此时,如果我们想继承刚才的上下文接着与 Kimi 大模子对话,需要切换一个更大上下文的模子,比方 <font style="color:rgb(51, 65, 85);">moonshot-v1-32k</font>。
但是,选择合适的模子非常麻烦,我们直接利用kimi官方的moonshot-v1-auto 模子,它可以根据对话次数内部调用合适的模子,yyds!

利用Stream流式输出

现在,我们已经根本实现AI的连续问答功能了,但是美中不敷,它的反应很慢。这是由于kimi后台后等问题全部生成后,才返回前端,如许显得就非常慢!那么,我们怎样像ChatGpt一样,实时按字符返回结果呢?
非常简朴,我们利用 Kimi API 的流式输出功能 —— Streaming 即可!

服务端的代码更改非常容易
  1. // WebSocket 路由
  2. app.ws.use((ctx) => {
  3.   // 初始化上下文消息
  4.   let messages = [];
  5.   // 监听前端发送的消息
  6.   ctx.websocket.on("message", async (message) => {
  7.     const { content } = JSON.parse(message); // 获取用户输入
  8.     // 添加用户输入到上下文消息中
  9.     messages.push({ role: "user", content });
  10.     try {
  11.       // 开启流式输出
  12.       const stream = await client.chat.completions.create({
  13.         model: "moonshot-v1-8k",
  14.         messages,
  15.         temperature: 0.3,
  16.         stream: true,
  17.       });
  18.       ctx.websocket.send(JSON.stringify({ reply: "", isStreaming: true }));
  19.       let fullReply = ""; // 用于记录完整回复
  20.       for await (const chunk of stream) {
  21.         const delta = chunk.choices[0]?.delta;
  22.         if (delta?.content) {
  23.           ctx.websocket.send(JSON.stringify({ reply: delta.content }));
  24.           fullReply += delta.content;
  25.         }
  26.       }
  27.       // 将 AI 回复添加到上下文消息中
  28.       messages.push({ role: "assistant", content: fullReply });
  29.     } catch (error) {
  30.       ctx.websocket.send(
  31.         JSON.stringify({ reply: "Kimi 暂时无法回答您的问题,请稍后再试。" })
  32.       );
  33.     }
  34.   });
  35. });
  36. // 启动服务器
  37. app.listen(3000, () => {
  38.   console.log("服务已启动,监听 ws://localhost:3000");
  39. });
复制代码
  上述代码通过启用 stream: true 设置,实时接收 AI 的部门回复数据流 (chunk.choices[0]?.delta)。每次接收到新内容时,立即通过 WebSocket 发送给客户端,确保用户可以逐字看到 AI 的回复。
  相应的,为了兼容流数据,我们前端代码也需要简朴的调整一下
  1. <script setup>
  2. import { ref } from "vue";
  3. // WebSocket 初始化
  4. const socket = new WebSocket("ws://localhost:3000");
  5. const messages = ref([]); // 聊天记录
  6. const userInput = ref(""); // 用户输入内容
  7. let isStreaming = false; // 是否正在流式接收数据
  8. let streamingMessage = ""; // 当前正在接收的消息
  9. // 监听后端发送的消息
  10. socket.onmessage = (event) => {
  11.   const data = JSON.parse(event.data);
  12.   // 如果是流式输出,逐步更新
  13.   if (data.isStreaming) {
  14.     isStreaming = true;
  15.     streamingMessage = "";
  16.     messages.value.push({ role: "assistant", content: "" });
  17.   } else if (isStreaming && data.reply) {
  18.     streamingMessage += data.reply;
  19.     messages.value[messages.value.length - 1].content = streamingMessage;
  20.   } else {
  21.     isStreaming = false;
  22.   }
  23. };
  24. // 发送消息给后端
  25. const sendMessage = () => {
  26.   if (!userInput.value.trim()) return;
  27.   // 添加用户消息到消息列表
  28.   messages.value.push({ role: "user", content: userInput.value });
  29.   // 将消息通过 WebSocket 发送到后端
  30.   socket.send(
  31.     JSON.stringify({
  32.       content: userInput.value,
  33.     })
  34.   );
  35.   userInput.value = ""; // 清空输入框
  36. };
  37. </script>
复制代码
  前端通过监听 WebSocket 的 onmessage 变乱接收流数据。若消息标志为流式 (data.isStreaming),初始化流式状态并在消息列表中添加占位符。后续每段流数据 (data.reply) 会渐渐追加到 streamingMessage 并实时更新末了一条消息的内容,模仿逐字显示效果。完成后重置流式状态。
  现在,我们看看效果:

显示代码块

现在我们已经实现了AI的实时问答功能,但是如上图,它不能显示代码块,看起来比较难受。
我们可以安装 markdown-it 和 highlight.js来实当代码块的展示。
安装依赖
安装 markdown-it 和 highlight.js:
  1. npm install markdown-it highlight.js
复制代码
改造代码显示逻辑
在前端利用 markdown-it 解析 Markdown 内容,并团结 highlight.js 实当代码块高亮。
  1. <template>
  2.   <div class="chat-container">
  3.   <div class="chat-box">
  4.   <div class="messages">
  5.   <div
  6. v-for="(message, index) in messages"
  7. :key="index"
  8. class="message-wrapper"
  9. :class="message.role === 'user' ? 'user-message' : 'ai-message'"
  10.   >
  11.   <div
  12.   class="message"
  13.   v-html="renderMessageContent(message.content)"
  14.   ></div>
  15.   </div>
  16.   </div>
  17.   </div>
  18.   <div class="input-box">
  19.   <textarea
  20. v-model="userInput"
  21. placeholder="请输入您的问题..."
  22. @keyup.enter="sendMessage"
  23.   ></textarea>
  24.   <button @click="sendMessage">发送</button>
  25.   </div>
  26.   </div>
  27.   </template>
  28.   <script setup>
  29.   import { ref } from "vue";
  30. import MarkdownIt from "markdown-it";
  31. import hljs from "highlight.js";
  32. import "highlight.js/styles/github.css"; // 引入代码高亮样式
  33. // 初始化 Markdown-it,并配置代码高亮
  34. const md = new MarkdownIt({
  35.   highlight: (code, lang) => {
  36.     if (lang && hljs.getLanguage(lang)) {
  37.       return hljs.highlight(code, { language: lang }).value;
  38.     }
  39.     return ""; // 如果未指定语言,则返回原始代码
  40.   },
  41. });
  42. // WebSocket 初始化
  43. const socket = new WebSocket("ws://localhost:3000");
  44. const messages = ref([]); // 聊天记录
  45. const userInput = ref(""); // 用户输入内容
  46. let isStreaming = false; // 是否正在流式接收数据
  47. let streamingMessage = ""; // 当前正在接收的消息
  48. // 渲染消息内容(支持 Markdown 和普通文本)
  49. const renderMessageContent = (content) => {
  50.   return md.render(content);
  51. };
  52. // 监听后端发送的消息
  53. socket.onmessage = (event) => {
  54.   const data = JSON.parse(event.data);
  55.   if (data.isStreaming) {
  56.     isStreaming = true;
  57.     streamingMessage = "";
  58.     messages.value.push({ role: "assistant", content: "" });
  59.   } else if (isStreaming && data.reply) {
  60.     streamingMessage += data.reply;
  61.     messages.value[messages.value.length - 1].content = streamingMessage;
  62.   } else {
  63.     isStreaming = false;
  64.   }
  65. };
  66. // 发送消息给后端
  67. const sendMessage = () => {
  68.   if (!userInput.value.trim()) return;
  69.   messages.value.push({ role: "user", content: userInput.value });
  70.   socket.send(
  71.     JSON.stringify({
  72.       content: userInput.value,
  73.     })
  74.   );
  75.   userInput.value = "";
  76. };
  77. </script>
复制代码
我们在试试效果:

个性化定制

我们也可以给我们的AI在每次发送信息前,携带一些固定的预设值,实现AI的定制话化!我们只需要在后端服务的messages中增加一些预设值即可。
  1.   let messages = [
  2.     {
  3.       role: "system",
  4.       content:
  5.         "你是一个AI助手,请你模拟一个阴阳怪气的人,用户无论问什么,你都要阴阳怪气的回答!",
  6.     },
  7.   ];
复制代码
我们试试效果:

我们在换种风格玩玩
  1.   let messages = [
  2.     {
  3.       role: "system",
  4.       content:
  5.         "请模拟我的女朋友,我问什么都要一副不耐烦,对我爱答不理的样子.",
  6.     },
  7.   ];
复制代码

兄弟们,真实啊,这语气像不像你的女朋友?
前后端完整代码

前端
  1. <template>
  2.   <div class="chat-container">
  3.     <div class="chat-box">
  4.       <div class="messages">
  5.         <div
  6.           v-for="(message, index) in messages"
  7.           :key="index"
  8.           class="message-wrapper"
  9.           :class="message.role === 'user' ? 'user-message' : 'ai-message'"
  10.         >
  11.           <div
  12.             class="message"
  13.             v-html="renderMessageContent(message.content)"
  14.           ></div>
  15.         </div>
  16.       </div>
  17.     </div>
  18.     <div class="input-box">
  19.       <textarea
  20.         v-model="userInput"
  21.         placeholder="请输入您的问题..."
  22.         @keyup.enter="sendMessage"
  23.       ></textarea>
  24.       <button @click="sendMessage">发送</button>
  25.     </div>
  26.   </div>
  27. </template>
  28. <script setup>
  29. import { ref } from "vue";
  30. import MarkdownIt from "markdown-it";
  31. import hljs from "highlight.js";
  32. import "highlight.js/styles/github.css"; // 引入代码高亮样式
  33. // 初始化 Markdown-it,并配置代码高亮
  34. const md = new MarkdownIt({
  35.   highlight: (code, lang) => {
  36.     if (lang && hljs.getLanguage(lang)) {
  37.       return hljs.highlight(code, { language: lang }).value;
  38.     }
  39.     return ""; // 如果未指定语言,则返回原始代码
  40.   },
  41. });
  42. // WebSocket 初始化
  43. const socket = new WebSocket("ws://localhost:3000");
  44. const messages = ref([]); // 聊天记录
  45. const userInput = ref(""); // 用户输入内容
  46. let isStreaming = false; // 是否正在流式接收数据
  47. let streamingMessage = ""; // 当前正在接收的消息
  48. // 渲染消息内容(支持 Markdown 和普通文本)
  49. const renderMessageContent = (content) => {
  50.   return md.render(content);
  51. };
  52. // 监听后端发送的消息
  53. socket.onmessage = (event) => {
  54.   const data = JSON.parse(event.data);
  55.   if (data.isStreaming) {
  56.     isStreaming = true;
  57.     streamingMessage = "";
  58.     messages.value.push({ role: "assistant", content: "" });
  59.   } else if (isStreaming && data.reply) {
  60.     streamingMessage += data.reply;
  61.     messages.value[messages.value.length - 1].content = streamingMessage;
  62.   } else {
  63.     isStreaming = false;
  64.   }
  65. };
  66. // 发送消息给后端
  67. const sendMessage = () => {
  68.   if (!userInput.value.trim()) return;
  69.   messages.value.push({ role: "user", content: userInput.value });
  70.   socket.send(
  71.     JSON.stringify({
  72.       content: userInput.value,
  73.     })
  74.   );
  75.   userInput.value = "";
  76. };
  77. </script>
  78. <style scoped lang="less">
  79. .chat-container {
  80.   height: 100vh;
  81.   background-color: #f6f7f9;
  82.   overflow: hidden;
  83.   .chat-box {
  84.     height: calc(100% - 60px);
  85.     box-sizing: border-box;
  86.     padding: 16px;
  87.     overflow-y: auto;
  88.     background-color: #ffffff;
  89.     .messages {
  90.       display: flex;
  91.       flex-direction: column;
  92.       gap: 12px;
  93.     }
  94.     .message-wrapper {
  95.       display: flex;
  96.       .message {
  97.         max-width: 70%;
  98.         padding: 5px 16px;
  99.         border-radius: 18px;
  100.         font-size: 14px;
  101.         line-height: 1.5;
  102.         white-space: pre-wrap;
  103.         word-wrap: break-word;
  104.         box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
  105.       }
  106.     }
  107.     .user-message {
  108.       justify-content: flex-end;
  109.       .message {
  110.         background-color: #0084ff;
  111.         color: #ffffff;
  112.         text-align: right;
  113.         border-bottom-right-radius: 4px;
  114.       }
  115.     }
  116.     .ai-message {
  117.       justify-content: flex-start;
  118.       .message {
  119.         background-color: #f1f0f0;
  120.         color: #333333;
  121.         text-align: left;
  122.         border-bottom-left-radius: 4px;
  123.         pre {
  124.           background-color: #f6f8fa;
  125.           padding: 10px;
  126.           border-radius: 6px;
  127.           overflow-x: auto;
  128.         }
  129.       }
  130.     }
  131.   }
  132.   .input-box {
  133.     height: 60px;
  134.     display: flex;
  135.     align-items: center;
  136.     gap: 8px;
  137.     background-color: #e5e5e5;
  138.     border-top: 1px solid #e5e5e5;
  139.     padding: 0 10px;
  140.     button {
  141.       padding: 5px 20px;
  142.       background-color: #0084ff;
  143.       color: #ffffff;
  144.       border: none;
  145.       border-radius: 10px;
  146.       font-size: 14px;
  147.       cursor: pointer;
  148.       box-shadow: 0 2px 4px rgba(0, 132, 255, 0.3);
  149.       transition: background-color 0.3s ease;
  150.     }
  151.     button:hover {
  152.       background-color: #006bbf;
  153.     }
  154.     button:active {
  155.       background-color: #0056a3;
  156.     }
  157.     textarea {
  158.       flex: 1;
  159.       padding: 10px;
  160.       border: 1px solid #d5d5d5;
  161.       border-radius: 15px;
  162.       resize: none;
  163.       font-size: 14px;
  164.       background-color: #ffffff;
  165.       box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
  166.       outline: none;
  167.       height: 20px;
  168.     }
  169.     textarea:focus {
  170.       border-color: #0084ff;
  171.       box-shadow: inset 0 1px 4px rgba(0, 132, 255, 0.2);
  172.     }
  173.   }
  174. }
  175. </style>
复制代码
服务端
  1. const Koa = require("koa");
  2. const websocket = require("koa-websocket");
  3. const OpenAI = require("openai");
  4. require("dotenv").config();
  5. const app = websocket(new Koa());
  6. // 配置 Moonshot AI 客户端
  7. const client = new OpenAI({
  8.   apiKey: "sk-I0a8XUeEavnJ381o0IQKiIXpAYrUttliu", // 替换为你的 Moonshot API Key
  9.   baseURL: "https://api.moonshot.cn/v1", // Moonshot API 基础路径
  10. });
  11. // WebSocket 路由
  12. app.ws.use((ctx) => {
  13.   console.log("WebSocket connected");
  14.   // 初始化上下文消息
  15.   let messages = [
  16.     {
  17.       role: "system",
  18.       content:
  19.         "你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。用户问什么问题,你都阴阳怪气他!",
  20.     },
  21.   ];
  22.   // 监听前端发送的消息
  23.   ctx.websocket.on("message", async (message) => {
  24.     const { content } = JSON.parse(message); // 获取用户输入
  25.     // 添加用户输入到上下文消息中
  26.     messages.push({ role: "user", content });
  27.     try {
  28.       // 开启流式输出
  29.       const stream = await client.chat.completions.create({
  30.         model: "moonshot-v1-8k",
  31.         messages,
  32.         temperature: 0.3,
  33.         stream: true,
  34.       });
  35.       ctx.websocket.send(JSON.stringify({ reply: "", isStreaming: true }));
  36.       let fullReply = ""; // 用于记录完整回复
  37.       for await (const chunk of stream) {
  38.         const delta = chunk.choices[0]?.delta;
  39.         if (delta?.content) {
  40.           ctx.websocket.send(JSON.stringify({ reply: delta.content }));
  41.           fullReply += delta.content;
  42.         }
  43.       }
  44.       // 将 AI 回复添加到上下文消息中
  45.       messages.push({ role: "assistant", content: fullReply });
  46.     } catch (error) {
  47.       console.error("调用 Moonshot API 出错:", error.message);
  48.       ctx.websocket.send(
  49.         JSON.stringify({ reply: "Kimi 暂时无法回答您的问题,请稍后再试。" })
  50.       );
  51.     }
  52.   });
  53. });
  54. // 启动服务器
  55. app.listen(3000, () => {
  56.   console.log("服务已启动,监听 ws://localhost:3000");
  57. });
复制代码
总结

本文带各人实现了一个简易的AI工具,具备实时通信、Markdown 支持和流式输出等特点,根本席卷了AI 的一些核心功能,相信各人基于此demo肯定能实现属于自己的套壳gpt了。
末了,我简朴做下技术总结吧:
前后端通过 WebSocket 通信,后端集成了 Moonshot AI 的 Kimi 模子处置处罚用户输入并生成回复。
前端功能

后端功能

关注我,我是石小石!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4