Skip to content

wire:stream

Livewire 允许你通过 wire:stream API 在请求完成之前将内容流式传输到网页。这对于在生成响应时流式传输响应的 AI 聊天机器人等事物是一个非常有用的功能。

与 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;
    }
}

当用户按下"开始倒计时"时,从用户的角度来看发生了什么:

  • 页面上显示"Count: 3"
  • 他们按下"开始倒计时"按钮
  • 经过一秒钟,显示"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;
    }
}

以下是上述示例中发生的情况:

  • 用户在标记为"发送消息"的文本字段中键入以向聊天机器人提问。
  • 他们按下 [Enter] 键。
  • 向服务器发送网络请求,将消息设置为 $question 属性,并清除 $prompt 属性。
  • 响应被发送回浏览器,输入被清除。由于调用了 $this->js('...'),因此触发了对服务器的新请求,调用 ask() 方法。
  • ask() 方法调用 ChatBot API 并通过回调中的 $partial 参数接收流式响应部分。
  • 每个 $partial 都被流式传输到浏览器到页面上的 wire:stream="answer" 元素中,向用户逐步显示答案。
  • 当接收到整个响应时,Livewire 请求完成,用户收到完整响应。

替换 vs. 追加

使用 $this->stream() 将内容流式传输到元素时,你可以告诉 Livewire 用流式传输的内容替换目标元素的内容或将它们追加到现有内容。

根据场景,替换或追加都可能是可取的。例如,在从聊天机器人流式传输响应时,通常需要追加(因此是默认值)。但是,在显示倒计时之类的内容时,替换更合适。

你可以通过向 $this->stream 传递带有布尔值的 replace: 参数来配置任一选项:

php
// 追加内容...
$this->stream(to: 'target', content: '...');

// 替换内容...
$this->stream(to: 'target', content: '...', replace: true);

还可以通过追加或删除 .replace 修饰符在目标元素级别指定追加/替换:

blade
// 追加内容...
<div wire:stream="target">

// 替换内容...
<div wire:stream.replace="target">