【vue】仿对话(包含下拉刷新、滚动条到底部、流式数据)

第一部分:下拉刷新

我这里就把主要的下拉刷新的写一下,上拉是一样的道理,就不写了

       <div class="talk_top" ref="listWrapper" id="listWrapper">
            <div class="loadingpic" v-loading="loading"></div>
            <div v-for="message in messages" :key="message.id" class="message">
                <div class="minetext" v-html="message.text"></div>
            </div>
          </div>
<script>
export default {
     data() {
        return {
              loading: false, // 加载中
              messages:[],
        }
     },
     mounted() {
       this.$nextTick(() => {
           this.getAiLogsByTopicId(); // 初始化数据
           this.re();
        });
      },
      methods: {
//这是放在mounted中获取初始数据
   getAiLogsByTopicId() {
      this.$axios
        .get("/userApp/ai/getAiLogsByTopicId", {
          params: {
            TopicId: this.topic.topicId,
            pageNum: 1,
            pageSize: this.page.pageSize
          }
        })
        .then(res => {
          if (res.code === 200) {
            this.messages = res.rows.reverse();
            // console.log(res.rows);
            this.total = Number(res.total);
          }
        });
    },
   //放在re()里面的数据接口
   getlist() {
     if (this.total >= this.messages.length) {
        this.page.pageNum++;
        this.loading = true;
        this.$axios
          .get("/userApp/ai/getAiLogsByTopicId", {
            params: {
              TopicId: this.topic.topicId,
              pageNum: this.page.pageNum,
              pageSize: this.page.pageSize
            }
          })
          .then(res => {
            if (res.code === 200) {
              if (this.page.pageNum >= Number(res.allPage)) {
                this.$message.success("数据到底啦");
                this.loading = false;
                return;
              } else {
                this.messages = [...res.rows.reverse(), ...this.messages];
                this.total = Number(res.total);
                // console.log(this.messages);
                this.loading = false;
              }
            }
          })
          .catch(error => {
            this.$message.error(error.msg);
            this.loading = false;
          });
      } else {
        this.$message.success("数据到底啦");
        // this.showdown = true;
        this.loading = false;
      }
    },
           // 下拉、上拉刷新
    re() {
      var flag = false;
      var PstartX;
      var PstartY;
      var PMoveX;
      var PMoveY;
      var PendX;
      var PendY;
      let that = this;
      document.onmousedown = function(ev) {
        flag = true;
        PstartX = ev.pageX;
        PstartY = ev.pageY;
        // console.log("start:" + PstartX, PstartY);
        document.onmousemove = function(ev) {
          PMoveX = ev.pageX;
          PMoveY = ev.pageY;
          if (flag) {
            // console.log("move:" + PMoveX, PMoveY);
            var resutl = getpostion(PMoveY, PstartY);
            switch (resutl) {
              case 0:
                // console.log("无操作");
                break;
              case 1:
                // console.log("向上");
                break;
              case 2:
                // console.log("向下");
                if (PMoveY - PstartY > 0) {
                  if (PMoveY - PstartY >= 50) {
                    document.getElementById("listWrapper").style.marginTop =
                      PMoveY - PstartY + "px";
                  }
                  that.loading = true;
                  // document.getElementById("loadingpic").style.display = "block";
                }
                break;
            }
          }
        };
        document.onmouseup = function(ev) {
          flag = false;
          PendX = ev.pageX;
          PendY = ev.pageY;
          // console.log("end:" + PendX, PendY);
          var resutl = getpostion(PMoveY, PstartY);
          switch (resutl) {
            case 0:
              // console.log("无操作");
              break;
            case 1:
              // console.log("向上");
              break;
            case 2:
              // console.log("向下");
              // location.reload();
              setTimeout(() => {
                that.getlist(); //调用接口
                //回弹到初始位置
                document.getElementById("listWrapper").style.marginTop = "0px";
              }, 500);
              break;
          }
        };
        // 判断是上拉还是下拉
        function getpostion(PMoveY, PstartY) {
          if (PMoveY - PstartY == 0) {
            return 0; //无操作
          }
          if (PMoveY - PstartY < 0) {
            return 1; //向上
          }
          if (PMoveY - PstartY > 0) {
            return 2; //向下
          }
        }
      };
    },
      }
}
</script>

在他的基础上修改了一下,他的上面有点小问题https://www.cnblogs.com/zmcxsf/p/10443189.html

第二部分:滚动条到底部

方法一

1、调用接口数据,然后调用re()方法,如果不调用re方法,页面没反应

2、在 mounted中获取滚动区域的document,然后让调用方法,让盒子滚动到底部,这样页面一打开,滚动条就在底部了

3、监听滚动事件,判断用户滚动状态

<template>
  <div class="chat">
    <div class="center">
      <div v-if="pasId === 0">
        <div class="talk_history" id="chat">
          <div class="talk_top" ref="listWrapper" id="listWrapper">
            <div class="loadingpic" v-loading="loading"></div>
            <div v-for="message in messages" :key="message.id" class="message">
             。。。。内容
            </div>
          </div>
        </div>
        <div class="talk_huifu">
          <div style="border-bottom:1px solid #f8f8f8;display:flex">
            <el-upload
              ref="upload"
              class="upload-demo"
              action="https://jsonplaceholder.typicode.com/posts/"
              :show-file-list="false"
              :file-list="fileList"
              accept=".pdf"
              :on-change="talkchange"
              :http-request="http"
            >
              <img
                style="margin:0 15px 0 10px"
                src="../../assets/img/wenjianjia.png"
                title="发送文件"
              />
            </el-upload>
          </div>
          <div align="right">
            <el-input
              type="textarea"
              v-model="talk"
              :maxlength="4000"
              :rows="3"
              resize="none"
              placeholder="有什么我可以帮您?"
            ></el-input>
            <el-button size="medium" :disabled="disabled" @click="sub(0)"
              >发送</el-button
            >
          </div>
        </div>
      </div>
  </div>
</template>

<script>
let source = null;
var AIurl = "http://192.168.4.172:8081/userApp/ai/sse";
var resultUrl = "http://192.168.4.172:8081/userApp/ai/aiHelp";

import { EventSourcePolyfill } from "event-source-polyfill";
export default {
  layout: "AI2",
  name: "",
  data() {
    return {
      disabled: false, //ai回答结束,按钮取消禁用
      showdown: false, //数据到底啦
      chatContent: null,
      isScrolling: true,
      loading: false, // 加载中
      page: 1,
      total: 0, //总条数
      
      talk: "", //发送的聊天信息
      len: true, //发送信息的次数
      fileList: [], //发送文件
      topic: {}, //新建对话的信息
      word: {}, //文件的信息
      messages: [], //聊天历史
      page: {
        pageNum: 0,
        pageSize: 10
      },
      total: 0,

      pasId: 0, //上一页的id
    };
  },
  mounted() {
      this.getAiLogsByTopicId(); // 初始化数据
      this.re();
      setTimeout(() => {
        this.chatContent = document.getElementsByClassName("talk_top")[0];
        this.scrollToBottom();
        // 监听滚动事件,判断用户滚动状态
        this.chatContent.addEventListener("scroll", this.handleScroll);
      }, 1000);
  },
  watch: {
    disabled: {
      handler(newval, oldval) {
        this.disabled = newval;
      },
      deep: true,
      immediate: true
    }
  },
  methods: {
    // 定义将滚动条定位在底部的方法
    scrollToBottom() {
      var that = this;
      this.$nextTick(() => {
        if (that.isScrolling) {
          that.chatContent.scrollTop =
            that.chatContent.scrollHeight - that.chatContent.offsetHeight;
          // console.log(that.chatContent.scrollTop);
        }
      });
    },
    handleScroll() {
      const scrollContainer = this.chatContent;
      const scrollTop = scrollContainer.scrollTop;
      const scrollHeight = scrollContainer.scrollHeight;
      const offsetHeight = scrollContainer.offsetHeight;

      if (scrollTop + offsetHeight < scrollHeight) {
        // 用户开始滚动并在最底部之上,取消保持在最底部的效果
        this.isScrolling = true;
      } else {
        // 用户停止滚动并滚动到最底部,开启保持到最底部的效果
        this.isScrolling = false;
      }
    },
    
    // 发送对话---------------------------------------------------
    // 加载数据对话
    getAiLogsByTopicId() {
      this.$axios
        .get("/userApp/ai/getAiLogsByTopicId", {
          params: {
            TopicId: this.topic.topicId,
            pageNum: 1,
            pageSize: this.page.pageSize
          }
        })
        .then(res => {
          if (res.code === 200) {
            this.messages = res.rows.reverse();
            // console.log(res.rows);
            this.total = Number(res.total);
          }
        });
    },
    getlist() {
      if (this.total >= this.messages.length) {
        this.page.pageNum++;
        this.loading = true;
        this.$axios
          .get("/userApp/ai/getAiLogsByTopicId", {
            params: {
              TopicId: this.topic.topicId,
              pageNum: this.page.pageNum,
              pageSize: this.page.pageSize
            }
          })
          .then(res => {
            if (res.code === 200) {
              if (this.page.pageNum >= Number(res.allPage)) {
                this.$message.success("数据到底啦");
                this.loading = false;
                return;
              } else {
                this.messages = [...res.rows.reverse(), ...this.messages];
                this.total = Number(res.total);
                // console.log(this.messages);
                this.loading = false;
              }
            }
          })
          .catch(error => {
            this.$message.error(error.msg);
            this.loading = false;
          });
      } else {
        this.$message.success("数据到底啦");
        // this.showdown = true;
        this.loading = false;
      }
    },
    // 下拉、上拉刷新
    re() {
      var flag = false;
      var PstartX;
      var PstartY;
      var PMoveX;
      var PMoveY;
      var PendX;
      var PendY;
      let that = this;
      document.onmousedown = function(ev) {
        flag = true;
        PstartX = ev.pageX;
        PstartY = ev.pageY;
        // console.log("start:" + PstartX, PstartY);
        document.onmousemove = function(ev) {
          PMoveX = ev.pageX;
          PMoveY = ev.pageY;
          if (flag) {
            // console.log("move:" + PMoveX, PMoveY);
            var resutl = getpostion(PMoveY, PstartY);
            switch (resutl) {
              case 0:
                // console.log("无操作");
                break;
              case 1:
                // console.log("向上");
                break;
              case 2:
                // console.log("向下");
                if (PMoveY - PstartY > 0) {
                  if (PMoveY - PstartY >= 50) {
                    document.getElementById("listWrapper").style.marginTop =
                      PMoveY - PstartY + "px";
                  }
                  that.loading = true;
                  // document.getElementById("loadingpic").style.display = "block";
                }
                break;
            }
          }
        };
        document.onmouseup = function(ev) {
          flag = false;
          PendX = ev.pageX;
          PendY = ev.pageY;
          // console.log("end:" + PendX, PendY);
          var resutl = getpostion(PMoveY, PstartY);
          switch (resutl) {
            case 0:
              // console.log("无操作");
              break;
            case 1:
              // console.log("向上");
              break;
            case 2:
              // console.log("向下");
              // location.reload();
              setTimeout(() => {
                that.getlist(); //调用接口
                //回弹到初始位置
                document.getElementById("listWrapper").style.marginTop = "0px";
              }, 500);
              break;
          }
        };
        // 判断是上拉还是下拉
        function getpostion(PMoveY, PstartY) {
          if (PMoveY - PstartY == 0) {
            return 0; //无操作
          }
          if (PMoveY - PstartY < 0) {
            return 1; //向上
          }
          if (PMoveY - PstartY > 0) {
            return 2; //向下
          }
        }
      };
    },
    
    sub(type) {
      let that = this;
      // 判断次数,在发送
      if (type === 1) {
        this.messages.push({
          aiId: "", //编号
          userId: "", //用户id
          userAsk: "", //用户询问
          aiReply: "", //ai回复
          askTime: "", //询问时间
          isPdf: true, //是否是pdf
          fileName: this.word.fileName, //文件名称
          fileSize: this.word.fileSize, //文件大小
          aiTopicId: this.topic.topicId //话题编号
        });
        this.$message.success("请稍等,正在响应中");
        this.load(this.messages[this.messages.length - 1], 1, that.chatContent);//调用接口
         setTimeout(() => {
            that.chatContent.scrollTop = that.chatContent.scrollHeight + 150;
          }, 500);
        this.word = {};
      } else {
        if (this.talk === "") {
          this.$message.error("请填写内容");
        } else {
          this.messages.push({
            aiId: "", //编号
            userId: "", //用户id
            userAsk: this.talk, //用户询问
            aiReply: "", //ai回复
            askTime: "", //询问时间
            isPdf: false, //是否是pdf
            fileName: null, //文件名称
            fileSize: null, //文件大小
            aiTopicId: this.topic.topicId //话题编号
          });
          this.$message.success("请稍等,正在响应中");
          this.load(
            this.messages[this.messages.length - 1],
            0,
            that.chatContent
          );
           setTimeout(() => {
            that.chatContent.scrollTop = that.chatContent.scrollHeight + 150;
          }, 500);
        }
        this.talk = "";
      }
    },
    // 获取ai结果
    load(item, type, chatContent) {
      this.disabled = true;
      if (type === 1) {
        source = new EventSourcePolyfill(
          Url +
            "userApp/ai/sse?question=" +
            this.word.fileDownloadPath +
            "&fileName=" +
            this.word.fileName +
            "&fileSize=" +
            this.word.fileSize +
            "&isPdf=true&aiTopicId=" +
            this.topic.topicId,
          {
            headers: {
              token: this.$cookies.get("userToken")
            }
          }
        );
      } else {
        source = new EventSourcePolyfill(
          Url +
            "userApp/ai/sse?question=" +
            this.talk +
            "&isPdf=false&aiTopicId=" +
            this.topic.topicId,
          {
            headers: {
              token: this.$cookies.get("userToken")
            }
          }
        );
      }
      // open:订阅成功(和后端连接成功)
      source.addEventListener("open", function(e) {});
      source.onmessage = e => {
        // console.log(JSON.parse(e.data));
        if (JSON.parse(e.data).end) {
          // 结束了
          item.aiId = JSON.parse(e.data).end;
          this.disabled = false;
          return;
        } else if (JSON.parse(e.data).error) {
          this.$message.error(JSON.parse(e.data).error);
          this.disabled = false;
          return;
        } else {
          item.aiReply += JSON.parse(e.data).content.replace(/\\n/g, "\n");
          if (this.isScrolling) {
            chatContent.scrollTop = chatContent.scrollHeight;
          }
        }
      };
      source.onerror = event => {
        if (event.error !== undefined) {
          this.$message.error("出错了,请联系客服人员");
        }
        this.disabled = false;
        event.target.close();
      };
    },
    // 上传文件
    talkchange(file, fileList) {
      this.$refs.upload.uploadFiles = [];
      this.fileList = [];
      this.fileList = fileList;
    },
    async http(file, fileList) {
      var formData = new FormData();
      formData.append("files", file.file);
      this.$axios.post("/dev/file/uploadFile", formData).then(res => {
        if (res.code === 200) {
          this.word = {
            fileName: res.data[0].fileName,
            fileSize: res.data[0].fileSize,
            fileDownloadPath: res.data[0].fileDownloadPath
          };
          // this.$message.success("上传成功");
          this.sub(1);
        }
      });
    },
  }
};
</script>

方法二

如果在流式数据中使用,需要添加判断条件,如果滚动条在底部就滚动,否则不滚动

  if (that.isScrolling) {

        that.scrollToBottom()

}

 

<template>
  <div id="Mindopt" class="Mindopt">
     <div id="right_AI" class="right_AI">
            <div
              style="text-align: left; margin-bottom: 20px"
              v-for="(ele, index) in messages"
              :key="index"
            >
              <div class="box1" v-if="ele.content"></div>
              <div class="box2" v-if="ele.reply_content"></div>
            </div>
          </div>
 </div>
</template>

<script>
export default {
  name: 'Mindopt',
  data () {
    return {
      isScrolling: true,
      oldScrollTop: 0,
    }
  },
  watch: {
    messages: {
      handler (val) {
        this.scrollToBottom()
      },
      deep: true
    }
  },
  mounted () {
    document.querySelector('#right_AI').addEventListener('scroll', this.handleScroll)
  },
  created () {
    this.chatMemory();
  },
  methods: {
    // 定义将滚动条定位在底部的方法
    scrollToBottom () {
      this.$nextTick(() => {
        if (this.isScrolling) {
          var container = this.$el.querySelector('#right_AI')
          container.scrollTop = container.scrollHeight
        }
      })
    },
    handleScroll () {
      let scrollTop = document.querySelector('#right_AI').scrollTop
      // 更新——滚动前,滚动条距文档顶部的距离
      let scrollStep = scrollTop - this.oldScrollTop
      this.oldScrollTop = scrollTop
      //判断当前是向上or向下滚动
      if (scrollStep < 0) {
        //向上
        this.isScrolling = false
      } else {
        this.isScrolling = true
      }
    },
     // 获取当前对话历史
    async chatMemory () {
      axios
        .get(this.$globalPath + "agent/info/1", {
          headers: {
            Authorization: getToken(),
          },
        })
        .then((response) => {
          this.messages = response.data.data.messages;
          // 为了防止图片在加载中,然后不能及时获取到高度,以至于滚动条不到底部
          setTimeout(() => {
            this.scrollToBottom()
          }, 1000)
        })
        .catch((error) => {
          // 处理错误情况
          console.error(error);
        });
    },
    
  }
}
</script>

三、流式数据

没有下拉刷新,就只是流式数据对话,

发送对话,滚动条到底部,ai回答的时候出现省略号闪烁。

上传多个文件并显示文件列表

              

第一种方法:使用new XMLHttpRequest()

<template>
  <div id="BotsLook" class="BotsLook">
    <!-- 左侧对话内容 -->
    <div style="display: flex; width: 100vw; height: calc(100% - 80px)">
      <div class="left">
        <div
          class="content"
          id="AIleft"
          :style="{
            height:
              uploadList.length > 0
                ? `calc(100% - 200px)`
                : `calc(100% - 110px)`,
          }"
        >
          <div
            style="text-align: left; margin-bottom: 20px"
            v-for="(ele, index) in ansterList"
            :key="index"
          >
            <div v-if="ele.files">
              <div style="margin-bottom: 20px">
                <div
                  style="
                    display: flex;
                    margin-bottom: 5px;
                    line-height: 32px;
                    font-size: 14px;
                    font-weight: 600;
                  "
                >
                  <img :src="avatar" class="left_box_content_lizi_avaterImg" />
                  <span>{{ info.nick_name }}</span>
                </div>
                <div v-if="Array.isArray(ele.files)">
                  <div
                    class="wenjian_box_show"
                    style="margin-left: 32px"
                    v-for="(obj, ind) in ele.files"
                    :key="ind"
                  >
                    <div style="display: flex">
                      <img
                        :src="imgs(obj.file_name)"
                        style="width: 30px; height: 36px"
                      />
                      <div style="width: 82%">
                        <div class="wenjian_box_name">
                          {{ obj.file_name }}
                        </div>
                        <div style="font-size: 12px">
                          {{ obj.file_size | sizeTostr }}
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
                <div v-else class="wenjian_box_show" style="margin-left: 32px">
                  <div style="display: flex">
                    <img
                      :src="imgs(ele.files.file_name)"
                      style="width: 30px; height: 40px"
                    />
                    <div style="width: 82%">
                      <div class="wenjian_box_name">
                        {{ ele.files.file_name }}
                      </div>
                      <div style="font-size: 12px">
                        {{ ele.files.file_size | sizeTostr }}
                      </div>
                    </div>
                  </div>
                </div>
                <div>
                  <div
                    v-if="ele.content"
                    class="left_box_content_anster"
                    :style="{
                      'margin-left': '32px',
                    }"
                    v-html="ele.content"
                  ></div>
                </div>
              </div>
            </div>
            <div v-else>
              <div v-if="ele.content" style="margin-bottom: 20px">
                <div
                  style="
                    display: flex;
                    margin-bottom: 5px;
                    line-height: 32px;
                    font-size: 14px;
                    font-weight: 600;
                  "
                >
                  <img :src="avatar" class="left_box_content_lizi_avaterImg" />
                  <span>{{ info.nick_name }}</span>
                </div>
                <div
                  class="left_box_content_anster"
                  style="margin-left: 32px"
                  v-html="ele.content"
                ></div>
              </div>
            </div>
            <div style="margin-bottom: 20px">
              <div
                style="
                  display: flex;
                  margin-bottom: 5px;
                  line-height: 32px;
                  font-size: 14px;
                  font-weight: 600;
                "
              >
                <img
                  :src="BotsInfo.ico"
                  class="left_box_content_lizi_avaterImg"
                />
                <span>{{ BotsInfo.name }}</span>
              </div>
              <div
                v-if="ele.reply_content"
                style="display: inline-block; max-width: 100%"
                class="imghover"
              >
                <div
                  v-highlight
                  class="left_box_content_lizi_AI markdown-body"
                  style="margin-left: 32px"
                  v-html="ele.reply_content"
                ></div>
              </div>
            </div>
          </div>
          <div
            v-if="overdot"
            class="left_box_content_lizi_AI"
            style="margin-left: 32px; padding: 12px"
          >
            <span class="dot">•••</span>
          </div>
        </div>
        <!-- 停止响应 -->
        <div v-if="over" style="margin: auto; width: 100px">
          <el-button
            :style="{
              position: 'absolute',
              bottom: uploadList.length > 0 ? '190px' : '100px',
            }"
            @click="overBtn"
            >{{ $t('Stop') }}</el-button
          >
        </div>
        <!-- 输入框+文件 -->
        <div class="left_box_inpt">
          <div class="right_box_inpt_wenjian" v-if="uploadList.length > 0">
            <div class="wenjian_far_box">
              <div
                class="wenjian_box"
                style="position: relative"
                v-for="(ele, index) in uploadList"
                :key="index"
              >
                <div v-if="imgs(ele.file.name) === 'image'">
                  <img :src="ele.location" style="width: 30px; height: 36px" />
                </div>
                <div v-else style="display: flex">
                  <img
                    :src="imgs(ele.file.name)"
                    style="width: 30px; height: 36px"
                  />
                  <div style="width: 65%">
                    <div class="wenjian_box_name">
                      {{ ele.file.name }}
                    </div>
                    <div style="font-size: 12px" v-if="!ele.progressFlag">
                      {{ ele.file.size | sizeTostr }}
                    </div>
                    <div style="font-size: 12px" v-if="ele.progressFlag">
                      {{ ele.progress }}%
                    </div>
                  </div>
                </div>
                <el-button
                  @click="deleteUpload(ele, index)"
                  type="text"
                  class="subbtn wenjian_btn"
                  icon="el-icon-error"
                >
                </el-button>
                <div v-if="ele.progressFlag" class="mask"></div>
              </div>
            </div>
            <div style="display: flex">
              <el-input
                v-model="memory"
                @input="oninput"
                @keyup.enter.native="sub()"
              >
              </el-input>
              <div style="display: flex; line-height: 50px; padding: 0 10px">
                <el-tooltip
                  class="item"
                  effect="dark"
                  :content="$t('upload')"
                  placement="top"
                >
                  <el-upload
                    ref="upload"
                    action="#"
                    multiple
                    :http-request="httpRequest"
                    :before-upload="beforeUpload"
                    :show-file-list="false"
                    accept=".pdf,docx,.xls,.xlsx"
                  >
                    <el-button
                      class="subbtn"
                      type="text"
                      style="padding: 17px 0"
                      icon="el-icon-circle-plus-outline"
                    >
                    </el-button>
                  </el-upload>
                </el-tooltip>
                <span
                  style="
                    width: 1px;
                    height: 15px;
                    background-color: #dcdfe6;
                    margin: auto 10px;
                  "
                ></span>
                <el-tooltip
                  class="item"
                  effect="dark"
                  :content="$t('send')"
                  placement="top"
                >
                  <el-button
                    :disabled="BtnDisabled"
                    class="subbtn"
                    type="text"
                    @click="sub()"
                  >
                    <img
                      v-if="BtnDisabled"
                      src="@/assets/right1.png"
                      style="width: 15px"
                    />
                    <img v-else src="@/assets/right3.png" style="width: 15px" />
                  </el-button>
                </el-tooltip>
              </div>
            </div>
          </div>
          <div class="right_box_inpt" v-else>
            <el-input
              v-model="memory"
              @input="oninput"
              @keyup.enter.native="sub()"
            >
            </el-input>
            <div style="display: flex; line-height: 50px; padding: 0 10px">
              <el-tooltip
                class="item"
                effect="dark"
                :content="$t('upload')"
                placement="top"
              >
                <el-upload
                  ref="upload"
                  action="#"
                  multiple
                  :http-request="httpRequest"
                  :before-upload="beforeUpload"
                  :show-file-list="false"
                  accept=".pdf,docx,.xls,.xlsx"
                >
                  <el-button
                    class="subbtn"
                    type="text"
                    style="padding: 17px 0"
                    icon="el-icon-circle-plus-outline"
                  >
                  </el-button>
                </el-upload>
              </el-tooltip>
              <span
                style="
                  width: 1px;
                  height: 15px;
                  background-color: #dcdfe6;
                  margin: auto 10px;
                "
              ></span>
              <el-tooltip
                class="item"
                effect="dark"
                :content="$t('send')"
                placement="top"
              >
                <el-button
                  :disabled="BtnDisabled"
                  class="subbtn"
                  type="text"
                  @click="sub()"
                >
                  <img
                    v-if="BtnDisabled"
                    src="@/assets/right1.png"
                    style="width: 15px"
                  />
                  <img v-else src="@/assets/right3.png" style="width: 15px" />
                </el-button>
              </el-tooltip>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import axios from "axios";
import { getToken } from "@/utils/auth";
import { matchType } from "@/utils/index";
import { mapGetters } from "vuex";
import { marked } from "marked";
// import hljs from "highlight.js";
// 两个地方
var xhr = new XMLHttpRequest();
export default {
  computed: {
    ...mapGetters(["sidebar", "avatar", "name", "info"]),
  },
  data () {
    return {
      active: null,
      BotsInfo: {
        plugins: [],
        private: [],
      },
      memory: "", //发送信息
      ansterList: [],
      over: false, //停止响应
      overdot: false, //省略号是否显示
      BtnDisabled: true, //发送按钮
      isScrolling: true,
      starttoolTime: 0, // 工具开始时间
      startTime: 0, // 模型开始时间
      endTime: 0, // 结束时间
      uploadList: [],
      loading: false, //文件上传中
      dicshow: true,
      dicshowBtn: false,
      tooltipIsShow: false,
      uniqueArr: [],
      shareData: {
        qr_code: "",
        url: ""
      },
      loading: true,
      oldScrollTop: 0
    };
  },
  filters: {
    sizeTostr: function (val) {
      if (val === 0) return "0 B";
      var k = 1024;
      var sizes = ["B", "KB", "MB", "GB", "PB", "TB", "EB", "ZB", "YB"],
        i = Math.floor(Math.log(val) / Math.log(k));
      return (val / Math.pow(k, i)).toPrecision(3) + "" + sizes[i];
    },
  },
  watch: {
    ansterList: {
      handler (val) {
        this.scrollToBottom()
      },
      deep: true
    }
  },
  created () {
    this.agentBotInfo();
  },
  mounted () {
    document.querySelector('#AIleft').addEventListener('scroll', this.handleScroll)
    setTimeout(() => {
      const dom = this.$el.querySelector("#rightdicshow")
      const dicFather = this.$el.querySelector("#dicFather")
      const domScrollWidth = dom && dom.scrollWidth
      const domClientWidth = dicFather && dicFather.clientWidth
      this.tooltipIsShow = (domScrollWidth + 62) === domClientWidth
    }, 100);
  },
  beforeDestroy () {
    xhr.abort();
    this.overdot = false;
    this.over = false;
    this.BtnDisabled = false;
    this.memory = "";
  },
  methods: {
    // 定义将滚动条定位在底部的方法
    scrollToBottom () {
      this.$nextTick(() => {
        if (this.isScrolling) {
          var container = this.$el.querySelector('#AIleft')
          container.scrollTop = container.scrollHeight
        }
      })
    },
    handleScroll () {
      let scrollTop = document.querySelector('#AIleft').scrollTop
      // 更新——滚动前,滚动条距文档顶部的距离
      let scrollStep = scrollTop - this.oldScrollTop
      this.oldScrollTop = scrollTop
      //判断当前是向上or向下滚动
      if (scrollStep < 0) {
        //向上
        this.isScrolling = false
      } else {
        this.isScrolling = true
      }
    },

    // 获取详情
    agentBotInfo () {
      axios
        .get(
          this.$globalPath + "agent/info/" + JSON.parse(this.$route_query.id),
          {
            headers: {
              Authorization: getToken(),
            },
          }
        )
        .then((response) => {
          this.BotsInfo = response.data.data;
          this.ansterList = this.BotsInfo.messages;
          if (this.ansterList.length > 0) {
            this.ansterList.forEach((i) => {
              i.text = i.reply_content;
              if (i.reply_content) {
                // 创建自定义渲染器
                class CustomRenderer extends marked.Renderer {
                  heading (text, level) {
                    // 将一级标题转换为h1标签
                    if (level === 1) {
                      return `<div class="hClass"># ${text}</div>`;
                    } else if (level === 2) {
                      return `<div class="hClass">## ${text}</div>`;
                    } else if (level === 3) {
                      return `<div class="hClass">### ${text}</div>`;
                    } else if (level === 4) {
                      return `<div class="hClass">#### ${text}</div>`;
                    } else if (level === 5) {
                      return `<div class="hClass">##### ${text}</div>`;
                    } else if (level === 6) {
                      return `<div class="hClass">###### ${text}</div>`;
                    }
                  }
                }
                // 使用自定义渲染器
                const renderer = new CustomRenderer();
                i.reply_content = marked(i.reply_content, {
                  renderer: renderer,
                  breaks: true,
                });
                // i.reply_content = marked(i.reply_content);
              }
            });
          }
        })
        .catch((error) => {
          // 处理错误情况
          console.error(error);
        });
    },
    // 对话-----------------------------------------------------------------------------------
    // 根据文件名,判断后缀名格式,然后返回对应的图片
    imgs (val) {
      let text = matchType(val);
      if (text === "pdf") {
        return require("@/assets/pdf.svg");
      } else if (text === "image") {
        return text;
      } else if (text === "excel") {
        return require("@/assets/excel.svg");
      } else if (text === "word") {
        return require("@/assets/word.svg");
      }
    },
    // 上传文件前
    beforeUpload (file) {
      // 限制文件大小和pdf不能超过250页
      const fileLi = {};
      for (const key in file) {
        fileLi[key] = file[key];
      }
      const isLt2M = file.size > 10 * 1024 * 1024;
      let fileType = file.name.substring(file.name.lastIndexOf(".") + 1);
      const istype = ["pdf", "docx", "xls", "xlsx"].includes(fileType);
      if (!istype) {
        this.$message.error(this.$t("uploadType"));
        return false;
      }
      if (isLt2M) {
        this.$message.error(this.$t("uploadLt2M"));
        return false;
      }

      if (istype && !isLt2M) {
        this.uploadList.push({
          file: fileLi,
          progress: 0,
          status: "ready",
          location: "",
          progressFlag: false,
          local_file_url: "",
        });
        // this.loading = true;
      }
      return istype && !isLt2M;
    },
    async httpRequest (file, callback) {
      this.BtnDisabled = true;
      let formdata = new FormData();
      formdata.append("file", file.file);
      this.uploadList.forEach((i) => {
        if (i.file.uid === file.file.uid) {
          i.progressFlag = true;
        }
      });
      axios({
        url: this.$globalPath + "upload/kimi_upload",
        method: "post",
        data: formdata,
        headers: {
          "Content-Type": "multipart/form-data",
          Authorization: getToken(),
        },
        onUploadProgress: (progressEvent) => {
          // progressEvent.loaded:已上传文件大小
          // progressEvent.total:被上传文件的总大小
          this.uploadList.forEach((i) => {
            if (i.file.uid === file.file.uid) {
              i.progress = (progressEvent.loaded / progressEvent.total) * 100;
            }
          });
          // this.uploadList[0].progress =
          //   (progressEvent.loaded / progressEvent.total) * 100;
        },
      }).then((response) => {
        if (response.status === 200) {
          this.uploadList.forEach((i) => {
            if (i.file.uid === file.file.uid) {
              i.status = "success";
              i.file_id = response.data.file_id;
              i.location = response.data.file_location;
              i.local_file_url = response.data.local_file_url;
              if (i.progress === 100) {
                i.progressFlag = false;
              }
            }
          });
        } else {
          this.uploadList.forEach((i) => {
            if (i.file.uid === file.file.uid) {
              i.status = "error";
              this.uploadList.splice(index, 1);
            }
          });
        }
        let a = this.uploadList.every((item) => !item.progressFlag);
        if (a) {
          this.BtnDisabled = false;
        }
      });
    },
    // 删除文件
    deleteUpload (ele, index) {
      this.uploadList.splice(index, 1);
      if (this.memory === "" && this.uploadList.length === 0) {
        this.BtnDisabled = true;
      }
    },
    // 监听输入框
    oninput (e) {
      if (e !== "") {
        this.BtnDisabled = false;
      } else {
        this.BtnDisabled = true;
      }
    },
    // 停止回响
    overBtn () {
      xhr.abort();
      this.overdot = false;
      this.over = false;
      this.BtnDisabled = false;
      this.memory = "";
      this.ansterList[this.ansterList.length - 1].reply_content = "";
    },
    // 发送对话
    sub (ele) {
      let that = this;
      if (ele) {
        // 输入的内容
        this.ansterList.push({
          reply_content: "", //ai回复内容
          // id: null,
          // appid: 1, //应用编号 首页对话为1
          // role: "user", //user=正常对话  system=预设内容
          content: ele, //对话内容
          userid: this.info.id, //当前用户
          toolshow: false, //是否调用工具
          tool: 0, //0是正在调用,1是已调用,2是运行完毕,3是展示运行结果
          modelTime: 0, //模型时间
          toolTime: 0, //工具时间
          Time: 0, //总时间
          toolName: "", //工具名称
          toolText: false,
        });
      } else {
        if (this.uploadList.length === 0) {
          // 输入的内容
          this.ansterList.push({
            reply_content: "", //ai回复内容
            content: this.memory, //对话内容
            userid: this.info.id, //当前用户
            toolshow: false,
            tool: 0,
            modelTime: 0, //模型时间
            toolTime: 0, //工具时间
            Time: 0,
            toolName: "",
            toolText: false,
          });
        } else {
          const files = [];
          this.uploadList.forEach((i) => {
            files.push({
              file_name: i.file.name, //文件名
              file_size: i.file.size, //文件大小
              file_url: i.location,
              file_id: i.file_id,
              local_file_url: i.local_file_url,
            });
          });
          // 上传文件
          this.ansterList.push({
            reply_content: "", //ai回复内容
            content: this.memory, //对话内容
            userid: this.info.id, //当前用户
            toolshow: false,
            tool: 0,
            modelTime: 0, //模型时间
            toolTime: 0, //工具时间
            Time: 0,
            toolName: "",
            toolText: false,
            files: files,
          });
        }
      }
      const tijiao = {
        id: this.BotsInfo.id,
        memory: ele ? ele : this.memory, //对话内容
      };
      if (this.uploadList.length > 0) {
        tijiao.files = [];
        this.uploadList.forEach((i) => {
          tijiao.files.push({
            file_name: i.file.name, //文件名
            file_size: i.file.size, //文件大小
            file_url: i.location,
            file_id: i.file_id,
            local_file_url: i.local_file_url,
          });
        });
      }
      // console.log(tijiao);
      setTimeout(() => {
        this.load(
          this.ansterList[this.ansterList.length - 1],
          that.chatContent,
          tijiao
        );
      }, 500);
    },
    // 获取ai结果
    async load (item, chatContent, tijiao) {
      this.startTime = 0;
      this.startTime = new Date().getTime();
      this.overdot = true;
      this.over = true;
      this.BtnDisabled = true;
      this.memory = "";
      this.uploadList = [];
      const that = this;
      let toolList = [];
      if (that.isScrolling) {
        that.scrollToBottom()
      }
      xhr.open("POST", this.$globalPath + "agent/public_chat", true);
      xhr.setRequestHeader("Content-Type", "application/json");
      xhr.setRequestHeader("Authorization", getToken());
      xhr.onreadystatechange = function () {
        // console.log(xhr);
        if (xhr.readyState === 4 && xhr.status === 200) {
          console.log("读取完毕");
          // var result = xhr.responseText;
          // console.log(result);
          that.over = false;
          that.overdot = false;

        } else if (xhr.status !== 200 && xhr.status !== 0) {
          console.log("HTTP 错误:" + xhr.status);
          that.over = false;
          that.overdot = false;
        } else if (xhr.status === 0 && xhr.readyState !== 4) {
          this.overdot = true;
          this.over = true;
        } else if (xhr.status === 0 && xhr.readyState === 4) {
          that.over = false;
          that.overdot = false;
          that.$message({
            message: this.$t("errorsub"),
            type: "error",
          });
        }
      };
      xhr.onprogress = function (e) {
        // console.log("onprogress", e);
        //每次数据到达都会触发
        var response = e.currentTarget.response;
        if (response.slice(-6) == "[done]") {
          // 这里做当收到'[done]'时的处理
          console.log("成功完成!");
          that.over = false;
          that.overdot = false;
          // item.reply_content = 'load1' + response
        } else if (response.substr(0, 6) === "开始工具调用") {
          item.toolshow = true;
          item.tool = 0;
          that.$forceUpdate();
          that.starttoolTime = 0;
          that.starttoolTime = new Date().getTime();
          let coli = response.substring(response.lastIndexOf("正在调用"));
          item.toolName = coli.substr(0, coli.indexOf(",")).substr(4);
          let str = response.substring(6, response.indexOf("正在调用"));
          that.toolRun(item, chatContent, str);
        } else if (response.startsWith("多工具调用")) {
          // console.log(response);
          let aa = response.slice(6);
          // 获取工具,too->开头,s\n结尾的
          const toolRegex = /tool->([\s\S]+?)s\n/g;
          if (aa.match(toolRegex) && aa.match(toolRegex).length > 0) {
            // 去重
            toolList = aa.match(toolRegex).filter((item, index, self) => {
              return index === self.findIndex((obj) => obj === item);
            });
            that.uniqueArr = [];
            // 去除开头tool->,结尾s\n
            toolList.forEach((i) => {
              i = i.slice(6, -2);
              that.uniqueArr.push(i.split("|"));
            });
            if (that.uniqueArr.length > 0) {
              item.toolName = that.uniqueArr[0][0];
              // that.uniqueArr.forEach((j) => {
              //   item.toolName = j[0];
              //   item.toolshow = true;
              //   item.tool = 0;
              // });
              if (item.toolName) {
                item.toolshow = true;
                item.tool = 0;
              }
            }
            // 去除插件的部分,保留回答的内容
            let abc = null;
            abc = toolList.join(" ");
            // 创建自定义渲染器
            class CustomRenderer extends marked.Renderer {
              heading (text, level) {
                // 将一级标题转换为h1标签
                if (level === 1) {
                  return `<div class="hClass"># ${text}</div>`;
                } else if (level === 2) {
                  return `<div class="hClass">## ${text}</div>`;
                } else if (level === 3) {
                  return `<div class="hClass">### ${text}</div>`;
                } else if (level === 4) {
                  return `<div class="hClass">#### ${text}</div>`;
                } else if (level === 5) {
                  return `<div class="hClass">##### ${text}</div>`;
                } else if (level === 6) {
                  return `<div class="hClass">###### ${text}</div>`;
                }
              }
            }
            // 使用自定义渲染器
            const renderer = new CustomRenderer();
            const lastcont = aa.slice(abc.length - 1);
            if (lastcont.trim().length > 0) {
              item.tool = 2;
              item.Time = 0;
              item.modelTime = 0;
              item.toolTime = 0;
              that.uniqueArr.forEach((i) => {
                item.Time += Number(i[1].slice(0, -1));
                item.modelTime += Number(i[2].slice(2, -1));
                item.toolTime += Number(i[3].slice(2));
              });
              item.Time = item.Time.toFixed(2);
              item.modelTime = item.modelTime.toFixed(2);
              item.toolTime = item.toolTime.toFixed(2);
              item.reply_content = marked(lastcont, {
                renderer: renderer,
                breaks: true,
              });
              item.text = lastcont;
            } else {
              item.tool = 1;
            }
            if (that.isScrolling) {
              chatContent.scrollTop = chatContent.scrollHeight;
            }
          }
        } else {
          item.toolshow = false;
          that.over = true;
          that.overdot = false;
          // 创建自定义渲染器
          class CustomRenderer extends marked.Renderer {
            heading (text, level) {
              // 将一级标题转换为h1标签
              if (level === 1) {
                return `<div class="hClass"># ${text}</div>`;
              } else if (level === 2) {
                return `<div class="hClass">## ${text}</div>`;
              } else if (level === 3) {
                return `<div class="hClass">### ${text}</div>`;
              } else if (level === 4) {
                return `<div class="hClass">#### ${text}</div>`;
              } else if (level === 5) {
                return `<div class="hClass">##### ${text}</div>`;
              } else if (level === 6) {
                return `<div class="hClass">###### ${text}</div>`;
              }
            }
          }
          // 使用自定义渲染器
          const renderer = new CustomRenderer();
          item.reply_content = marked(response, {
            renderer: renderer,
            breaks: true,
          });
          item.text = response;
          // item.reply_content = item.reply_content.replace(/\s+$/m, "");
        }
        if (that.isScrolling) {
          that.scrollToBottom()
        }
      };
      xhr.send(JSON.stringify(tijiao));
    },
    //调用工具接口
    toolRun (item, chatContent, str) {
      this.endTime = 0;
      this.over = true;
      this.BtnDisabled = true;
      const that = this;
      item.tool = 1;
      if (that.isScrolling) {
        that.scrollToBottom()
      }
      xhr.open("POST", this.$globalPath + "agent/tool_run", true);
      xhr.setRequestHeader("Content-Type", "application/json");
      xhr.setRequestHeader("Authorization", getToken());
      xhr.onreadystatechange = function () {
        // console.log('toolRun', xhr);
        if (xhr.readyState === 4 && xhr.status === 200) {
          console.log("读取完毕");
          // var result = xhr.responseText;
          // console.log(result);
          if (item.tool === 3) {
            localStorage.setItem("tool", 2);
            item.toolText = true;
            that.$forceUpdate();
          } else {
            item.tool = 2;
            item.toolText = true;
          }
          that.over = false;
          that.overdot = false;
          // that.agentBotInfo();
        } else if (xhr.status !== 200 && xhr.status !== 0) {
          console.log("HTTP 错误:" + xhr.status);
          that.over = false;
          that.overdot = false;
        } else if (xhr.status === 0 && xhr.readyState !== 4) {
          this.overdot = true;
          this.over = true;
        } else if (xhr.status === 0 && xhr.readyState === 4) {
          that.over = false;
          that.overdot = false;
          that.$message({
            message: this.$t("errorsub"),
            type: "error",
          });
        }
      };
      xhr.onprogress = function (e) {
        if (that.endTime === 0) {
          that.endTime = new Date().getTime();
          item.toolTime = (that.endTime - that.starttoolTime) / 1000; // 转换为秒
          item.modelTime = (that.endTime - that.startTime) / 1000; // 转换为秒
          item.Time = (item.toolTime + item.modelTime).toFixed(2);
        }
        // console.log("onprogress", e);
        //每次数据到达都会触发
        var response = e.currentTarget.response;
        // console.log('toolRun', response);
        if (response.slice(-6) == "[done]") {
          // 这里做当收到'[done]'时的处理
          console.log("成功完成!");
          that.over = false;
          that.overdot = false;
        } else {
          that.over = true;
          that.overdot = false;
          // 创建自定义渲染器
          class CustomRenderer extends marked.Renderer {
            heading (text, level) {
              // 将一级标题转换为h1标签
              if (level === 1) {
                return `<div class="hClass"># ${text}</div>`;
              } else if (level === 2) {
                return `<div class="hClass">## ${text}</div>`;
              } else if (level === 3) {
                return `<div class="hClass">### ${text}</div>`;
              } else if (level === 4) {
                return `<div class="hClass">#### ${text}</div>`;
              } else if (level === 5) {
                return `<div class="hClass">##### ${text}</div>`;
              } else if (level === 6) {
                return `<div class="hClass">###### ${text}</div>`;
              }
            }
          }
          // 使用自定义渲染器
          const renderer = new CustomRenderer();
          item.reply_content = marked(response, {
            renderer: renderer,
            breaks: true,
          });
          item.text = response;
          // item.reply_content = item.reply_content.replace(/\s+$/m, "");
        }
        if (that.isScrolling) {
          that.scrollToBottom()
        }
      };
      xhr.send(str);
    },
  },
};
</script>

<style lang="scss" scoped>
.dot {
  font-family: simsun; /*固定字体避免设置的宽度无效*/
  animation: dot 1s infinite step-start;
  display: inline-block;
  width: 1.5em;
  vertical-align: bottom; /*始终让省略号在文字的下面*/
  overflow: hidden;
}
@keyframes dot {
  /*动态改变显示宽度, 但始终让总占据空间不变, 避免抖动*/
  0% {
    width: 0;
    margin-right: 1.5em;
  }
  33% {
    width: 0.5em;
    margin-right: 1em;
  }
  66% {
    width: 1em;
    margin-right: 0.5em;
  }
  100% {
    width: 1.5em;
    margin-right: 0;
  }
}
</style>

第二种方法:post方法使用插件@microsoft/fetch-event-source

这里就只写方法了

 其中new AbortController()是为了可以断开连接

<script>
let ctrlAbout2 = null;
import { fetchEventSource } from '@microsoft/fetch-event-source';
export default {
  data () {
    return {
    };
  },
  beforeDestroy () {
    if (ctrlAbout2) {
      ctrlAbout2.abort()
    }
  },
  methods: {
    toolRun (item, chatContent, str) {
      ctrlAbout2 = new AbortController();
      let source2 = fetchEventSource(this.$globalPath + "agent/tool_run", {
        method: 'POST',
        headers: {
          "Content-Type": 'application/json',
          "Accept": 'text/event-stream; charset=utf-8',
          'Connection': 'keep-alive',
          'Authorization': getToken()
        },
        body: JSON.stringify(str),
        openWhenHidden: true,
        onopen (response) {
          // // console.log(response)
          // if (response.ok) {
          //   return; // everything's good
          // } else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
          //   // client-side errors are usually non-retriable:
          //   throw new FatalError();
          // } else {
          //   throw new RetriableError();
          // }
        },
        onmessage (event) {
          //每次数据到达都会触发
          // 返回的JSON格式,是单引号不能转换,必须双引号才能JSON.parse
          var res2 = event.data.replaceAll(/"/g, '\\"')
          var res = res2.replaceAll(/'/g, '"')
          var response = JSON.parse(res).message
          // var response = event.data
          if (response === "[done]") {
            // 这里做当收到'[done]'时的处理
            console.log("成功完成!");
            that.over = false;
            that.overdot = false;
            that.BtnDisabled = true;
            that.inputdisabled = false
          } else {
            that.over = true;
            that.overdot = false;
            messag1 += response.replaceAll("\\n", "\n")
            item.reply_content = marked(messag1, markedOptions);
            item.text = messag1;
          }
          if (that.isScrolling) {
            that.scrollToBottom()
          }
        },
        onclose (msg) {
          // console.log(msg);
          ctrlAbout2.abort()//出错后不要重试
          that.over = false;
          that.BtnDisabled = true;
          that.inputdisabled = false
        },
        onerror (error) {
          // console.log(error);
          ctrlAbout2.abort()
          that.over = false;
          that.BtnDisabled = true;
          that.inputdisabled = false
          throw error// 必须抛出错误,否则停止不了
        }
      })
    },
    // 停止回响
    overBtn () {
      if (ctrlAbout) {
        ctrlAbout.abort()
      }
      if (ctrlAbout2) {
        ctrlAbout2.abort()
      }
      // xhr.abort();
      this.overdot = false;
      this.over = false;
      this.BtnDisabled = false;
      this.inputdisabled = false
      this.messinput = "";
      // this.messages[this.messages.length - 1].reply_content = "";
    },
  },
};
</script>

第三种方法:get使用插件"event-source-polyfill"

<script>
let eventSource = null
import { EventSourcePolyfill } from "event-source-polyfill";
export default {
  data () {
    return {
    };
  },
  beforeDestroy () {
    if (eventSource) {
      eventSource.close();
    }
  },
  methods: {
     getanalysis (uuid, ele) {
      this.itemanalysis = ele
      ele.isanalysis = true
      this.$forceUpdate();
      if (eventSource) {
        eventSource.close();
      }
      ele.drawerAnalysis = ''
      let url = this.$globalPath + 'agent/get_logs?uuid=' + uuid
      let that = this
      this.drawerdot = true
      if (that.isScrolling) {
        that.scrollToBottom()
      }
      // 如果需要加参数的话是在url 后面拼接参数 sysm/user/1
      eventSource = new EventSourcePolyfill(
        url,
        {
          // heartbeatTimeout: 3 * 60 * 1000,//这是自定义配置请求超时时间  默认是4500ms(印象中是)
          headers: {
            'Authorization': getToken(),
          },
        }
      );
      let message = ''
      // open:订阅成功(和后端连接成功)
      eventSource.onopen = function (e) { };
      eventSource.onmessage = function (e) {
        if (e.data === '[done]') {
          eventSource.close();
          that.drawerdot = false
        } else {
          // 返回的JSON格式,是单引号不能转换,必须双引号才能JSON.parse
          var res2 = e.data.replaceAll(/"/g, '\\"')
          var res = res2.replaceAll(/'/g, '"')
          var cont = JSON.parse(res).message

          message += cont.replaceAll("\\n", "\n") + ``
          ele.drawerAnalysis = marked(message, {
            breaks: true,
          });
          if (that.isScrolling) {
            that.scrollToBottom()
          }
        }
      };
      //error:错误(可能是断开,可能是后端返回的信息)
      eventSource.onerror = function (e) {
        that.drawerdot = false
        e.target.close(); // 关闭连接
      };
    },
  },
};
</script>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现人工智能对话可以使用第三方库或API,例如微软的Bot Framework、阿里的小蜜、百度的UNIT等。同时,Vue.js作为一款流行的前端框架,可以通过引入相关的第三方库来实现人工智能对话。 一种常用的实现方式是将人工智能对话系统封装为一个独立的组件,然后在Vue组件中进行调用。具体来说,可以通过以下步骤来实现人工智能对话: 1. 引入人工智能对话的第三方库或API; 2. 创建一个Vue组件,用于渲染对话窗口; 3. 在Vue组件中通过调用第三方库或API来实现对话功能。 以下是一个简单的示例代码: ```html <template> <div class="dialog"> <div class="chat-window"> <div v-for="message in messages" :key="message.id"> {{ message.text }} </div> </div> <div class="input-field"> <input type="text" v-model="inputValue" @keyup.enter="sendMessage" /> </div> </div> </template> <script> import chatbot from 'chatbot-library'; export default { data() { return { messages: [], inputValue: '', }; }, methods: { async sendMessage() { // 发送用户输入 const userText = this.inputValue; this.messages.push({ id: Date.now(), text: userText, isBot: false, }); this.inputValue = ''; // 获取机器人回复 const botText = await chatbot.getReply(userText); // 显示机器人回复 this.messages.push({ id: Date.now(), text: botText, isBot: true, }); }, }, }; </script> <style> .chat-window { height: 300px; overflow-y: scroll; } .input-field { margin-top: 10px; } </style> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值