基于uniapp/vue 通过实现ai 对话 fetch

<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;
      try {
        // 通过try cath捕获终止请求
        const resp = await fetch(
          configService.aiUrl + "/v1/api/completion",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              Accept: ["text/event-stream", "application/json"],
              Authorization: this.aiToken,
            },
            signal,
            body: JSON.stringify({
              conversation_id: "aedc12748b6211efa8234e3599f53586",
              messages: [
                {
                  content: this.userMessage,
                  role: "user",
                },
              ],
            }),
          }
        );
        this.lodingAi = true; // 显示加载中
        this.listData.push({
          // 存储会话记录
          oldUserMessage: this.userMessage,
          messages: "",
          idx: this.msgIndex++,
        });
        this.userMessage = ""; // 保存后清空上次记录
        // 处理流数据
        const reader = resp.body.getReader(); //读取返回结果的流数据方法,内置 .read()方法
        const decoder = new TextDecoder("utf-8"); //解码器
        while (true) {
          //解构读取返回数据,在流式接口中,done 为请求结束,value 是未解码前的二进制数据
          const { done, value } = await reader.read();
          if (done) {
            break;
          }

          const str = decoder.decode(value); //二进制解码
          const text = str?.split("data:")[1] || "";
          let textJson = null;
          // 防止解析报错
          try {
            textJson = JSON.parse(text);
          } catch (e) {
            // console.log("解析 JSON 报错:", e);
          }
          if (!textJson?.data?.answer) {
            // 数据读取完
            this.lodingAi = false;
            this.getDoc(this.listData[this.listData.length - 1]);
            return;
          } else {
            // 更新会话记录
            this.listData[this.listData.length - 1].messages =
              textJson.data.answer;
            this.scrollToBottom(); // 更新滚动条到最底部
            // 储存对应文档以及文档名称
            const doc_aggs = textJson?.data?.reference?.doc_aggs || [];
            if (doc_aggs.length > 0) {
              this.listData[this.listData.length - 1].docAggs = {
                docId: doc_aggs[0].doc_id,
                docName: doc_aggs[0].doc_name,
              };
            }
          }
        }
      } catch (error) {
        if (error.name === "AbortError") {
          this.lodingAi = false;
          console.log("Ai请求已被中止");
        }
      }
    },
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辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容