基于uniapp/vue 通过实现ai 对话流 EventSource的形式

<template>
  <view class="container">
    <view>
      <view v-for="item in listData" :key="item.id">
        <view class="icon" style="justify-content: flex-end">
          <view class="model-titel right"
            ><span> {{ item.oldUserMessage }}</span></view
          >
          <span class="icon-title right">: 提问</span
          ><img
            style="margin-right: 0px; margin-left: 2px"
            src="../../static/imgs/user-icon.png"
        /></view>
        <view class="icon">
          <img
            src="../../static/imgs/service-icon.png"
            style="width: 22px; padding: 0 1px"
          /><span class="icon-title"> 回复:</span>
          <view class="model-titel"
            ><span> {{ item.messages }}</span></view
          >
        </view>
      </view>
      <view class="btn">
        <view style="padding-bottom: 10rpx">
          <u-input
            v-model="userMessage"
            :disabled="lodingAi"
            @keyup.enter="sendMessage"
            placeholder="输入消息"
          />
        </view>
        <u-button @click="sendMessage" :disabled="lodingAi">发送</u-button>
      </view>
    </view>
  </view>
</template>
<script>
let controller = new AbortController();
let signal = controller.signal;
export default {
  components: {},
  data() {
    return {
      messages: [],
      lodingAi: false,
      userMessage: "",
      msgIndex: 0,
      listData: [
        {
          oldUserMessage: "测试问题",
          messages: "测试AI回复",
          id: 1,
        },
      ],
      eventSource: null,
    };
  },
  methods: {
  async sendMessage() {
      if (!this.userMessage.trim()) return;

        const obj = {
           message: '问题',
           model: { id: ’id-1‘, idx: 3}
        }
        //  数据处理
        const queryString = JSON.stringify(obj); // 将对象转换为查询字符串
        const param = encodeURIComponent(queryString); //转url编码
        const param1 = param.replace(/\%/g, '%25');  // 转换%号传给后端
        const param2 = param1.replace(/\+/g, '%2B');  // 转换+号传给后端
       // eventSource  请求
        this.eventSource = new EventSource(JAVA_API_BASE_URL +"/lark/getAnswerEmitterToWebUi?req=" + param2)
        this.lodingAi = true; // 显示加载中
        this.listData.push({
          // 存储会话记录
          oldUserMessage: this.userMessage,
          messages: "",
          idx: this.msgIndex++,
        });
        this.userMessage = ""; // 保存后清空上次记录
        // 处理流数据
       
       this.eventSource.onmessage = (event) => {
        this.scrollToBottom(); // 更新滚动条到最底部
       // 接口异常处理
        if ( event.data &&this.isJSON(event.data) &&JSON.parse(event.data)?.errorCode === 406 ) {
          this.$tip.toast("抱歉!提问解析答案异常,请联系相关开发人员解决");
           return
        }
        const text = event.data;
        textJson = JSON.parse(text);
        let textJson = null;
        if (textJson?.data?.answer) {
          // 解析文字
          const answer = textJson?.data?.answer?.replace(
            /\*\*(.*?)\*\*/g,
            "<strong>$1</strong>"
          );
          // const javastr = answer.replace(/System\.out\.println/g, 'console.log'); // 解析java代码, js直接读取会报错
          const answer03 = answer.replace(
            /```([\s\S]*?)```/g,
            '<p style="background: rgb(52,53,65);padding: 10px;border-radius: 4px; color: rgb(255,255,255);font-size: 12px;" >$1</p>'
          );
          const answer01 = answer03.replace(/\\div/g, "/");
          const answer0 = answer01.replace(/\\\[(.*?)\\\]/g, "[$1]");
          const answer1 =  answer0?.replace(/\##(.+?)\$\$/g, (match) => {
              return `<a class="dialog" style="display: inline-block; width: 18px;" idx="${
                this.listData.length - 1
              }" id="${match}" href="javascript:void(0);" data-action="click"><img style="width: 18px; height:18px; margin-right: 0;" src=${imgicon} alt=""></a>`;
            });
          const answer2 = answer1?.replace(/### (.*)/g, "<h4>$1</h4>");
          const answer3 = answer2.replace(/\\\((.*?)\\\)/g, "($1)");
          // 更新会话记录
          this.listData[this.listData.length - 1].messages = answer3 ;
          // 更新弹窗内容
          this.listData[this.listData.length - 1].chunks =
            textJson?.data?.reference?.chunks || [];
          this.scrollToBottom(); // 更新滚动条到最底部
          // 储存对应文档以及文档名称
          const doc_aggs = textJson?.data?.reference?.doc_aggs || [];

          this.listData[this.listData.length - 1].docAggs = doc_aggs;
        }
        // 处理接收到的数据
      };

      this.eventSource.onerror = (error) => {
        console.error("Error:", error);
        this.eventSource.close();
        this.lodingAi = false;
      };
    },

    },
getDoc(v) {
      console.log(v);
    },
    // 停止输出
    stopMessage() {
        controller.abort();
        // 重新创建 AbortController 实例
        controller = new AbortController();
        signal = controller.signal;
 },
},
  created() {},
  beforeDestroy() {
    this.disconnect();
  },
};
</script>
<style lang="scss" scoped>
.container {
  padding: 5rpx 10rpx;
  padding-bottom: 180rpx;
  height: 100%;
  .icon {
    padding: 20rpx 20rpx;
    display: flex;
    align-items: flex-start;
    img {
      margin-right: 2px;
    }
    .model-titel {
      padding-top: 2px;
      flex: 1;
      display: flex;
      span {
        white-space: pre-wrap;
        padding: 5px 8px;
        letter-spacing: 2px;
        border-radius: 5px;
        display: flex;
        width: fit-content;
        background: #fff;
      }
    }
    .icon-title {
      width: 3em;
      text-align: left;
      padding-top: 2px;
    }
    .right {
      justify-content: flex-end;
      span{
        background: #1e90ff;
        color: #fff;
      }
    }
  }
}
.btn {
  position: fixed;
  z-index: 99;
  bottom: 0;
  width: 100%;
  height: 180rpx;
  padding: 10rpx 30rpx;
  background: #fff;
  .u-button {
  }
}
</style>
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容