vue接收stream流数据,实现AI智能回答效果

一、使用EventSOurce

EventSource 接口是 web 内容与服务器发送事件通信的接口。

const evtSource = new EventSource("服务器地址");

evtSource.onopen = (e) => {
 console.log('建立链接',e)
};
evtSource.onmessage = (e) => {
 console.log('接收消息',e)
};
evtSource.onerror= (error) => {
 console.log('错误信息',error)
};

备注: 你可以在 Github 上找到完整的示例——使用 PHP 语言的简单 SSE 示例

二、 使用@microsoft/fetch-event-source

1、安装

npm i @microsoft/fetch-event-source

2、使用

import { fetchEventSource } from '@microsoft/fetch-event-source';
const url = ref('/getMsg');
class RetriableError extends Error {}
class FatalError extends Error {}
const abortController = ref();
const eventSource = ref();
abortController.value = new AbortController();
const initFetchEventSource = (url) => {
  eventSource.value = fetchEventSource(url, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json'
    },

    signal: abortController.value.signal,
    asynconopen() {
      if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
        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(msg) {
      // if the server emits an error message, throw an exception
      // so it gets handled by the onerror callback below:
      // console.log('接收消息', msg);

      if (msg.event === 'FatalError') {
        throw new FatalError(msg.data);
      }
      if (msg.data.includes('DONE')) {
         abortController.value.abort(); // 结束会话
      } else {
        let res = JSON.parse(msg.data);
       console.log('接收消息',res)
      }
    },
    onclose() {
       console.log('关闭链接')
     abortController.value.abort(); // 结束会话
    },
    onerror(err) {
      console.log('抛出异常', err);
      abortController.value.abort(); // 结束会话
    }
  });

使用POST请求

const initFetchEventSource = (url) => {
  eventSource.value = fetchEventSource(url, {
    method: 'POSt',
    headers: {
      'Content-Type': 'application/json',
      'authorization': 'Bearer ' + getStorages(),
      
    },
    body: JSON.stringify({
      msg: message.value,
      modelId: detailObj.value?.id,
      temperature: sliderList.value[0].num,
      topPnum: sliderList.value[1].num,
      promptId: currentPrompt.value?.id
    }),
    signal: abortController.value.signal,
    asynconopen() {
      if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
        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(msg) {
      // if the server emits an error message, throw an exception
      // so it gets handled by the onerror callback below:
      // console.log('接收消息', msg);

      if (msg.event === 'FatalError') {
        throw new FatalError(msg.data);
      }
      if (msg.data.includes('DONE')) {
        closeEventSource();
      } else {
        let res = JSON.parse(msg.data);
       console.log('接收消息',res)
      }
    },
    onclose() {
       console.log('关闭链接')
     abortController.value.abort(); // 结束会话
    },
    onerror(err) {
     console.log('抛出异常', err);
      abortController.value.abort(); // 结束会话
    }
  });
};

示例

1736940217165.png
1736928560753.png

回显回答,仿照Ai 打字

let index = 0;
        messageObj.answer = '';
        timer.value = setInterval(() => {
          if (index < tempContent.value.length) {
            messageObj.answer = `${messageObj.answer}${tempContent.value[index]}`;
            index++;
            scrollToBottom();
          } else {
            clearInterval(timer.value);
            tempContent.value = '';
            scrollToBottom();
            closeEventSource();
            isAnswer.value = false;
          }
        }, 80);

所有代码

import { require } from '@/utils';
import { ref } from 'vue';
import Prompt from './prompt.vue';
import { useUserStore } from '@/store';
import empty from '@/assets/images/models/empty.png';
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { apiPath } from '@/config';
import { getStorages } from '@/utils';
const userStore = useUserStore();
const detailObj = ref();
const currentPrompt = ref();
const isAnswer = ref(false);
const contentRef = ref();

const conversations = ref([]);
const max = 5;
const abortController = ref();
abortController.value = new AbortController();
const tempContent = ref('');
const eventSource = ref();
const url = ref(`/getMsg`);
const timer = ref();
// 滑块
const handleInput = (id, value) => {};
const identy = ref(1);
const message = ref(null);
const setData = (obj = {}) => {
  detailObj.value = obj;
};
defineExpose({ setData });
const addNewLine = (event) => {
  console.log('event', event);
  if (event.ctrlKey && event.key === 'Enter') {
    event.preventDefault();
    message.value += '\n';
  } else {
    return;
  }
};
const onCloseAnswer = () => {
  if (timer.value) {
    clearInterval(timer.value);
  }

  closeEventSource();
  isAnswer.value = false;
};
const initFetchEventSource = (url) => {
  class RetriableError extends Error {}
  class FatalError extends Error {}

  // console.log('conversations.value', conversations.value);
  let list = conversations.value.map((item) => item.list);
  let realyList = [].concat(...list);
  let msgList = [];
  if (realyList.length > max) {
    let length = realyList.length;
    // console.log('length', length);
    for (let i = 0; i < length; i++) {
      // console.log('length', length, length - max + i);
      if (i < max) {
        msgList.push({
          'role': 'user',
          'content': realyList[length - max + i].question
        });
        msgList.push({
          'role': 'system',
          'content': realyList[length - max + i]?.answer || ''
        });
      } else {
        break;
      }
    }
  } else {
    for (let i = 0; i < realyList.length; i++) {
      msgList.push({
        'role': 'user',
        'content': realyList[i].question
      });
      msgList.push({
        'role': 'system',
        'content': realyList[i]?.answer || ''
      });
    }
  }

  // console.log('realyListlist==', realyList);
  eventSource.value = fetchEventSource(url, {
    method: 'POSt',
    headers: {
      'Content-Type': 'application/json',
     
    },
    body: JSON.stringify({
      msg: msgList,
      modelId: detailObj.value?.id,
      temperature: sliderList.value[0].num,
      topPnum: sliderList.value[1].num,
      promptId: currentPrompt.value?.id
    }),
    signal: abortController.value.signal,
    // openWhenHidden: true,
    async onopen(response) {
      // if (response.ok && response.headers.get('content-type') === 'EventStreamContentType') {
      //   return; // everything's good
      // } else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
      //   // client-side errors are usually non-retriable:
      //   console.log('response', response);
      //   throw new FatalError();
      // } else {
      //   throw new RetriableError();
      // }
    },

    onmessage(msg) {
      // if the server emits an error message, throw an exception
      // so it gets handled by the onerror callback below:
      // console.log('接收消息', msg);

      if (msg.event === 'FatalError') {
        throw new FatalError(msg.data);
      }
      if (msg.data.includes('DONE')) {
        // console.log('sss');
        let messageList = conversations.value[conversations.value.length - 1];
        let messageObj = messageList.list[messageList.list.length - 1];
        // console.log(' tempContent.value', tempContent.value);
        let index = 0;
        messageObj.answer = '';
        timer.value = setInterval(() => {
          if (index < tempContent.value.length) {
            messageObj.answer = `${messageObj.answer}${tempContent.value[index]}`;
            index++;
            scrollToBottom();
          } else {
            clearInterval(timer.value);
            tempContent.value = '';
            scrollToBottom();
            closeEventSource();
            isAnswer.value = false;
          }
        }, 80);
      } else {
        let res = JSON.parse(msg.data);
        let content = res.choices[0].delta.content;
        //.replace(/\n/g, '<br/>');
        tempContent.value = `${tempContent.value}${content}`;
      }
    },
    onclose() {
      closeEventSource();
      throw new FatalError();
    },
    onerror(err) {
      // console.log('err instanceof FatalError', err instanceof FatalError);
      if (err instanceof FatalError) {
        throw err; // rethrow to stop the operation
      } else {
        console.log('抛出异常', err);
        closeEventSource();
        isAnswer.value = false;
      }

      // eventSource.value.close();
    }
  }).catch((err) => {
    console.log('err', err);
  });
};
// 结束回答
const closeEventSource = () => {
  if (abortController.value) {
    abortController.value.abort(); // 结束会话
  }
  eventSource.value = null;

  // return;
};
// 发送数据
const onClickSend = () => {
  if (event.ctrlKey && event.key === 'Enter') {
    return;
  }

  if (!message.value.trim() || isAnswer.value) {
    return;
  }
  isAnswer.value = true;
  let messageObj = conversations.value[conversations.value.length - 1];
  let json = {
    userName: userStore.nickname,
    question: message.value,
    prompt: currentPrompt.value,
    answer: messageObj.answer || ''
  };
  // console.log('conversations.value', conversations.value.length);
  messageObj.list.push(json);
  nextTick(() => {
    scrollToBottom();
  });
  // console.log('detailObj', detailObj.value, currentPrompt.value);
  // console.log(conversations.value);
  initFetchEventSource(url.value);
  message.value = '';
};
const scrollToBottom = () => {
  contentRef.value.scrollTo(0, contentRef.value.scrollHeight);
};
const drawer = ref(false);
const onClickPrompt = () => {
  drawer.value = true;
};

// 使用模板
const promptUse = (value) => {
  currentPrompt.value = value;
  identy.value += 1;

  let obj = {
    promptName: currentPrompt.value.name,
    promptContent: currentPrompt.value.content,
    showPromptContent: false,
    list: []
  };
  conversations.value.push(obj);
  drawer.value = false;
};
const clearPrompt = () => {
  currentPrompt.value = null;
  let obj = {
    promptName: '系统模型回答',
    promptContent: '',
    list: []
  };
  conversations.value.push(obj);
};
const updateDrawer = (value: boolean) => {
  drawer.value = value;
};
const init = () => {
  conversations.value = [];
  // 物理添加 页面
  conversations.value.push({
    promptName: '系统模型回答',
    promptContent: '',
    showPromptContent: false,
    list: []
  });
};
onMounted(() => {
  init();
});
onUnmounted(() => {
  closeEventSource();
});
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容