微信小程序实现接入chatgpt 文心一言 moonshot 和豆包等大模型,然后实现流式返回数据渲染到页面上,实现打字机效果,支持重新生成回答和复制回答

微信小程序实现接入chatgpt 文心一言 moonshot 和豆包等大模型,然后实现流式返回数据渲染到页面上,实现打字机效果,支持重新生成回答和复制回答

使用了Vant组件 和 towxml来实现解析markdown 和实现代码高亮

效果图如下

js代码

const app = getApp();

Page({
  data: {
    inputText: '',            // 用户输入的消息
    chatHistory: [],          // 存储聊天历史
    currentChunk: '',         // 存储当前正在接收的流式数据块
    isLoading: false,         // AI响应加载状态
  },

  onLoad: function() {
    // 页面加载时初始化
  },

  // 处理输入框变化
  onInput: function(e) {
    this.setData({
      inputText: e.detail.value,
    });
  },

  // 发送用户消息并获取AI响应
  sendMessage: function() {
    const userMessage = this.data.inputText.trim();
    if (!userMessage) return;

    // 添加用户消息到聊天历史
    this.addMessageToHistory(userMessage, 'user');

    // 清空输入框
    this.setData({
      inputText: '',
      isLoading: true,
      currentChunk: '' // 清空当前流式数据的块
    });

    // 调用 AI 接口获取回复
    this.requestAiResponse(userMessage);
  },

  // AI接口请求方法,复用的代码块
  requestAiResponse: function(message) {
    const self = this;
    const requestTask = wx.request({
      url: 'https://www.xxxx.com/chat.php', // 替换为你的AI API地址
      method: 'POST',
      data: {
        message: message, // 发送提问到 AI
      },
      enableChunked: true,
      success(res) {
        // 处理成功响应
      },
      fail(err) {
        console.error(err);
        self.setData({ isLoading: false });
      }
    });

    // 监听数据块接收事件
    requestTask.onChunkReceived(function(res) {
      try {
        const chunkText = self.decode(res.data); // 处理ArrayBuffer
        const dataLines = chunkText.trim().split('\n');
        
        dataLines.forEach(line => {
          if (line.startsWith('data: ')) {
            const jsonString = line.slice(6); // 去掉 'data: ' 前缀

            if (jsonString === '[DONE]') {
              console.log('[DONE]');
              return;
            }

            const chunkData = JSON.parse(jsonString);

            if (chunkData.choices && chunkData.choices[0].delta && chunkData.choices[0].delta.content) {
              const newContent = chunkData.choices[0].delta.content;

              // 将新内容追加到当前正在处理的消息块中
              self.data.currentChunk += newContent;

              // 实时渲染新内容
              self.renderChunk(self.data.currentChunk);
            }
          }
        });
      } catch (e) {
        console.error('解析数据块失败:', res.data, e);
      }
    });
  },

  // 实时渲染当前内容
  renderChunk: function(chunk) {
    const self = this;

    // 使用 Towxml 实时解析并渲染
    const parsedMarkdown = app.towxml(chunk, 'markdown', {
      theme: 'light',
      events: {
        tap: (e) => {
          console.log('tap', e);
        }
      }
    });

    // 查找聊天记录中最新的 AI 消息并更新其内容
    let lastIndex = self.data.chatHistory.length - 1;
    let lastMessage = self.data.chatHistory[lastIndex];

    if (lastMessage && lastMessage.sender === 'ai') {
      // 更新最后一条 AI 消息的内容
      lastMessage.markdownContent = parsedMarkdown;
      lastMessage.text = chunk;

      self.setData({
        [`chatHistory[${lastIndex}]`]: lastMessage // 实时更新最后一条消息
      });
    } else {
      // 如果是新的消息则添加到聊天历史中
      self.addMessageToHistory(chunk, 'ai', parsedMarkdown);
    }
  },

  // 将消息添加到聊天历史
  addMessageToHistory: function(text, sender, markdownContent = '') {
    const newMessage = {
      text, 
      sender,
      markdownContent
    };
    this.setData({
      chatHistory: [...this.data.chatHistory, newMessage],
    });
  },

  // 将 ArrayBuffer 转为字符串
  decode: function(arrayBuffer) {
    const uint8Array = new Uint8Array(arrayBuffer);
    const encodedString = String.fromCharCode.apply(null, uint8Array);
    const decodedString = decodeURIComponent(escape(encodedString)); // 解码为 UTF-8
    return decodedString;
  },

  // 提取 markdownContent 中的纯文本
  extractTextFromMarkdown: function(nodes) {
    let textContent = '';
    const traverse = (node) => {
      if (typeof node === 'string') {
        textContent += node;
      } else if (Array.isArray(node)) {
        node.forEach(traverse);
      } else if (node.type === 'text') {
        textContent += node.text;
      } else if (node.children) {
        node.children.forEach(traverse);
      }
    };
    traverse(nodes);
    return textContent.trim();
  },

  // 复制消息的函数
  copyMessage: function(e) {
    const index = e.currentTarget.dataset.index; // 获取消息的索引
    const markdownNodes = this.data.chatHistory[index].markdownContent;

    // 提取 markdown 内容中的纯文本
    const textContent = this.extractTextFromMarkdown(markdownNodes);

    wx.setClipboardData({
      data: textContent,
      success() {
        wx.showToast({
          title: '复制成功',
          icon: 'none',
          duration: 2000
        });
      },
      fail() {
        wx.showToast({
          title: '复制失败',
          icon: 'none',
          duration: 2000
        });
      }
    });
  },

  // 点击更新消息
  updateMessage: function(e) {
    const index = e.currentTarget.dataset.index; // 获取消息索引
    const userMessage = this.data.chatHistory[index - 1]?.text; // 上一条是用户消息,获取用户消息的内容

    if (userMessage) {
      // 清空当前 AI 回答并显示加载状态
      this.setData({
        isLoading: true,
        currentChunk: '', // 清空当前块数据
      });

      // 再次发送上次的问题
      this.requestAiResponse(userMessage);
    } else {
      wx.showToast({
        title: '没有找到用户问题',
        icon: 'none',
        duration: 2000
      });
    }
  }
});

wxml代码

<view class="container">
  <view class="chat-container">
    <view class="chat-history">
      <block wx:for="{{chatHistory}}" wx:key="index">
        <view wx:if="{{item.sender === 'user'}}" class="chat-message user-message">
          <view class="chat-message-container">
            <view class="chat-message-item">
              {{item.text}}
            </view>
          </view>
        </view>
        <view wx:if="{{item.sender === 'ai'}}" class="chat-message ai-message">
          <view class="chat-message-container">
            <view class="chat-message-item">
              <towxml nodes="{{item.markdownContent}}" />
            </view>
            <view class="chat-input-actions">
            <view class="chat-message-action copy" bindtap="copyMessage" data-index="{{index}}">
              <van-icon class="copy" class-prefix="icon" name="copy" />
            </view>
            <view class="chat-message-action update" bindtap="updateMessage" data-index="{{index}}">
              <van-icon class="update" class-prefix="icon" name="update" />
            </view>
          </view>
          </view>
        </view>
      </block>
    </view>

    <!-- 输入区域 -->
    <view class="chat-input-panel">
    <view class="chat-input">
      <input type="text" 	confirm-type="send" bindinput="onInput" placeholder="输入你的问题..." value="{{inputText}}" />
      <button bindtap="sendMessage"><van-icon class="copy" class-prefix="icon" name="send" /></button>
    </view>
  </view>
  </view>
</view>

wxss代码

page {
  background-color: #fff;
  height: 100%;
}

.h2w {
  border-radius: 10rpx;
}

.h2w .h2w__main {
  padding: 0rpx;
}

.h2w .h2w__p {
  margin: 0;
}

.container {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.chat-container {
  display: flex;
  flex-direction: column;
  position: relative;
  height: 100%;
}

.h2w.h2w-light {
  background-color: #fff;
}

.chat-history {
  background-color: #eff2f5;
  flex: 1 1;
  overflow: auto;
  overflow-x: hidden;
  padding: 30rpx;
  position: relative;
  overscroll-behavior: none;
}

.chat-message {
  margin-bottom: 30rpx;
}

.user-message {
  max-width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

.chat-message-item {
  background-color: #465cff;
  color: #fff;
  padding: 20rpx;
  max-width: 100%;
  border-radius: 10rpx;
  word-break: break-word;
}

.ai-message .chat-message-item {
  background-color: #fff;
  border: 4rpx solid #c6ccfc;
  border-radius: 20rpx;
}

.ai-message {
  display: flex;
  flex-direction: row;
}
.chat-input-actions{
  display: flex;
  gap: 16rpx;
}
.chat-message-action {
  margin-top: 20rpx;
  background-color: #fff;
  width: 50rpx;
  height: 50rpx;
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  display: flex;
  box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, .05);
}

.chat-input-panel {
  position: relative;
  width: 100%;
  padding: 10px 20px 20px;
  box-sizing: border-box;
  padding: 10px;
  border-top: 1px solid #dedede;
  background-color: #fff;
}

.chat-input {
  display: flex;
}

.chat-input input {
  background-color: #fff;
  flex-grow: 1;
  padding: 20rpx;
  border: 1px solid #dedede;
  border-radius: 10rpx 0 0 10rpx;
  margin-right: -1rpx;
}

.chat-input button {
  padding: 0rpx 30rpx;
  background-color: #465cff;
  color: #fff;
  border-radius: 0 10rpx 10rpx 0;
  font-size: 22rpx;
}

<think>好的,我现在需要帮用户做一个微信小程序项目,要求实现AI对话界面,接入深度求索(DeepSeek)的官方API,功能官网一样,外观大气简约。首先,我需要理清楚整个项目的步骤需要注意的地方。 首先,用户的需求是做一个类似DeepSeek官网的AI对话功能的小程序。我需要考虑前端界面设计、后端API对接、以及微信小程序的整体架构。可能需要分步骤来实现,比如先注册小程序,然后设计界面,再处理API调用,最后测试发布。 第一步,注册微信小程序账号。用户需要有一个已注册的账号,并且完成开发者资质认证,因为涉及到网络请求,必须使用HTTPS,所以需要配置合法域名。这里可能需要提醒用户去微信公众平台注册并认证。 接下来是前端界面设计。用户希望外观大气简约,参考DeepSeek官网。可能需要设计一个聊天界面,包含消息列表、输入框发送按钮。消息列表需要区分用户AI的消息,用不同的样式显示。输入框可能需要支持多行输入,发送按钮可以设计成图标或者文字。颜色搭配方面,可能选择深蓝色或科技感强的颜色,保持简洁。 然后是API对接。需要接入DeepSeek的官方API,用户需要提供API密钥。这里需要查阅DeepSeek的API文档,了解请求格式、参数返回数据结构。在微信小程序中,使用wx.request方法发送POST请求到API端点,携带必要的headers,比如AuthorizationContent-Type。需要注意异步处理,可能使用Promise或async/await来优化代码结构。 在处理API响应时,可能需要支持流式传输,如果DeepSeek的API支持类似ChatGPT流式返回,这样可以实现逐字显示的效果。不过微信小程序的WebSocket或者持续请求可能需要额外处理,或者使用分块接收数据。如果没有流式支持,可能需要一次性获取全部响应再显示。 同时,要考虑网络请求的安全性错误处理。比如,处理API调用失败的情况,显示错误提示,并可能重试。用户的输入也需要做合法性校验,防止空消息或过长文本。 后端部分,如果DeepSeek的API可以直接从小程序前端调用,可能不需要额外搭建后端服务器。但需要注意小程序的AppSecretAPI密钥的安全,避免暴露在前端代码中。如果DeepSeek要求API密钥不能放在前端,可能需要搭建一个中转服务器,小程序前端请求自己的服务器,再由服务器转发请求到DeepSeek。这涉及到服务器搭建,比如使用Node.js、Python等,但用户可能希望简化步骤,所以需要确认DeepSeek的API是否允许前端直接调用。 另外,实现历史对话记录的功能,可能需要使用小程序的本地存储,比如wx.setStorageSync来保存聊天记录。但本地存储容量有限,如果用户需要云存储,可能需要接入数据库,比如微信云开发或第三方数据库服务,这会增加复杂度。 性能优化方面,需要注意消息列表的渲染效率,使用好的数据结构渲染方法,避免卡顿。特别是当消息较多时,需要确保滚动流畅。 测试环节,需要使用微信开发者工具进行模拟器测试,真机调试,检查不同设备的兼容性。特别是API请求在不同网络环境下的表现,比如慢速网络下的超时处理。 最后,发布小程序需要提交审核,确保内容符合微信的规定,比如不涉及敏感信息,AI对话内容合法等。可能需要内容安全审核接口,比如调用微信的imgSecCheckmsgSecCheck,防止用户输入或AI生成违规内容。 可能遇到的难点包括API密钥的安全处理、流式响应的实现、历史数据的管理以及审核合规性。需要逐一解决这些问题,同时保持代码结构清晰,便于维护迭代。 总结下来,步骤大概是:注册小程序、设计UI、接入API、处理数据存储、实现交互功能、测试优化、发布上线。每个步骤都需要详细考虑可能出现的问题,并找到合适的解决方案。同时,保持与用户的沟通,确保需求被正确理解实现。</think>以下是开发微信小程序实现AI对话功能的分步指南,包含DeepSeek API对接界面设计要点: --- ### 一、前期准备 1. **注册小程序** - 前往[微信公众平台](https://mp.weixin.qq.com/)注册账号 - 完成企业资质认证(需300元认证费,个人主体无法使用网络API) 2. **获取DeepSeek API** - 登录[DeepSeek官网](https://www.deepseek.com/)申请开发者权限 - 获取API_KEY接口文档(请求示例): ```javascript // 典型请求结构 { "model": "deepseek-chat", "messages": [ {"role": "user", "content": "你好"} ] } ``` --- ### 二、界面设计(WXML+WXSS) ```html <!-- pages/chat/chat.wxml --> <view class="container"> <!-- 消息区域 --> <scroll-view scroll-y class="message-list"> <block wx:for="{{messages}}" wx:key="index"> <view class="message {{item.role}}"> <image wx:if="{{item.role === 'assistant'}}" src="/images/ai-avatar.png" /> <text class="content">{{item.content}}</text> </view> </block> </scroll-view> <!-- 输入区域 --> <view class="input-area"> <textarea auto-height bindinput="onInput" value="{{inputText}}" placeholder="输入你的问题..." /> <button class="send-btn" bindtap="sendMessage"> <image src="/icons/send.png" /> </button> </view> </view> ``` **样式建议**: - 主色调:`#1A237E`(深蓝科技感) - AI消息气泡:浅灰色背景 + 左侧AI头像 - 用户消息:蓝色右对齐气泡 - 使用`rpx`单位保证多设备适配 --- ### 三、核心逻辑实现 ```javascript // pages/chat/chat.js Page({ data: { messages: [], inputText: '', apiKey: 'sk-your-api-key' // 正式环境需加密存储 }, sendMessage() { const newMsg = {role: 'user', content: this.data.inputText}; this.setData({ messages: [...this.data.messages, newMsg], inputText: '' }); wx.request({ url: 'https://api.deepseek.com/v1/chat/completions', method: 'POST', header: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.data.apiKey}` }, data: { model: "deepseek-chat", messages: this.data.messages }, success: (res) => { const aiResponse = res.data.choices[0].message; this.setData({ messages: [...this.data.messages, aiResponse] }); } }); } }) ``` --- ### 四、关键优化点 1. **流式输出实现** ```javascript // 使用WebSocket实现逐字显示 const socket = wx.connectSocket({ url: 'wss://api.deepseek.com/v1/chat/stream' }); socket.onMessage(res => { const partialResponse = JSON.parse(res.data); this.setData({ messages: this.data.messages.map((msg, index) => index === this.data.messages.length -1 ? {...msg, content: msg.content + partialResponse.choices[0].delta.content} : msg ) }); }); ``` 2. **安全措施** - 使用微信云函数转发API请求,避免暴露API_KEY - 启用HTTPS并配置合法域名 - 接入内容安全API(需额外开发) --- ### 五、部署流程 1. 配置服务器域名:登录小程序后台添加`api.deepseek.com`到request合法域名 2. 本地调试:使用微信开发者工具模拟对话 3. 提交审核:准备AI类目资质材料(通常需要《增值电信业务经营许可证》) --- ### 六、扩展功能建议 - 对话历史管理(使用云开发数据库) - Markdown格式渲染(需引入第三方解析库) - 预设prompt模板 - 打字机动画效果 开发周期预估:基础功能约2周,含安全加固优化需3-4周。建议使用`WeUI`组件库加速开发。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值