chatgpt 逐字输出 使用fetch/eventSource/fetchEventSouce进行sse流式处理

chatgpt 逐字输出 使用fetch/eventSource/fetchEventSouce进行sse流式处理_第1张图片
前端使用vue

1.逐字输出 闪动css样式

<span id="response_row" class="result-streaming">{{ item.assistantContent }}span>
.result-streaming:after {
  -webkit-animation: blink 1s steps(5, start) infinite;
  animation: blink 1s steps(5, start) infinite;
  content: "▋";
  margin-left: 0.25rem;
  vertical-align: baseline;
}

2.使用fetch/eventSource/fetchEventSource进行sse流式处理

先贴最后成功使用的
使用fetchEventSource方法
参考代码:https://blog.csdn.net/cuiyuchen111/article/details/129468291
参考/下载文档:https://www.npmjs.com/package/@microsoft/fetch-event-source?activeTab=readme

以下为后端接口要求
在这里插入图片描述
前端代码

<p v-if="item.requestFlag" class="content robot_content"><span id="response_row" class="result-streaming">{{ item.assistantContent }}span>p>
<p class="content robot_content"><span v-html="item.assistantContent">span>p>
 async getResponseFromAPI() {
          const that = this;
          this.sendLoading = true;
          // 用户提问时间
          let userTime = that.getNowTime();
          const form = JSON.parse(JSON.stringify(this.form));
          console.log(form, "请求里的form");
          //物理添加 页面
          that.conversations.push({
            userContentId: "",
            userContent: form.prompt,
            userContentDatetime: userTime,
            assistantContentId: "",
            assistantContent: "",
            assistantContentDatetime: userTime,
            requestFlag: true,
          });
          // 对话请求
          const abortController = new AbortController();
          let formData = new FormData();
          formData.append("chatid", this.currentChatId);
          formData.append("clientid", form.clientid);
          formData.append("prompt", form.prompt);
          const url = "xxxxx";
          const headers = new Headers();
          const body = formData;
          const eventSource = fetchEventSource(url, {
            method: "POST",
            headers,
            body,
            signal: abortController.signal,
            onmessage(e) {
              that.handleScrollBottom();
              that.form.prompt = "";
              const response_row = document.getElementById("response_row");
              console.log(e.data);
              let res = JSON.parse(e.data);
              let index = that.conversations.length - 1;
              if (res.message == "[DONE]") {
                res.data = JSON.parse(res.data);
                console.log(res.data);
                let obj = {
                  userContentId: res.data.userContentId,
                  userContent: res.data.userContent,
                  userContentDatetime: userTime,
                  assistantContentId: res.data.assistantContentId,
                  assistantContent: res.data.assistantContent,
                  assistantContentDatetime: that.getNowTime(),
                  requestFlag: false,
                };
                console.log(obj);
                that.$set(that.conversations, index, obj);
                that.sendLoading = false;
                abortController.abort();
                eventSource.close();
                console.log("我是结束!!");
              } else {
                var content = res.data;
                response_row.innerText += content;
                // console.log(content)
                // if (content.includes("[ENTRY]")) {
                //   content = content.replaceAll("[ENTRY]", "\n");
                // }
              }
            },
            onclose() {
              console.log("close");
              that.sendLoading = false;
              abortController.abort();
              eventSource.close();
            },
            onerror(error) {
              let index = that.conversations.length - 1;
              that.conversations.splice(index, 1);
              that.sendLoading = false;
              console.log("error", error);
              abortController.abort();
              eventSource.close();
            },
          });
        }

遇到的问题:
1.只调用一次事件 但fetch请求发送了两次或多次且终止失败

//按照fetchEventSource文档内的写法 请求暂停无效
const ctrl = new AbortController();
fetchEventSource('/api/sse', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        foo: 'bar'
    }),
    signal: ctrl.signal,
});

//而后看到一种说法,fetchEventSource是针对EventSource API的,而不是xhr或fetch API
//因此定义EventSource存储接口所返回的数据 使用EventSource的暂停方法 =》 fetchEventSource暂停成功
const eventSource = fetchEventSource(url, {
            method: "POST",
            headers,
            body,
            signal: abortController.signal,
            onmessage(e) {
                eventSource.close();
            },
            onclose() {
              eventSource.close();
            },
            onerror(error) {
              eventSource.close();
            },
          });

以下为fetch/eventSource使用过程中遇到的问题

1.使用fetch方式进行sse流式处理
优点:可以使用post请求
缺点:获取到的数据处理困难 获取事件返回格式或有错误
参考代码:https://blog.csdn.net/betterAndBetter_/article/details/129900233
     http://681314.com/A/YaHyYpjoPF

async function send() {
    const input = document.getElementById("input").value;
    const output = document.getElementById("output");
    output.innerText = "";
    const url = "/api/stream";
    const data = { "Prompt": input };
//直接获取 Fetch 的response, 无法使用 await的话, Promise的方式也是可以的。
    const response = await fetch(url, {
        method: "POST",
        body: JSON.stringify(data),
        headers: {
            "Content-Type": "application/json"
        }
    })
	//获取UTF8的解码
    const encode = new TextDecoder("utf-8");
	//获取body的reader
	  const reader = response.body.getReader();
	// 循环读取reponse中的内容
    while (true) {
        const { done, value } = await reader.read();
        if (done) {
            break;
        }
	// 解码内容
        const text = encode.decode(value);
	// 当获取错误token时,输出错误信息
        if (text === "") {
            output.innerText = "Error";
            break;
        } else {
	// 获取正常信息时,逐字追加输出
            output.innerText += text;
        }
    }
}

【记得补截图】

2.使用eventSource进行sse流式处理
优点:获取到的数据格式规范 易处理
缺点:无法使用post请求
参考b站视频:https://www.bilibili.com/video/BV1QA411C7mN/?spm_id_from=333.880.my_history.page.click&vd_source=384646ea9baa6985ceb5331bff5b87b0

var rsource = (this.rsource = new EventSource(
        `/api/chat/repeat/${this.cid}`
      ));
      rsource.addEventListener("open", function () {
        console.log("connect");
      });

      //如果服务器响应报文中没有指明事件,默认触发message事件
      rsource.addEventListener("message", function (e) {
        console.log(`resp:(${e.data})`);
        var rconv = that.conversation[that.conversation.length - 1];
        if (e.data == "[DONE]") {
          rsource.close();

          rconv["loading"] = false;
          that.convLoading = false;
          that.refrechConversation();
          that.rsource = undefined;
          return;
        }

        var content = e.data;
        if (content.includes("[ENTRY]")) {
          content = content.replaceAll("[ENTRY]", "\n");
        }

        // 滚动到最下面
        that.handleScrollBottom();

        var idx = rconv.idx;
        rconv["speeches"][idx] += content;
        that.refrechConversation();
      });

      //发生错误,则会触发error事件
      rsource.addEventListener("error", function (e) {
        console.log("error:" + e.data);
        rsource.close();
        that.rsource = undefined;
      });

由于eventSource获取到的数据比fetch流畅许多,所以研究过eventSource能否使用post请求,使用过以下代码,但失败了
chatgpt 逐字输出 使用fetch/eventSource/fetchEventSouce进行sse流式处理_第2张图片
3.fetch和eventSource同时使用
优点:可以很顺利的请求并且获取到数据
缺点:fetch支持post eventSource不支持post 对接口请求方式有要求 几乎不太能兼容

// 获取表单元素
const form = document.querySelector('#my-form');

// 监听表单提交事件
form.addEventListener('submit', (event) => {
  event.preventDefault(); // 阻止默认提交行为

  const formData = new FormData(form); // 创建 FormData 对象

  // 发送 POST 请求并接收 SSE 流式输出
  fetch('/api/submit-form', {
    method: 'POST',
    body: formData
  }).then((response) => {
    // 如果请求成功,则创建 EventSource 对象监听 SSE 输出
    if (response.ok) {
      const eventSource = new EventSource('/api/stream');

      eventSource.onmessage = (event) => {
        const data = JSON.parse(event.data);
        console.log(data); // 处理接收到的数据
      };

      eventSource.onerror = (error) => { // 监听错误事件
        console.error(error);
      };
    }
  }).catch((error) => {
    console.error(error);
  });
});

你可能感兴趣的:(chatgpt,javascript,vue,前端)