> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Streaming

> 說明如何在 CLI、SSE、WebSocket、Livewire 中處理 Laravel Copilot SDK 的 Streaming 回應。

## Streaming

在 SessionConfig 中指定 `streaming: true` 後,可以以 streaming 方式接收 Copilot 的回應。
原本無論在 stdio 模式或 TCP 模式下都類似 streaming 的動作。差異在於會接收到 `ASSISTANT_MESSAGE_DELTA`。

```php theme={null}
use Revolution\Copilot\Contracts\CopilotSession;
use Revolution\Copilot\Facades\Copilot;
use Revolution\Copilot\Types\SessionEvent;

$config = new SessionConfig(
    streaming: true,
);

Copilot::start(function (CopilotSession $session) {
    $session->on(function (SessionEvent $event): void {
        if ($event->isAssistantMessageDelta()) {
            // delta 中訊息會分段傳來。可用於顯示過程,或僅靠這個完成處理。可實現流暢的顯示。
            echo $event->deltaContent();
        } elseif ($event->isAssistantMessage()) {
            // 分段的 delta 傳送結束後,會以 ASSISTANT_MESSAGE 形式傳來包含全文的整段訊息。
            // 若有下一則訊息會再度以 delta 傳來。
            // 對一個 prompt 可能會傳來多則訊息,因此 streaming 情況下多個 delta 與 1 次全文會重複出現。
        } elseif ($event->isAssistantReasoningDelta()) {
            // reasoning delta 同樣會分段傳來。
            echo $event->deltaContent();
        }
    });

    $session->sendAndWait(prompt: 'Tell me something about Laravel.');
}, config: $config);
```

* 為了流暢顯示,不要新增換行符號,直接顯示。
* Laravel Prompts 的 `spin()` 會造成顯示錯亂,不要一起使用。

## 具體使用模式

### Artisan 指令

如上以 `echo` 或 `$this->output->write()` 直接輸出。與直接使用 Copilot CLI 的顯示相同,容易理解。使用 Laravel Prompts 的 `stream()` 可以帶淡入效果顯示。

```php theme={null}
use function Laravel\Prompts\stream;

Artisan::command('copilot:streaming {--resume=}', function () {
    $config = new SessionConfig(
        streaming: true,
    );

    Copilot::start(function (CopilotSession $session) {
        info('Starting Copilot with streaming: '.$session->id());

        $stream = stream();

        $session->on(function (SessionEvent $event) use ($stream) {
            if ($event->isAssistantMessageDelta()) {
                // delta 分段傳來的訊息。使用 Laravel Prompts 的 stream() 以淡入效果流暢顯示。
                $stream->append($event->deltaContent());
            } elseif ($event->isIdle()) {
                // 進入 idle 後 close。同一 Session 中即便多次送出 prompt 也可正常運作,因此不需要再次 `$stream = stream()`。
                $stream->close();
            }
        });

        while (true) {
            $prompt = text(
                label: 'Enter your prompt',
                placeholder: 'Ask me anything...',
                required: true,
                hint: 'Ctrl+C to exit',
            );

            $session->sendAndWait($prompt);
        }
    }, config: $config, resume: $this->option('resume'));
})->purpose('Copilot streaming');
```

### 於網頁以 Server-Sent Events (SSE) 傳送

#### 使用 `response()->eventStream()` 的寫法

`response()->eventStream()` 是在 Laravel 12 發布前不久加入的。此套件只支援 Laravel 12 以上版本,因此可以使用。

```php theme={null}
Route::get('/copilot/sse', function () {
    return response()->eventStream(function () {
        yield from Copilot::stream(function (CopilotSession $session) {
            foreach ($session->sendAndStream('Tell me something about Laravel.') as $event) {
                if ($event->isAssistantMessageDelta()) {
                    yield $event->deltaContent();
                }
            }
        }, config: new SessionConfig(streaming: true));
    });
});

Route::get('/copilot', function () {
    return view('copilot');
});
```

#### 使用 `response()->stream()` 的寫法

```php theme={null}
Route::get('/copilot/sse', function () {
    return response()->stream(function () {
        Copilot::start(function (CopilotSession $session) {
            $session->on(function (SessionEvent $event) {
                if ($event->isAssistantMessageDelta()) {
                    echo "event: update\n";
                    echo 'data: '.$event->deltaContent()."\n\n";
                    ob_flush();
                    flush();
                }
            });

            $session->sendAndWait('Tell me something about Laravel.');
        }, config: new SessionConfig(streaming: true));

        echo "event: update\n";
        echo "data: </stream>\n\n";
        ob_flush();
        flush();
    }, 200, [
        'Content-Type' => 'text/event-stream',
        'Cache-Control' => 'no-cache',
        'Connection' => 'keep-alive',
        'X-Accel-Buffering' => 'no',
    ]);
});

Route::get('/copilot', function () {
    return view('copilot');
});
```

#### 前端範例程式碼

`copilot.blade.php` 僅供簡易顯示驗證。實際使用時若採用 React 或 Vue,建議使用 Laravel 官方的 npm 套件,例如 `@laravel/stream-react` 或 `@laravel/stream-vue`。

```html theme={null}
<html>
<body>

<script>
    const source = new EventSource('/copilot/sse');

    source.addEventListener('update', (event) => {
        if (event.data === '</stream>') {
            source.close();

            return;
        }

        console.log(event.data);
        document.getElementById("output").innerHTML += event.data;
    });
</script>

<h1>Copilot SSE Test</h1>
<div id="output"></div>
</body>
</html>
```

## 以 WebSocket 傳送

可組合 Laravel 的 Notification、Broadcast 與 Reverb,將 `delta` 透過 WebSocket 傳送。

## Livewire 的 `wire:stream`

Livewire 也可透過 `wire:stream` 指令實現 streaming 顯示。

```php theme={null}
<?php

use Livewire\Attributes\Layout;
use Livewire\Component;
use Revolution\Copilot\Contracts\CopilotSession;
use Revolution\Copilot\Enums\SessionEventType;
use Revolution\Copilot\Facades\Copilot;
use Revolution\Copilot\Types\SessionEvent;

new class extends Component
{
    public string $prompt = 'Tell me about Laravel Livewire.';
    public string $question = '';
    public string $answer = '';
    public ?string $sessionId = null;

    function submitPrompt(): void
    {
        $this->question = $this->prompt;

        $this->prompt = '';

        $this->answer = '';

        $this->js('$wire.copilot()');
    }

    public function copilot(): void
    {
        Copilot::start(function (CopilotSession $session) {
            $this->sessionId = $session->id();

            $session->on(SessionEventType::ASSISTANT_MESSAGE_DELTA, function (SessionEvent $event): void {

                $this->stream(
                    content: $event->deltaContent(),
                    to: 'answer',
                );
                $this->answer = $event->deltaContent();
            });

            $response = $session->sendAndWait(prompt: $this->question);
            $this->answer = $response->content();
        }, config: ['streaming' => true], resume: $this->sessionId);
    }
};
?>

<div>
    <div class="border border-gray-200 rounded-lg p-4 mb-6">
        @if ($question)
            <h3 class="font-extrabold mb-3">{{ $question }}</h3>

            <div wire:stream="answer">{{ $answer }}</div>
        @endif
    </div>

    <flux:input.group>
        <flux:input wire:model="prompt" placeholder="Ask Copilot"/>
        <flux:button wire:click="submitPrompt" icon="paper-airplane" icon:variant="outline"></flux:button>
    </flux:input.group>
</div>
```

<Info>
  最新資訊請參閱 [GitHub 儲存庫](https://github.com/invokable/laravel-copilot-sdk)。
</Info>


## Related topics

- [Streaming Events](/zh-TW/packages/laravel-copilot-sdk/streaming-events.md)
- [Laravel AI SDK](/zh-TW/ai-sdk.md)
- [SessionEvent](/zh-TW/packages/laravel-copilot-sdk/session-event.md)
- [Steering 與 Queueing](/zh-TW/packages/laravel-copilot-sdk/steering.md)
- [Cloud Sessions](/zh-TW/packages/laravel-copilot-sdk/cloud-sessions.md)
