Звичайна дія Livewire повертає результат один раз, наприкінці. Для відповіді чат-бота, що генерується 20 секунд, користувач бачив би порожній екран усі 20 секунд. wire:stream дає змогу відправляти частини відповіді в браузер під час виконання дії.
new class extends Component {
public string $prompt = '';
public string $question = '';
public string $answer = '';
public function submitPrompt(): void
{
$this->question = $this->prompt;
$this->prompt = '';
$this->answer = '';
$this->js('$wire.ask()'); // друга дія - вже після того, як поле очистилося
}
public function ask(): void
{
// streamAnswer() - генератор над будь-яким клієнтом LLM зі стримінгом
foreach ($this->streamAnswer($this->question) as $chunk) {
$this->answer .= $chunk;
$this->stream(to: 'answer', content: $chunk);
}
}
};
<p wire:stream="answer">{{ $answer }}</p>
<form wire:submit="submitPrompt">
<input wire:model="prompt" placeholder="Ваше питання">
</form>
Як це працює: відповідь на запит ask() стає потоковою: кожен $this->stream() одразу відправляє шматок у браузер, і Livewire дописує його в елемент з wire:stream="answer". Коли метод завершується, приходить звичайна фінальна відповідь з повним станом компонента.
Параметри stream():
to:- назва цілі (значенняwire:streamв шаблоні);content:- текст;replace: true- замінити вміст замість дописування (для індикатора прогресу «Оброблено 40%»).
Чому дві дії (submitPrompt + ask): перша швидко повертає відповідь - поле введення очищується, питання з'являється на екрані. Друга вже стрімить відповідь.
Що враховувати в продакшені:
- процес PHP зайнятий весь час стримінгу. Двадцять секунд генерації - двадцять секунд зайнятого воркера PHP-FPM. При багатьох одночасних користувачах це вичерпує пул; доречні Octane/FrankenPHP чи винесення генерації в чергу з трансляцією результату через Reverb;
- буферизація по дорозі: проксі (Nginx
proxy_buffering, CDN), стиснення відповідей можуть накопичувати шматки й віддавати все наприкінці. Потрібно вимкнути буферизацію для цих запитів; - тайм-аути:
max_execution_time, тайм-аути проксі й балансувальника мають покривати найдовшу генерацію; - зберігати повний результат у властивості (
$this->answer .= $chunk) - інакше після фінальної відповіді та наступного рендеру текст зникне; - XSS: потоковий вміст вставляється як текст, але якщо ви рендерите відповідь як Markdown/HTML, санітизуйте її на сервері.
Альтернативи: response()->eventStream() з SSE у звичайному маршруті Laravel, якщо компонентна модель не потрібна.