当前位置: 首页 > news >正文

vue3之写一个aichat---已聊天组件部分功能

渲染聊天数据

这个不必多说,直接从stores/chat中取出聊天列表数据渲染就好,因为前面添加的消息都是按照用户消息、AI助手消息这样添加的,效果如图
在这里插入图片描述

但是需要注意每条助手消息的状态,需要根据状态显示不同的图标或不显示图标,比如,正常完成的回答,不显示图标,如果是调用流式回调的接口出错的或者中途停止的,且是chatRecordList中的最后一条消息,那么就要显示重新生成的图标,点击重新生成会重新发送消息
重新发送的逻辑处理,主要是找到当前的AI助手消息将其状态变为stop停止状态,然后找到当前AI助手消息之前的最后一条用户消息获取其content,去调用stores/chat中的startChat方法从而走到聊天处理逻辑的文件中去

const handleRefresh = async messageId => {
  try {
    const message = props.messages.find(msg => msg.id === messageId)
    if (message && message.role === 'assistant') {
      // 设置为等待状态
      message.status = 'stop'

      // 找到当前助手消息之前的最后一条用户消息
      const messageIndex = props.messages.findIndex(msg => msg.id === messageId)
      let lastUserMessage = null

      for (let i = messageIndex; i >= 0; i--) {
        if (props.messages[i].role === 'user') {
          lastUserMessage = props.messages[i]
          break
        }
      }

      if (lastUserMessage) {
        try {
          // 直接使用用户消息重新请求
          await chatStore.startChat(lastUserMessage.content)
        } catch (error) {
          // 如果失败,恢复原状态
          message.status = 'error'
          console.error('重新生成失败:', error)
        }
      }
    }
  } catch (error) {
    console.error('处理重新生成失败:', error)
  }
}

生成AI助手消息时,滚动条自动滚动向下

在聊天时要自动滚动到最新消息的位置,使用户能看到最新的消息
因为已聊天组件和未聊天组件的切换写在Chat.vue页面中,所以scrollToBottom定义到Chat.vue中才能正常的实现这个功能
在这里插入图片描述
通过scrollTo方法将top设为container.scrollHeight即元素内容的总高度,包括由于滚动而看不见的内容,浏览器会将滚动条的位置设置为元素内容的底部,由于scrollHeight是元素内容的总高度,因此滚动条会被移动到内容的最底部,从而实现滚动到底部的效果

// 滚动到底部
const scrollToBottom = () => {
  setTimeout(() => {
    const container = document.querySelector('.chat-container')
    if (container) {
      container.scrollTo({
        top: container.scrollHeight,
        behavior: 'smooth'
      })
    }
  }, 100)
}

当chatRecordList变化的时候,自动触发滚动到底部的函数,isClickThought是因为在显示正式的回答前会显示思考的过程,思考完成后,开始回答问题时,会将思考的内容收起,通过点击按钮控制思考内容的显示与隐藏,但是当点击思考内容变化的时候,我们是不希望滚动条滚动的,所以就通过isClickThought来控制

watch(
  () => chatRecordList.value,
  async (newList, oldList) => {
    await nextTick()
    if (!isClickThought.value) {
      scrollToBottom()
      // 短暂延迟后检查滚动位置
      setTimeout(() => {
        isScrolledToBottom()
      }, 100)
    }
    isClickThought.value = false
  },
  { deep: true }
)

当消息特别多的时候,有时候可能需要查看之前的聊天内容,查看完成后可能需要继续提问,但是由于内容特别多,手动往下翻肯定费劲,所以需要一个“置底”按钮,这个按钮只有在没有消息生成的时候才会出现,不然滚动条变化的时候页面显示不友好,点击的时候直接调用scrollToBottom函数就行,isScrolledToBottom判断是否显示该按钮

const isScrolledToBottom = () => {
  const container = chatContainer.value
  if (!container) return true

  // 计算是否接近底部(允许20px的误差)
  const isAtBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 20
  isShowToBottom.value = !isAtBottom && !isResponding.value
}

停止当前对话和开启新对话功能

通过计算属性isResponding判断是显示“停止”还是显示“开启新对话”,当进入已聊天界面且没有正在回答的消息的时候,显示“开启新对话”按钮,否则显示停止
在这里插入图片描述

const isResponding = computed(() => {
  const lastMessage = chatRecordList.value[chatRecordList.value.length - 1]
  return lastMessage?.role === 'assistant' && ['thinking', 'typing'].includes(lastMessage.status)
})

点击停止按钮
触发chatStore中的停止响应函数

const handleStopResponse = () => {
  chatStore.stopResponse()
}

stores/chat.js,其中isResponseStopped是在chat.service.js判断是否需要停止响应用到的

  const isResponseStopped = ref(false) //是否停止响应
   function stopResponse() {
      isResponseStopped.value = true
      // 确保调用 abortRequest
      abortRequest()
      const lastMessage = chatRecordList.value[chatRecordList.value.length - 1]//获取到最后一条聊天记录
      if (lastMessage && lastMessage.role === 'assistant') {
        if (lastMessage.status === 'thinking') {//如果当前AI助手消息的状态是思考中则将状态变为stop停止状态
          lastMessage.status = 'stop'
        } else if (lastMessage.status === 'typing') {//如果当前AI助手消息的状态是已经构建回答但尚未完成,则将状态设为已完成
          lastMessage.status = 'done'
        }
      }
    }

如果当前停止的消息是最后一条AI助手消息那么就又会有handleRefresh
点击开启新会话
开启新会话调用chatStore.newChat()方法

const handleNewChat = () => {
  chatStore.newChat()
}

stores/chat.js
开启新对话要把之前的对话列表、是否已聊天等状态重置

  // 清除聊天状态
    const clearChatState = () => {
      hasChatted.value = false
      chatRecordList.value = []
      assistantMessageId.value = null
      isResponseStopped.value = false
      sessionId.value = ''
      isHistory.value = false
      historyTime.value = ''
      recommendedQuestions.value = []
      isFromRecommend.value = false
    }
    const newChat = () => {
      clearChatState()
      setSessionId(chatService.getRandomSessionId())
    }
   cosnt setSessionId = (id) => {
      sessionId.value = id
    }

每个回答完成会根据当前的问题返回对应的三条推荐,当点击推荐的时候会在当前页面发送一个新的问题

// 点击推荐问题,发送问题
const handleRecommend = item => {
  chatStore.setRecommendedQuestions([])
  chatStore.startChat(item) // 添加 true 标识这是推荐问题
}

记住推荐问题一般都是跟在最后一条消息后面的,所以每次调用会话接口的时候要记得将setRecommendedQuestions列表置空

相关文章:

  • 快速部署Linux + Ollama + AnythingLLM + Deepseek
  • CEF 多进程模式时,注入函数,获得交互信息
  • 关于 2>/dev/null 的作用以及机理
  • 半导体制造行业的现状 内检LIMS系统在半导体制造的应用
  • EJS缓存解决多页面相同闪动问题
  • MySQL中的锁机制:从全局锁到行级锁
  • 「JavaScript深入」Socket.IO:基于 WebSocket 的实时通信库
  • keepalived+nginx+tomcat高可用
  • C# 中比较实用的关键字,基础高频面试题!
  • Linux系统中安装各种常用中间件
  • android adjust 卸载与重装监测
  • 投影算子(Projection Operator)的定义、性质、分类以及应用
  • CentOS 7 64位安装Docker
  • python关键字汇总
  • 面试总结之 Glide自定义的三级缓存策略
  • 回调方法传值汇总
  • websocket中spring注入失效
  • 【多线程】线程安全集合类,ConcurrentHashMap实现原理
  • 本地部署DeepSeek-R1(每天8:00Dify通过企微机器人推送新闻热点到群里)
  • C语言:结构化程序设计的核心思想笔记
  • 上海北外滩,未来五年将如何“长个子”“壮筋骨”?
  • Manus向全球用户开放注册
  • 云南大理铁路枢纽工程建设取得两大进展,预计明年建成
  • 打击网络谣言、共建清朗家园,中国互联网联合辟谣平台2025年4月辟谣榜
  • 淡马锡辟谣:淡马锡和太白投资未在中国销售任何投资产品或金融工具
  • 香港将展“天方奇毯”,从地毯珍品看伊斯兰艺术