wire:stream
Livewire 允许你通过 wire:stream API,在请求完成之前将内容流式传输到网页。这对 AI 聊天机器人等在生成过程中流式返回响应的场景极为有用。
WARNING
与 Laravel Octane 不兼容
目前 Livewire 不支持将 wire:stream 与 Laravel Octane 一起使用。
为演示 wire:stream 最基本的功能,下面是一个简单的 CountDown 组件:按下按钮后,向用户显示从「3」到「0」的倒计时:
php
use Livewire\Component;
class CountDown extends Component
{
public $start = 3;
public function begin()
{
while ($this->start >= 0) {
// Stream the current count to the browser...
$this->stream( // [tl! highlight:4]
to: 'count',
content: $this->start,
replace: true,
);
// Pause for 1 second between numbers...
sleep(1);
// Decrement the counter...
$this->start = $this->start - 1;
};
}
public function render()
{
return <<<'HTML'
<div>
<button wire:click="begin">Start count-down</button>
<h1>Count: <span wire:stream="count">{{ $start }}</span></h1> <!-- [tl! highlight] -->
</div>
HTML;
}
}用户按下「Start count-down」时,从用户视角发生的情况如下:
- 页面上显示「Count: 3」
- 他们按下「Start count-down」按钮
- 过一秒后显示「Count: 2」
- 该过程持续到显示「Count: 0」
以上全部发生在发往服务器的单次网络请求期间。
按下按钮时,从系统视角发生的情况如下:
- 向 Livewire 发送请求以调用 `begin()` 方法
- 调用 `begin()` 方法并开始 `while` 循环
- 调用 `$this->stream()`,立即开始向浏览器发送「流式响应」
- 浏览器收到流式响应,指示在组件中查找带有 `wire:stream="count"` 的元素,并用收到的载荷替换其内容(首次流式数字为「3」)
- `sleep(1)` 使服务器暂停一秒
- 重复 `while` 循环,每秒流式传输一个新数字,直到 `while` 条件为假
- 当 `begin()` 运行完毕且所有计数都已流式传到浏览器后,Livewire 完成请求生命周期,渲染组件并向浏览器发送最终响应
流式传输聊天机器人响应
wire:stream 的常见用例是:从支持流式响应的 API(如 OpenAI 的 ChatGPT)接收时,将聊天机器人响应流式传输出来。
下面示例用 wire:stream 实现类似 ChatGPT 的界面:
php
use Livewire\Component;
class ChatBot extends Component
{
public $prompt = '';
public $question = '';
public $answer = '';
function submitPrompt()
{
$this->question = $this->prompt;
$this->prompt = '';
$this->js('$wire.ask()');
}
function ask()
{
$this->answer = OpenAI::ask($this->question, function ($partial) {
$this->stream(to: 'answer', content: $partial); // [tl! highlight]
});
}
public function render()
{
return <<<'HTML'
<div>
<section>
<div>ChatBot</div>
@if ($question)
<article>
<hgroup>
<h3>User</h3>
<p>{{ $question }}</p>
</hgroup>
<hgroup>
<h3>ChatBot</h3>
<p wire:stream="answer">{{ $answer }}</p> <!-- [tl! highlight] -->
</hgroup>
</article>
@endif
</section>
<form wire:submit="submitPrompt">
<input wire:model="prompt" type="text" placeholder="Send a message" autofocus>
</form>
</div>
HTML;
}
}上例中发生的情况如下:
- 用户在标为「Send a message」的文本字段中输入,向聊天机器人提问。
- 他们按下 [Enter] 键。
- 向服务器发送网络请求,将消息设到 `$question` 属性,并清空 `$prompt` 属性。
- 响应返回浏览器并清空输入。由于调用了 `$this->js('...')`,会触发新请求到服务器调用 `ask()` 方法。
- `ask()` 方法调用 ChatBot API,并通过回调中的 `$partial` 参数接收流式响应片段。
- 每个 `$partial` 都会流式传到页面上带有 `wire:stream="answer"` 的元素中,让答案逐步呈现给用户。
- 收到完整响应后,Livewire 请求结束,用户得到完整答案。
替换与追加
用 $this->stream() 向元素流式传输内容时,可以告诉 Livewire:用流式内容替换目标元素的内容,或追加到现有内容之后。
替换或追加取决于场景,两者都可能合适。例如,流式传输聊天机器人响应时通常希望追加(因此这也是默认行为)。而在显示倒计时时,替换更合适。
可通过向 $this->stream 传入布尔值的 replace: 参数来配置:
php
// Append contents...
$this->stream(to: 'target', content: '...');
// Replace contents...
$this->stream(to: 'target', content: '...', replace: true);也可以在目标元素级别通过添加或去掉 .replace 修饰符来指定追加/替换:
blade
// Append contents...
<div wire:stream="target">
// Replace contents...
<div wire:stream.replace="target">参考
blade
wire:stream="name"修饰符
| 修饰符 | 说明 |
|---|---|
.replace | 替换元素内容,而不是追加 |