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

从用户的角度来看,当他们按下 "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 请求完成,用户收到完整响应。

替换 vs 追加

使用 $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替换元素内容而不是追加