创新实训(十) 项目开发——sse打字机效果前端实现

后端的同学已经做好了后端的SSE,并给出了我们选型的原因:报告。我就不再赘述,主要还是从前端的实现来阐述。

EventSource

SSEServer-Sent Events,服务器发送事件)是一种实现服务器到客户端单向实时通信的技术。它允许服务器在有新数据可用的任何时候向浏览器推送信息,而不需要浏览器不断地询问服务器是否有新数据。这是通过在客户端和服务器之间建立一个持久的连接来实现的,服务器通过这个连接推送数据。

EventSource 是一个JavaScript接口,它为服务器发送事件提供了一种简单的实现方式。使用 EventSource,开发者可以非常容易地创建一个连接到服务器的客户端,并监听服务器推送的事件。当服务器发送一个事件时,EventSource 对象会触发一个事件,开发者可以监听这个事件并相应地处理。

在SSE中,浏览发送一个请求给服务端,通过响应头中的Content-Type:text/event-stream等向客户端声名这是一个长连接,发送的是流数据,这样客户端就不会关闭连接,一直等待服务端发送数据。

注意,sse本身是不支持post的方式,可以通过fetch的方式完成post相关操作。

post方式

使用post方式实现的大概格式如下:

// 创建一个AbortController实例,用于在未来某个时刻取消fetch请求
const controller = new AbortController();

// 获取由AbortController实例控制的信号对象
const signal = controller.signal;

// 使用fetchEventSource发起一个到指定URL的POST请求,用于建立SSE连接
fetchEventSource(`${url}`, {
  method: 'POST',           // 请求方法为POST
  signal: signal,           // 将信号对象传递给请求,以便能够取消请求
  headers: {                // 设置请求头
    'Content-Type': 'application/json',  // 请求内容类型为JSON
  },
  body: JSON.stringify(message),  // 请求体,将message对象转换为JSON字符串
  onmessage(msg) {           // 定义收到消息时的回调函数
    // 当服务器发送消息时,这里会接收到消息并处理
  },
  onerror(err) {             // 定义出错时的回调函数
    throw err;              // 如果发生错误,抛出这个错误
    // 抛出错误将中断fetchEventSource的处理流程
  }
});

onmessage回调函数不触发

具体实现时,我发现log的onmessage消息全部为空。但是使用apipost测试后端接口可以得到流式输出。

进一步查看network,我发现其中的response中有响应,格式如下:(因为返回的是字符串且默认解码方式为utf-8所以没有成功解码)

{"docs": ["<span style='color:red'>\u672a\u627e\u5230\u76f8\u5173\u6587\u6863,\u8be5\u56de\u7b54\u4e3a\u5927\u6a21\u578b\u81ea\u8eab\u80fd\u529b\u89e3\u7b54\uff01</span>"]}

{"text": "\u60a8\u597d", "message_id": "148994c7260445e9a23736808658b4fd"}

但是EventStream中全部为空。

查看响应标头,content-type没有问题:
image-20240603195141384

使用onopen方法,控制台输出一切正常:

async onopen(response) {
            console.log(response);
            if (response.ok && response.headers.get('content-type') === 'text/event-stream') {
              console.log(response);
                return; 
            } else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
                // client-side errors are usually non-retriable:
                console.log("回应错误")
            } else {
                // throw new RetriableError();
            }
        },

说明只有onmessage的实现有问题。

查阅资料,我发现原因是因为SSE规定了schema,如果不符合下面的格式,前端无法解析数据

SSE规定的schema

event: message\n
data: {数据}\n\n

以上为纯字符串,同时message也可以是其他消息,不会影响onmessage回调函数。

可以发现后端的返回格式并不如我们所愿,更改后端返回的代码如下:

# event_data = json.dumps(data)
event_data = json.dumps({"data":data})

# yield f"{event_data}\n\n"
yield f"event: message\ndata: {event_data}\n\n"

重启后端,重新运行,将onmessage中的msg log出来,结果如下:

image-20240603200027638

再查看EventStream,已经成功解析出来了:

image-20240603200049358

聊天打字机输出

可以看到后端返回的消息中,其中的 Unicode字符未被正确转义,首先需要使用JSON.parse() 方法来解码它。

const parsedData = JSON.parse(msg.data);

查看data的格式,分为两种,一种里面为完整的docs文本,另一种是需要拼接的text文本。

event: message
data: {"data": {"docs": ["<span style='color:red'>\u672a\u627e\u5230\u76f8\u5173\u6587\u6863,\u8be5\u56de\u7b54\u4e3a\u5927\u6a21\u578b\u81ea\u8eab\u80fd\u529b\u89e3\u7b54\uff01</span>"]}}

event: message
data: {"data": {"text": "\u5468", "message_id": "d67aac633ac0456f9945d3d0c5f20f45"}}

对于docs,我直接将其添加到aichat中,但是注意要将其中的换行符切换为<br>

let aiCurrentChatDocs = JSON.stringify(parsedData.data.docs);
aiCurrentChatDocs = aiCurrentChatDocs.replace(/\n/g, '<br>');
// 将ai回复加入list
AIReplay('参考:'+ aiCurrentChatDocs);

对于text,我使用resultAnswer拼接目前为止得到的text的字符串。

然后找到msgList中最后一条ai的消息(也就是消息内容和resultAnswer相等的消息),如果没有的话,说明当前是接收到的第一个token,于是创建AIReplay;如果可以找到的话,将当前的lastAI的content直接更改为resultAnswer.value。

因为msg是reactive创建的,所以可以动态修改。

实现如下:

// 将ai回复加入list
let lastAI = msgList.find((msg) => msg.content === resultAnswer.value);
resultAnswer.value +=parsedData.data.text;
//如果没有回答就创建新的:
if(!lastAI){
	AIReplay(resultAnswer.value);
}
else{
	lastAI.content=resultAnswer.value;
}
  • 13
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值