Перейти к содержанию

Директива wire:stream⚓︎

Livewire позволяет передавать поток контента на веб-страницу до завершения запроса через API wire:stream. Это чрезвычайно полезная функция для таких вещей, как чат-боты с ИИ, которые передают ответы по мере их генерации.

Не совместимо с 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) {
            // Передаём текущее значение отсчёта в браузер...
            $this->stream(
                to: 'count',
                content: $this->start,
                replace: true,
            );

            // Пауза в 1 секунду между числами...
            sleep(1);

            // Уменьшаем счётчик...
            $this->start = $this->start - 1;
        };
    }

    public function render()
    {
        return <<<'HTML'
        <div>
            <button wire:click="begin">Начать отсчёт</button>

            <h1>Отсчёт: <span wire:stream="count">{{ $start }}</span></h1>
        </div>
        HTML;
    }
}

Вот что происходит с точки зрения пользователя, когда он нажимает «Начать отсчёт»:

  • На странице отображается «Отсчёт: 3»
  • Пользователь нажимает кнопку «Начать отсчёт»
  • Проходит одна секунда, и отображается «Отсчёт: 2»
  • Этот процесс продолжается до тех пор, пока не будет показано «Отсчёт: 0»

Всё вышеперечисленное происходит во время одного сетевого запроса к серверу.

Вот что происходит с точки зрения системы при нажатии кнопки:

  • Запрос отправляется в Livewire для вызова метода begin()
  • Вызывается метод begin() и запускается цикл while
  • Вызывается $this->stream() и немедленно начинается «потоковый ответ» в браузер
  • Браузер получает потоковый ответ с инструкциями найти элемент в компоненте с wire:stream="count" и заменить его содержимое полученной полезной нагрузкой («3» в случае первого переданного числа)
  • Метод sleep(1) заставляет сервер ожидать одну секунду
  • Цикл while повторяется, и процесс передачи нового числа каждую секунду продолжается до тех пор, пока условие while не станет ложным
  • Когда метод begin() завершает выполнение и все значения отсчёта переданы в браузер, Livewire завершает свой жизненный цикл запроса, рендерит компонент и отправляет финальный ответ в браузер

Потоковая передача ответов чат-бота⚓︎

Обычным вариантом использования для wire:stream является потоковая передача ответов чат-бота, полученных от API, поддерживающего потоковые ответы (например, ChatGPT от OpenAI).

Ниже приведён пример использования 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);
        });
    }

    public function render()
    {
        return <<<'HTML'
        <div>
            <section>
                <div>Чат-бот</div>

                @if ($question)
                    <article>
                        <hgroup>
                            <h3>Пользователь</h3>
                            <p>{{ $question }}</p>
                        </hgroup>

                        <hgroup>
                            <h3>Чат-бот</h3>
                            <p wire:stream="answer">{{ $answer }}</p>
                        </hgroup>
                    </article>
                @endif
            </section>

            <form wire:submit="submitPrompt">
                <input wire:model="prompt" type="text" placeholder="Отправить сообщение" autofocus>
            </form>
        </div>
        HTML;
    }
}

Вот что происходит в приведённом выше примере:

  • Пользователь вводит текст в поле «Отправить сообщение», чтобы задать вопрос чат-боту.
  • Пользователь нажимает клавишу [Enter].
  • Сетевой запрос отправляется на сервер, устанавливает сообщение в свойство $question и очищает свойство $prompt.
  • Ответ отправляется обратно в браузер, и поле ввода очищается. Поскольку был вызван $this->js('...'), запускается новый запрос к серверу, вызывающий метод ask().
  • Метод ask() обращается к API чат-бота и получает частичные потоковые ответы через параметр $partial в обратном вызове.
  • Каждая часть $partial передаётся в браузер в элемент wire:stream="answer" на странице, постепенно показывая ответ пользователю.
  • Когда полный ответ получен, запрос Livewire завершается, и пользователь получает полный ответ.

Замена или добавление⚓︎

При передаче контента в элемент с помощью $this->stream() вы можете указать Livewire либо заменить содержимое целевого элемента переданным содержимым, либо добавить его к существующему содержимому.

Замена или добавление могут быть желательны в зависимости от сценария. Например, при потоковой передаче ответа от чат-бота обычно требуется добавление (поэтому оно и является значением по умолчанию). Однако при отображении чего-то вроде обратного отсчёта замена более уместна.

Вы можете настроить любой из вариантов, передав параметр replace: в $this->stream с логическим значением:

<?php

// Добавить содержимое...
$this->stream(to: 'target', content: '...');

// Заменить содержимое...
$this->stream(to: 'target', content: '...', replace: true);

Добавление/замену также можно указать на уровне целевого элемента, добавив или удалив модификатор .replace:

<?php

// Добавить содержимое...
<div wire:stream="Target">

// Заменить содержимое...
<div wire:stream.replace="target">

Справочник⚓︎

wire:stream="имя"

Модификаторы⚓︎

Модификатор Описание
.replace Заменяет содержимое элемента вместо добавления