SpringAI打造自己的AI应用流式响应(前端篇)

继上一篇文章SpringAI打造自己的AI应用(入门篇),我们已经使用SpringAI完成了项目搭建,并完成了模型调用接口,接下来我们开始使用前端来实现调用接口。

1.环境装备

在开始项目之前我们先装备如下环境

- 1. Node.js 16或更高

- 2. VUE3.x

⚠️:后台需要跨域,前端使用devServer配置代理的话会影响流式效果。

2.功能实现与分析(主要是流式输出打字风格的实现)

方案1: SSE:

Server-Sent Events(SSE)是一种用于在Web应用程序中从服务器向浏览器推送实时更新的技术。与WebSockets相比,SSE是单向的,意味着数据流只能从服务器发送到客户端,而不是双向通信。因为AI对话刚好需要服务器推送到客户端的场景,所及这个方案比较适合。对于前端来说,使用SSE一般使用自带的EventSource或者微软提供的@microsoft/fetch-event-source库来实现。

EventSource 说明:
优点:
  • 简单易用,与 HTTP 协议兼容。
  • 只需要一个长连接,服务器可以推送任意数量的事件。
  • 适用于服务端向客户端发送频率较低的数据。
  • 可以自动重连,并且在连接断开时会触发 error 和 close 事件,方便处理异常情况。
缺点:
  • 不支持双向通信。
  • 不支持二进制数据传输。
  • 兼容性存在问题,不支持 IE 浏览器。
  • 不支持POST请求

使用示例

ini 复制代码
let source = new EventSource("url");  
 
source.onmessage = function (event) {
     //收到响应数据
}

⚠️:这里需要注意使用EventSource 需要我们后台接口返回一个结束标识符号,需要在前端判断结束断开,不然就是重复请求。 后台接口加标识比如:[done]

less 复制代码
@CrossOrigin  
@GetMapping(value = "/api/stream")  
public Flux<String> stream(String prompt){  
  
  
final Flux<String> content = ollamaChatModel.stream(prompt);  
  
return content.concatWith(Flux.just("[end]"));
 }

前端改造后

ini 复制代码
let source = new EventSource("url");  
 
source.onmessage = function (event) {
     //收到响应数据
     
     //收到结束标识主动断开
     if(event.data==='[done]'){
      
      source.close()
     }
}

fetch-event-source 微软提供的封装库:

优点:
  • 简化使用:fetch-event-source 封装了 SSE 的复杂性,提供了更简洁的 API 进行连接和处理。
  • 自动重连:库内置了自动重连机制,方便开发者处理网络不稳定的问题。
  • 灵活性:支持自定义事件和消息处理,适应不同应用场景。
  • 轻量级:相比 WebSocket,SSE 使用更少的资源和带宽,适合轻量级实时数据更新。
  • 兼容性:与标准 EventSource API 兼容,易于集成到现有项目中。
  • 支持POST

缺点:

  • 单向通信:和标准 SSE 一样,只能从服务器向客户端发送数据,不支持双向通信。
  • 仅支持文本数据:无法原生处理二进制数据,需要进行编码转换(如 Base64)。
  • 连接限制:浏览器对同一源的并发 SSE 连接数有限制(通常为 6 个)。
  • 跨域限制:需要正确配置 CORS 才能跨域使用。
  • 浏览器兼容性:依赖浏览器支持 EventSource,在不支持的浏览器上需要 polyfill。

安装: npm install @microsoft/fetch-event-source --save

示例代码

vbnet 复制代码
let data = new FormData()  
data.append("query", "输入的问题")  
fetchEventSource("url", {  
method:"POST",   
body: data,  
onmessage:(event=>{  
//结果
console.log(event)  
 
})  
})

方案2: fetch

fetch 是现代 JavaScript 中用于执行网络请求的内置 API,提供了一种简单、灵活且基于 Promise 的方式来替代传统的 XMLHttpRequest。它是现代 Web 应用开发中非常重要的工具。

优点
  • 现代化:fetch 是现代浏览器的标准 API,语法简洁。
  • Promise 支持:基于 Promise,方便链式处理异步操作。
  • 灵活性:支持各种 HTTP 方法和请求设置。
缺点
  • 不支持超时:原生不支持请求超时,需要自行实现。
  • 不支持上传进度监听。
  • 错误处理复杂:只对网络错误抛出异常,HTTP 错误需手动检查响应状态。
  • 浏览器兼容性:在较老的浏览器中需要 polyfill。

示例代码

javascript 复制代码
let response = await fetch("url")  
 
const reader = response.body.getReader();  
  
// 读取流数据  
const decoder = new TextDecoder('utf-8'); // 用于将 Uint8Array 转换为字符串  
let done, value;  
while ({ done, value } = await reader.read()) {  
   
const chunk = decoder.decode(value, { stream: true });  
  //获取流式数据
 console.log(chunk)
if(done){  
break  
}  
  
}

方案3 axios

Axios 是一个基于 Promise 的 JavaScript 库,用于在浏览器和 Node.js 中进行 HTTP 请求。它是一个简洁且易于使用的工具,被广泛应用于前端开发中,用于与服务器进行数据交互,使用Vue生态对这个就不陌生了。 网上搜索了很多实现流都是在node.js环境中,在前端使用response.data.on监听是不行的,那么还有什么办法实现了,可以使用下载进度条监听回调来实现。

安装: npm install axios --save

示列代码

csharp 复制代码
axios.get("url", {  
 onDownloadProgress:(e=>{  
      //流式响应结果
     console.log( e.event.target.responseText )
 })  
 });

虽然也可以实现类似效果,但体验不是那么好,返回的内容不是一个一个来的,而是自动拼接成一个结果的,如:

复制代码
 你
 你好
 你好吗
 你好吗?

方案4 Websocket

WebSocket 是一种在单个 TCP 连接上提供全双工通信的协议,它使得客户端和服务器之间进行实时交互变得更加容易。它是一种标准化的通信协议,客户端和服务器都可以通过它发送消息。

优点:

  • 支持双向通信,客户端和服务端都可以发送和接收消息;
  • 可以发送二进制数据,支持大文件传输;
  • 协议比较轻量级,能够节省网络带宽和服务器资源;
  • 兼容性较好,大部分现代浏览器都支持 WebSocket。

缺点:

  • 需要在服务端实现 WebSocket 协议的支持;
  • 相对于 HTTP 请求来说,WebSocket 连接需要占用更多的服务端资源;
  • 安全性问题:需要注意防止 CSRF 和 XSS 攻击,避免恶意用户利用 WebSocket 劫持会话或注入脚本等。

这种方案会比较麻烦,需要后端实现websocket服务器,然后ai调用返回数据后使用后台给前端websocket推送数据,这种代码网上很多我就不去实现了。

完整演示代码

xml 复制代码
<script setup>  
import { ref } from "vue";  
import axios from "axios";  
  
import { fetchEventSource } from "@microsoft/fetch-event-source";  
  
const message = ref("");  
const result = ref("")  
  
  
function send(){  
  
result.value = "思考中...."  
  
// useFetch()  
  
  
useMFetch()  
  
}  
  
  
function useMFetch(){  
  
let data = new FormData()  
data.append("query", message.value)  
fetchEventSource("http://localhost:8050/api/responseStream", {  
method:"POST",  
mode:'cors',  
body: data,  
onmessage:(event=>{  
console.log(event)  
result.value += event.data  
})  
})  
  
  
}  
  
function useAxios(){  
  
axios.get("http://localhost:8050/api/stream?conversationId=1&query=" + message.value, {  
onDownloadProgress:(e=>{  
  
result.value = e.event.target.responseText  
  
})  
});  
  
  
}  
  
/* eslint-disable */  
async function useFetch(){  
  
let response = await fetch("http://localhost:8050/api/stream?conversationId=1&query=" + message.value)  
result.value=''  
message.value=''  
const reader = response.body.getReader();  
  
// 读取流数据  
const decoder = new TextDecoder('utf-8'); // 用于将 Uint8Array 转换为字符串  
let done, value;  
while ({ done, value } = await reader.read()) {  
// value 是一个 Uint8Array,表示数据  
const chunk = decoder.decode(value, { stream: true });  
  
result.value +=chunk  
if(done){  
break  
}  
  
}  
  
}  
  
/* eslint-disable */  
function sse(){  
let source = new EventSource("http://localhost:8050/api/stream?conversationId=1&query=" + message.value);  
message.value = ''  
source.onmessage = function (event) {  
  
if(event.data === "[end]"){  
console.log('结束')  
source.close()  
}else{  
if(result.value==='思考中....'){  
result.value = ''  
}  
result.value += event.data  
  
}  
  
}  
}  
  
  
  
</script>  
  
<template>  
  
<div style="padding: 50px">  
<div id="content">  
{{result}}  
</div>  
<div style="display: flex;gap: 10px;margin-top: 200px;">  
<input type="text" v-model="message" style="width: 100%" />  
<button @click="send" style="flex-shrink: 0">发送</button>  
</div>  
</div>  
</template>  
  
<style scoped>  
  
</style>

3.总结

通过本文,我们了解了如何实现流式响应效果以及常用的方案有哪些优缺点等。看似简单但必须要自己去实现了,才能理解里面的坑以及各种技术的优缺点。希望这篇文章可以帮到有需要的人吧,有什么不好的地方欢迎大家评判指正。

相关推荐
诗书画唱1 分钟前
【前端面试题】JavaScript 核心知识点解析(第二十二题到第六十一题)
开发语言·前端·javascript
excel7 分钟前
前端必备:从能力检测到 UA-CH,浏览器客户端检测的完整指南
前端
前端小巷子14 分钟前
Vue 3全面提速剖析
前端·vue.js·面试
悟空聊架构21 分钟前
我的网站被攻击了,被干掉了 120G 流量,还在持续攻击中...
java·前端·架构
CodeSheep22 分钟前
国内 IT 公司时薪排行榜。
前端·后端·程序员
尖椒土豆sss26 分钟前
踩坑vue项目中使用 iframe 嵌套子系统无法登录,不报错问题!
前端·vue.js
遗悲风27 分钟前
html二次作业
前端·html
江城开朗的豌豆30 分钟前
React输入框优化:如何精准获取用户输入完成后的最终值?
前端·javascript·全栈
CF14年老兵31 分钟前
从卡顿到飞驰:我是如何用WebAssembly引爆React性能的
前端·react.js·trae
画月的亮34 分钟前
前端处理导出PDF。Vue导出pdf
前端·vue.js·pdf