【小程序】聊天功能

发布于:2024-06-25 ⋅ 阅读:(51) ⋅ 点赞:(0)

聊天功能

实现功能

要实现一个聊天机器人,它能够解答用户疑问,并且能够识别到用户聊天的主题,涉及到饮食方面时,会自动决定是否要去数据库中读取用户的相关喜好信息,以实现自动化。同时,还要支持重新生成、复制回答、同时生成两条文本让用户选择以优化模型回复。同时还要对每次的会话进行管理。

实现思路

主要思路就是要将用户输入传给模型,模型予以解答。

主要实现的功能和解决的问题:

  1. 需要维护一个history来存储聊天记录。
  2. 实现打字机效果。
  3. 消息正在生成时,发送按钮变成灰色图片并禁止点击。
  4. 解决输入法弹起后输入框自动调整高度以避免被遮挡问题。
  5. 解决输入法弹起后,顶部导航栏不会上移。
  6. 实现文本的复制功能。
  7. 实现重新生成功能,如果不是最后一条消息,则要删掉该会话中要重新生成的消息后面的所有消息。
  8. 实现同时生成两种回答,让用户选择更喜欢哪种回答。然后选择后,后续回答风格会按照用户选择的那条来回答。
  9. 解决如果用户没有选择哪种回答,就进行发送下一条消息、切换页面、切换会话等操作,则会自动选择详细版的哪种风格。
  10. 实现会话的增加,要求当前会话没有聊天记录时,不会创建会话,只有发送了至少一条聊天记录,才会新增到数据库。同时,当前会话没有聊天记录时,前端会话新增按钮要禁用,即不能再次创建一个新的会话窗口。
  11. 实现会话的删除,使用滑动框,左滑删除。弹出提示框,询问是否删除。并且要解决uview滑动框组件的bug,即删除一个后,该删除状态不会自动消失,还是打开的状态。
  12. 会话列表要实现按照今天、昨天和更早以前进行分组。

后端

由于后端代码很多,只截取片段展示。

def chat_response(query, user_info, session_id=None, history=None, is_chat=None, response_type=None):
    if user_info is None:
        return "无法找到用户信息,请检查用户ID是否正确。"
    
    if session_id is None and is_chat == "1":
        session_id = create_session(user_info['user_id'])
        
    if is_chat == "1":
        is_food_topic = is_food_related_topics(query)
        print("用户输入是否是食物相关主题:" + is_food_topic)
    
        if is_food_topic == "true":
            emotions = get_user_emotions(user_info['user_id'])
            emotions_str = ", ".join([f"{e['emotion']}{e['food']}" for e in emotions])
            user_info_str = f"疾病: {user_info['diseases']}, 喜好的食物: {user_info['likes']}, 忌口的食物: {user_info['dislikes']}, 其他喜恶: {emotions_str}"

            personalized_prompt = f"{query}\n用户信息: {user_info_str}"

            print("\n============= info_prompt ===============\n")
            print(personalized_prompt)

            # 提取用户对食物的情感,并存到数据库中
            extract_emotions_about_food(query, user_info)
           
        else:
            personalized_prompt = f"{query}"
            
            print("\n============= no_info_prompt ===============\n")
            print(personalized_prompt)
        
        store_message(session_id, 'user', query)

        # 检查用户提问的数量
        messages = get_session_messages(session_id)
        user_messages = [msg for msg in messages if msg['role'] == 'user']
        if len(user_messages) == 3:
            # 获取前三条用户提问生成标题
            summary_prompt = "\n".join([msg['content'] for msg in user_messages])
            title, _ = p_model.chat(tokenizer, f"请总结以下对话主题,不超过10个字,只输出一行,不要有换行:\n{summary_prompt}", top_p=1, temperature=0.01)
            title = title.strip()
            update_session_title(session_id, title)

        print("history\n")
        print(history)
    
        if response_type == "detailed":
            prompt = f"详细(100字以上)回答:\n{personalized_prompt}"
        elif response_type == "concise":
            prompt = f"简洁(50字以内)回答:\n{personalized_prompt}"
        else:
            prompt = personalized_prompt
        
    else:
        prompt = f"{query}"

        print("\n============= not_chat_prompt ===============\n")
        print(prompt)

    
    response, _ = p_model.chat(tokenizer, prompt, history=history, top_p=1, temperature=0.01)
    
    if is_chat == "1":
        message_id = store_message(session_id, 'system', response)  # 存储消息并返回消息ID
        return response, session_id, message_id

    return response

前端

由于前端代码太多,只截取部分代码展示。

async sendMessage() {
        if (this.userInput.trim() !== '' && !this.isTyping) {
          // 检查是否需要自动选择详细版
          if (this.showChoice) {
            await this.autoChooseDetailed(); // 等待自动选择详细版完成
          }
    
          this.messageList.push({
            role: 'user',
            content: this.userInput,
            avatar: 'https://xxx/avatar.png'
          });
          const query = this.userInput;
          this.userInput = '';
    
          // 添加ChatGPT的占位消息
          const loadingMessage = {
            role: 'chatgpt',
            content: '',
            avatar: 'https://xxx/chatai-avatar.png',
            isLoading: true
          };
          this.messageList.push(loadingMessage);
          this.handleScrollTop(); // 确保视图滚动到底部
    
          let that = this;
          this.isTyping = true;
          uni.request({
            url: 'https://xxx/chat',
            method: 'POST',
            data: { query, history: this.history, user_id: this.userId, session_id: this.currentSessionId },
            header: {
              'Content-Type': 'application/json',
            },
            success: function (resp) {
              that.currentSessionId = resp.data.session_id;
              const index = that.messageList.indexOf(loadingMessage);
              if (index !== -1) {
                that.messageList.splice(index, 1); // 删除占位消息
    
                if (resp.data.detailed_response && resp.data.concise_response) {
                  // 第四次对话,展示两个回答框
                  that.conciseResponse = resp.data.concise_response;
                  that.detailedResponse = resp.data.detailed_response;
                  that.showChoice = true; // 设置 showChoice 为 true
                  console.log("showChoice")
                  console.log(that.showChoice)
                  // 插入提示语
                  that.messageList.push({
                    role: 'system',
                    content: '请选择您更喜欢哪种回答?',
                    avatar: '',
                    isTypingComplete: true,
                    responseType: 'prompt'
                  });
                  // 分别为精简版和详细版添加打字机效果
                  that.addTypingEffect('ChatGLM3-6B', resp.data.concise_response, resp.data.concise_message_id, 'concise', true);
                  that.addTypingEffect('ChatGLM3-6B', resp.data.detailed_response, resp.data.detailed_message_id, 'detailed', false);
                } else {
                  that.addTypingEffect('ChatGLM3-6B', resp.data.response, resp.data.message_id);
                }
              }
              that.history.push({ role: 'user', content: query });
              if (that.history.filter(msg => msg.role === 'user').length === 1 || that.history.filter(msg => msg.role === 'user').length === 3) {
                that.refreshSessionList();
              }
            },
            fail: function (error) {
              console.error('Error sending message:', error);
            },
            complete: function() {
              that.isTyping = false;
            }
          });
        }
      },
      addTypingEffect(user, text, messageId, responseType = null, showAvatar = true) {
          if (!text) {
            console.error('Error: text is undefined or empty');
            return;
          }
          
          let index = 0;
          const message = { 
            role: 'chatgpt',
            content: '',
            avatar: showAvatar ? 'https://xxx/chatai-avatar.png' : 'https://xxx/0b46f21fbe096b63d30f4b590d338744ebf8aca0.png',
            id: messageId,
            isLoading: false,
            isTypingComplete: false, // 新变量,初始化为 false
            responseType: responseType // 添加 responseType
          };
          this.messageList.push(message);
      
          const typing = setInterval(() => {
            if (index < text.length) {
              message.content += text[index];
              index++;
              this.handleScrollTop().catch((error) => {
                console.warn('Error while scrolling:', error);
              });
            } else {
              clearInterval(typing);
              this.history.push({ role: 'assistant', content: text });
              message.isLoading = false; // 设置为不再加载
              this.$set(message, 'isTypingComplete', true); // 打字机效果完成后设置为 true
			  this.handleScrollTop().catch((error) => {
			            console.warn('Error while scrolling:', error);
			          }); // 确保滚动到最底部
            }
          }, this.typingInterval);
        },

效果展示


网站公告

今日签到

点亮在社区的每一天
去签到