【精选技术】今个咱们来模仿一下ChatGPT逐字打印的效果

技术文章 2023年6月26日 935

最近我在开发chatgpt的第三方对接系统,于是顺便就研究了一下ChatGPT逐字打印的效果,其实也不是啥高深的技术,主要用到了 EventSource 服务器推送的一个网络事件接口

EventSource

EventSource 是服务器推送的一个网络事件接口。一个 EventSource 实例会对 HTTP 服务开启一个持久化的连接,以text/event-stream 格式发送事件,会一直保持开启直到被要求关闭。

一旦连接开启,来自服务端传入的消息会以事件的形式分发至你代码中。如果接收消息中有一个事件字段,触发的事件与事件字段的值相同。如果没有事件字段存在,则将触发通用事件。

与 WebSockets 不同的是,服务端推送是单向的。数据信息被单向从服务端到客户端分发。当不需要以消息形式将数据从客户端发送到服务器时,这使它们成为绝佳的选择。例如,对于处理社交媒体状态更新,新闻提要或将数据传递到客户端存储机制(如 IndexedDB 或 Web 存储)之类的,EventSource 无疑是一个有效方案。

PHP代码服务端

<?php
header("Access-Control-Allow-Origin:*");
header('X-Accel-Buffering: no');
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
ob_end_clean();
ob_implicit_flush(1);


while(1) {
    $data = [
        "id" => time(),
        "message" => '欢迎来到PHP学习网,现在是北京时间'.date('Y-m-d H:i:s')
    ];
    $xh = $xh + 1;
    returnEventData($data,"message",$xh);
    sleep(2);
}


function returnEventData($returnData, $event='message', $id=0, $retry=0) {
    $str = '';
    if($id>0) {
        $str .= "id: {$id}".PHP_EOL;
    }
    if($event) {
        $str.= "event: {$event}".PHP_EOL;
    }
    if($retry>0) {
        $str .= "retry: {$retry}".PHP_EOL;
    }
    if(is_array($returnData)) {
        $returnData = json_encode($returnData);
    }
    $str .= "data: {$returnData}".PHP_EOL;
    $str .= PHP_EOL;
    echo $str;
}
?>

以上代码流程大致为:

1.把报头 “Content-Type” 设置为 “text/event-stream”;

2.规定不对页面进行缓存;

3.输出发送数据;

4.向客户端刷新输出数据。

注意:每一次发送的信息,由若干个message组成,每个message内部由若干行组成,每一行都是如下格式。

[field]: value\n

其中[field]有四个值,分别是:

id:数据标识符用id字段表示,相当于每一条数据的编号。

event:表示自定义的事件类型,默认是message事件。浏览器可以用addEventListener()监听该事件。

retry:指定浏览器重新发起连接的时间间隔。当时间间隔到期会重连,另外一个是由于网络错误等原因,导致连接出错时也会重连。

data:数据内容,如果数据很长,可以分成多行,最后一行用\n\n结尾,前面行都用\n结尾。

完整的消息内容格式:

id: msg1\n
event: foo\n
retry: 10000\n
data: some text\n
data: another message\n
data: with two lines \n\n

特别注意:需要开启一个长连接,服务端代码一定要设置Content-Type为text/event-stream类型。一定要开启 ob_end_clean()方法清除之前的缓冲区域,否则返回的是text/html格式。然后设置请求头,其他都好理解,重点是这句header(‘X-Accel-Buffering: no’);,这段代码是强制关闭Nginx的代理缓存,其实也是关闭缓冲区域,不加这句话就会出现事件流最后一次性输出的问题;

然后就是使用echo+flush()方法向外输出事件流了,每次echo后面都要加上flush(),强制输出事件流;

前端js代码

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>EventSource测试</title>
</head>
<body>
<div>EventSource测试</div>
<ul></ul>
<script>
console.log("1111")
//这里填写要链接的地址
if(typeof(EventSource) !== "undefined") {
let source = new EventSource("message.php");
source.onmessage = (e) => {
if (e.data == 'null') {
return false;
} else {
let edata = JSON.parse(e.data);
$('#result').append(edata.id + ':' + edata.message + "<br/>");
}
};
} else {
alert('您的浏览器不支持SSE');
}

</script>
</body>
</html>

首先,使用typeof(EventSource)来判断浏览器对SSE的支持情况。

接着创建一个新的EventSource对象,然后定义发送更新的服务端的 URL(本例中是 “sse.php”),如果是跨域的请求,需要这样设置: let source = new EventSource(“http://xxx.com/sse.php”, { withCredentials: true });,并需要服务端代码开启允许跨域。

每接收到一次更新,就会发生 onmessage 事件。

当 onmessage 事件发生时,把已接收的数据推入 id 为 #result 的元素中。

EventSource 对象支持3种事件:

onopen:当通往服务器的连接被打开时触发。

onmessage:当接收到消息时触发。

onerror:当发生错误时触发。

出于安全,我们可以在onmessage事件中检测消息的来源域:

source.onmessage = (e) => {
  if (e.origin != 'https://www.viphper.com') {
    alert('消息来源不属于https://www.viphper.com');
    return;
  }
  ...
}

演示效果

请关注 PHP学习网 公众号,公众号上有视频演示。


关注微信公众号『PHP学习网

第一时间了解最新网络动态
关注博主不迷路~

PHP学习网:站内收集的部分资源来源于网络,若侵犯了您的合法权益,请联系我们删除!
分享到:
赞(0)

文章评论

您需要之后才可以评论
0点赞 0评论 收藏 QQ分享 微博分享

PHP学习网

PHP学习网