当前位置: 首页 > article >正文

根据模型数据 处理流式数据 生成AI对话

技术栈 : react + js + ant - design (本人用的是react , vue也可以实现 , 在最后有重点方法的vue版)


实现效果如下 : 



1. 我专门封装了一个fetch.js来处理流式数据 , 流式数据返回如下

import { FETCH_DIFY_API_REQUEST } from '@/utils/fetch.js'

// 流式内容返回
const postChatMessage = params => {
  return FETCH_DIFY_API_REQUEST.post(`/chat-messages`, params, {
    responseType: 'stream',
    signal: params?.signal
  })
}
export { postChatMessage }

-------- 以下为 @/utils/fetch.js 的内容 .env.VITE_DIFY_API_URL就需自己配置了 --------------


import Cookies from 'js-cookie'

class FetchService {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }
  /**
   * 发起 GET 请求
   * @param {string} path - API 路径
   * @param {object} [options] - 可选配置项
   * @returns {Promise<Response>}
   */
  get (path, params, options = {}) {
    return this.Fetch(`${this.baseUrl}${path}`, params, { method: 'GET', ...options }
    );
  }

  /**
   * 发起 POST 请求
   * @param {string} path - API 节点路径
   * @param {object} [options] - 可选配置项
   * @returns {Promise<Response>}
   */
  post (path, params, options = {}) {
    return this.Fetch(`${this.baseUrl}${path}`, params, { method: 'POST', ...options });
  }

  /**
   * 发起 PUT 请求
   * @param {string} path - API 路径
   * @param {object} [options] - 可选配置项
   * @returns {Promise<Response>}
   */
  put (path, params, options = {}) {
    return this.Fetch(`${this.baseUrl}${path}`, params, { method: 'PUT', ...options, });
  }

  /**
   * 发起 DELETE 请求
   * @param {string} path - API 路径
   * @param {object} [options] - 可选配置项
   * @returns {Promise<Response>}
   */
  delete (path, params, options = {}) {
    return this.Fetch(`${this.baseUrl}${path}`, params, { method: 'DELETE', ...options });
  }
  /**
    * 发起通用的 fetch 请求
    * @param {string} url - 请求 URL
    * @param {object} [options] - 可选配置项
    * @returns {Promise<Response>}
    */
  async Fetch (url, params, options = {}) {
    const response = await fetch(url,
      {
        body: JSON.stringify(params),
        headers: {
          Authorization: "Bearer " + Cookies.get('token'),
          "Content-Type": "application/json",
        },
        ...options,
      }
    );
    if (!response.ok) {
      throw new Error(`Request failed with status ${response.status}`);
    }
    return response;
  }

}

const FETCH_DIFY_API_REQUEST = new FetchService(import.meta.env.VITE_DIFY_API_URL);
export { FETCH_DIFY_API_REQUEST }


2. 导入接口和获取流式数据内容 方法如下 

  const [chatList, setChatList] = useState([])
  const [conversationId, setConversationId] = useState('')
  const [isStep, setIsStep] = useState(false)
  const [isSelect, setIsSelect] = useState(false)
  const [referInfo, setReferInfo] = useState({ referList: [] })
  const [userId, setUserId] = useState('user-123') // 假设的用户ID
  const [disabled, setDisabled] = useState(false)
  const [loading, setLoading] = useState(false)
  const [abortController, setAbortController] = useState(null)

// 点击发送的事件,该方法大同小异也可以转成适用于vue的方法  
const sendHandle = async () => {
    try {
      if (!inputValue.trim()) return
      setDisabled(true)

      if (abortController) {
        abortController.abort() // 如果已有请求,先取消它
      }

      // 创建一个新的 AbortController 实例
      const newAbortController = new AbortController()
      setAbortController(newAbortController)

      if (chatList.some(item => item.loading)) {
        message.warning('正在搜索,请稍等...')
        return
      }

      const userObj = { role: 'user', loading: false, content: inputValue }
      const aiObj = {
        role: 'assistant',
        loading: true,
        content: '',
        finished: false,
        contenteditable: false
      }
      setChatList(prevChatList => [...prevChatList, userObj, aiObj])
      setLoading(true) // 设置加载状态为 true

      const refs =
        referInfo.referList.map(item => item.content).join(';\n') || ''
      const params = {
        inputs: {},
        query: isStep ? inputValue + '大纲' : inputValue,
        refs: isSelect ? refs : '',
        conversation_id: conversationId,
        response_mode: 'streaming', // blocking, streaming
        user: userId
      }

      const response = await postChatMessage({
        ...params,
        signal: newAbortController.signal
      })

      setChatList(prevChatList => {
        const newChatList = [...prevChatList]
        newChatList[newChatList.length - 1].loading = false
        return newChatList
      })

      const reader = response.body.getReader()
      const decoder = new TextDecoder('utf-8')
      let result = true
      let contenteditable = false

      while (result) {
        const { done, value } = await reader.read()
        if (done) {
          setDisabled(false)
          setLoading(false) // 设置加载状态为 false
          setChatList(prevChatList => {
            const newChatList = [...prevChatList]
            newChatList[newChatList.length - 1].finished = true
            return newChatList
          })
          break
        }
        const chunk = decoder.decode(value)

        const lines = chunk.split('\n')
        for (let line of lines) {
          if (line.startsWith('data: ')) {
            if (isJsonString(line.substring(6))) {
              const jsonData = JSON.parse(line.substring(6))
              if (jsonData.event === 'error') {
                setLoading(false) // 设置加载状态为 false

                setChatList(prevChatList => {
                  const newChatList = [...prevChatList]
                  newChatList[newChatList.length - 1].content =
                    '没有找到您想要的答案,请您稍后再试哦~~~'
                  newChatList[newChatList.length - 1].loading = false
                  newChatList[newChatList.length - 1].finished = true
                  return newChatList
                })
                return
              }
              if (jsonData.answer === 'outline') {
                contenteditable = true
              }
              setConversationId(jsonData.conversation_id)
              setChatList(prevChatList => {
                const newChatList = [...prevChatList]
                newChatList[newChatList.length - 1].contenteditable =
                  contenteditable
                let content =
                  (jsonData.answer &&
                    jsonData.answer.replace(/\n+/g, '<br>')) ||
                  ''
                if (content.indexOf('outline') > -1) {
                  content = content.replace('outline', '')
                }
                newChatList[newChatList.length - 1].content += content
                return newChatList
              })
            }
          }
        }

        setInputValue('') // 清空输入框
      }
    } catch (error) {
      setDisabled(false)
      setLoading(false) // 设置加载状态为 false
      message.error(error.message)
      setChatList(prevChatList => {
        const newChatList = [...prevChatList]
        newChatList[newChatList.length - 1].content =
          '没有找到您想要的答案,请您稍后再试哦~~~'
        newChatList[newChatList.length - 1].loading = false
        newChatList[newChatList.length - 1].finished = true
        return newChatList
      })
    }
  }

  // 取消请求的事件
  const handleCancelClick = () => {
    if (abortController) {
      abortController.abort() // 取消请求
      setAbortController(null) // 清除控制器
    }
    setLoading(!loading)
  }


2.1 以下是重点方法sendHandle的vue版本适用的方法
 

const sendHandle = async () => {
  try {
    if (!form.prompt) return;
    disabled.value = true;
    Cookies.set('token', chatToken);
    const isLoading = chatList.value.some(item => item.loading);
    if (isLoading) return proxy.$message.warning('正在搜索,请稍等...');
    let userObj = { role: 'user', loading: false, content: form.prompt };
    let aiObj = { role: 'assistant', loading: true, content: '', finished: false, contenteditable: false };
    chatList.value.push(userObj, aiObj);
    const refs = referInfo.value.referList.map(item => item.content).join(';\n') || '';
    const params = {
      inputs: {},
      "query": isStep.value ? form.prompt + '大纲' : form.prompt,
      "refs": isSelect.value ? refs : '',
      "conversation_id": conversation_id.value,
      "response_mode": "streaming",//blocking, streaming
      "user": userId
    }
    const response = await postChatMessage(params);
    chatList.value[chatList.value.length - 1].loading = false;
    const reader = response.body.getReader();
    const decoder = new TextDecoder('utf-8');
    let result = true;
    let contenteditable = false;

    while (result) {
      const { done, value } = await reader.read();
      if (done) {
        result = false;
        disabled.value = false;
        chatList.value[chatList.value.length - 1].finished = true;
        break;
      }
      const chunk = decoder.decode(value);

      // 处理以 data: 开头的数据
      const lines = chunk.split('\n');
      for (let line of lines) {
        if (line.startsWith('data: ')) {
          if (isJsonString(line.substring(6))) {
            const jsonData = JSON.parse(line.substring(6));
            // console.log(jsonData);
            if (jsonData.event == 'error') {
              chatList.value[chatList.value.length - 1].content = '没有找到您想要的答案,请您稍后再试哦~~~';
              chatList.value[chatList.value.length - 1].loading = false;
              chatList.value[chatList.value.length - 1].finished = true;
              return
            }
            if (jsonData.answer == "outline") {
              contenteditable = true;
            }
            conversation_id.value = jsonData.conversation_id;
            chatList.value[chatList.value.length - 1].contenteditable = contenteditable;
            let content = jsonData.answer && jsonData.answer.replace(/\n+/g, '<br>') || '';
            if (content.indexOf('outline') > -1) {
              content = content.replace('outline', '')
            }
            // console.log(content);
            chatList.value[chatList.value.length - 1].content += content;

          }
        }
      }
    }
  } catch (error) {
    // console.log(error);
    disabled.value = false;
    proxy.$message.error(error.message);
    chatList.value[chatList.value.length - 1].content = '没有找到您想要的答案,请您稍后再试哦~~~';
    chatList.value[chatList.value.length - 1].loading = false;
    chatList.value[chatList.value.length - 1].finished = true;
  }
}



总结 : 

流式数据处理 + 地址配好token加上 + 重点方法 = 成功处理流式数据

优化项 : 

中止请求 (文中有)  + 样式的优化调整





 


http://www.kler.cn/a/392504.html

相关文章:

  • MySQL系列之如何在Linux只安装客户端
  • 【OceanBase 诊断调优】—— ocp上针对OB租户CPU消耗计算逻辑
  • 32位、64位、x86与x64:深入解析计算机架构
  • Pycharm PyQt5 环境搭建创建第一个Hello程序
  • Linux 进程线程间通信总结
  • Gurobi学术版+Anaconda安装步骤
  • [运维][Nginx]Nginx学习(1/5)--Nginx基础
  • QTableWidget的简单使用
  • Swift 开发教程系列 - 第11章:内存管理和 ARC(Automatic Reference Counting)
  • Redhat8.6安装MySQL8.0.31
  • 在启动 Spring Boot 项目时,报找不到 slf4j 的错误
  • openresty入门教程:access_by_lua_block
  • windows环境下手工创建oracle数据库监听
  • kafka生产消费问题
  • ffmpeg内存模型
  • 【go从零单排】go中的range的用法
  • 【原创】java+ssm+mysql美食论坛网系统设计与实现
  • macOS 应用公证指南:使用 fastlane 实现自动化公证流程
  • 网络安全之信息收集
  • AlphaFold3中文使用说明
  • 11月上海月赛解报告(丙组)T5
  • Castle.DynamicProxy的NET Core和Framework的AOP实施
  • 15 个改变世界的开源项目:塑造现代技术的先锋力量
  • 在 .NET 6.0 中创建用于 CRUD 操作的 Web API
  • 上河AI上线ComfyUI工作台
  • 如何使用OpenCV和Python进行相机校准