核工业西南物理研究院知识库AI客户端
xiangpei
2025-04-16 689aced342a8c6b41c1f7dcb7594e9fce387112b
src/components/AiChat.vue
@@ -1,5 +1,5 @@
<template>
  <div style="position: relative;height:calc(100vh - 20px);width: 100%;display: flex;justify-content: center">
  <div style="height:calc(100vh - 20px);width: 100%;display: flex;justify-content: center">
    <!-- 聊天消息列表 -->
    <div class="chat-messages">
      <div
@@ -11,8 +11,11 @@
        <div class="avatar">
          <img :src="getAvatar(message.role)" alt="avatar" />
        </div>
        <div class="content">
          <div class="text" :ref="'msg' + index">{{ message.content }}</div>
        <div v-if="!message.content && message.role == 'assistant'" v-loading="!message.content && message.role == 'assistant'">
        </div>
        <div v-else class="content">
          <div class="text" :ref="'msg' + index" v-html="getHtml(message.content)"></div>
          <div v-if="message.role === 'assistant'">
            <div v-show="msgIndex === message" class="msg-op-list">
              <el-button class="copy" v-show="false"/>
@@ -37,7 +40,7 @@
    <!-- 输入框 -->
    <div class="chat-input">
      <div style="position: relative;width: 100%;box-sizing: border-box;">
      <div style="position: relative;width: 800px;box-sizing: border-box;">
        <el-input
            v-model="inputMessage"
            type="textarea"
@@ -74,45 +77,29 @@
<script>
import ClipboardJS from 'clipboard';
import {sendKbMsg} from "@/api/chat";
// import {sendKbMsg} from "@/api/chat";
import {Message} from "element-ui";
const markdownIt = require('markdown-it')();
export default {
  name: 'AiChat',
  data() {
    return {
      messages: [], // 用于页面展示的对话列表
      netSearchEnable: false,
      messages: [
        {
          role: 'assistant',
          content: '你好!我是你的 AI 助手,有什么可以帮你的吗?',
        },
        {
          role: 'user',
          content: '你好!我是你的 AI 助手,有什么可以帮你的吗?',
        },
      ],
      inputMessage: '',
      sendMsgForm: {
        query: "",
        mode: "local_kb",
        kb_name: "samples",
        kb_name: "SouthWest_Neclear_Develepment_KB",
        top_k: 3,
        score_threshold: 2,
        history: [
          {
            "content": "我们来玩成语接龙,我先来,生龙活虎",
            "role": "user"
          },
          {
            "content": "虎头虎脑",
            "role": "assistant"
          }
        ],
        stream: true,
        model: "qwen:7b",
        model: "Qwen25-32B-Instruct",
        temperature: 0.7,
        max_tokens: 0,
        max_tokens: 64,
        prompt_name: "default",
        return_direct: false
      },
@@ -121,6 +108,10 @@
    };
  },
  methods: {
    // md渲染为html
    getHtml(md) {
      return markdownIt.render(md)
    },
    changeNetEnable() {
      this.netSearchEnable = ! this.netSearchEnable
    },
@@ -129,11 +120,9 @@
      e.preventDefault(); // 阻止默认换行行为
    },
    handleMouseEnter(msgIndex) {
      console.log("鼠标移入", msgIndex)
      this.msgIndex = msgIndex
    },
    handleMouseLeave() {
      console.log("鼠标移除")
      this.msgIndex = null
    },
    // 重新生成
@@ -176,18 +165,69 @@
        role: 'user',
        content: this.inputMessage,
      });
      this.messages.push({
        role: 'assistant',
        content: ''
      })
      this.sendMsgForm.query = this.inputMessage
      // 清空输入框
      this.inputMessage = '';
      sendKbMsg(this.sendMsgForm).then(res => {
        console.log(res, "拿到回复了!!")
        this.messages.push({
          role: 'assistant',
          content: '这是一条 AI 回复。',
        });
      })
      const response = await fetch('/api/chat/kb_chat', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(this.sendMsgForm)
      });
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      // eslint-disable-next-line no-constant-condition
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        console.log("结束循环")
        const chunk = decoder.decode(value, { stream: false });
        const dataList = chunk.split('\r\n\r\n')
        console.log("获取到了流式响应数据", dataList)
        if (dataList && dataList.length > 0) {
          for (const data of dataList) {
            if (data.startsWith("data: ")) {
              const jsonStr = data.slice(6).trim();
              if (jsonStr) {
                const json = JSON.parse(jsonStr);
                if (json.docs && json.docs.length > 0) {
                  console.log("1", json)
                  json.docs.forEach(str => {
                    this.messages[this.messages.length - 1].content += str
                    // 强制Vue更新视图
                    this.$forceUpdate();
                  })
                } else if (json.choices && json.choices.length > 0) {
                  console.log("2", json)
                  json.choices.forEach(choice => {
                    this.messages[this.messages.length - 1].content += choice.delta.content
                    // 强制Vue更新视图
                    this.$forceUpdate();
                  })
                }
              }
            }
          }
        }
      }
      // sendKbMsg(this.sendMsgForm).then(res => {
      //   console.log(res, "拿到回复了!!")
      //   this.sendMsgForm.history.push({
      //     role: 'assistant',
      //     content: JSON.parse(res.data).choices[0].message.content,
      //   });
      // })
    },
  },
};
</script>
@@ -229,9 +269,7 @@
.chat-messages {
  padding: 16px;
  margin-top: 14px;
  overflow-y: auto;
  width: 800px;
  height: 680px;
}
.message {
@@ -280,9 +318,8 @@
}
.chat-input {
  padding: 16px;
  background-color: #fff;
  width: 800px;
  width: 100%;
  display: flex;
  flex-direction: row;
  justify-content: center;