ユーザー: SSEの利用例のデモを作るのにおすすめを挙げてください。コード例は不要です。たくさんの例を挙げてください。
AI: はい、承知いたしました。SSE (Server-Sent Events) の利用例として、デモ作成に最適なアイデアをたくさん挙げます。コード例は含めません。
SSEは「サーバーからクライアントへの一方向のプッシュ通知」という特性を持っているため、それを活かせるシナリオがデモとして効果的です。
SSEの基本を理解するのに最適な、シンプルで分かりやすいデモです。
-
リアルタイム時刻表示
- サーバーが1秒ごとに現在時刻をクライアントに送信し、ブラウザの時計がリアルタイムで更新され続けるデモ。最も基本的な実装例です。
-
ニュース速報ティッカー
- 画面の上部や下部に、サーバーからプッシュされたニュース見出しが次々と流れていくデモ。
-
ランダムメッセージジェネレーター
- サーバーが数秒おきにランダムな名言やメッセージを生成し、クライアントに送信するデモ。手軽に「プッシュ」を体感できます。
複数の情報を一度に、かつリアルタイムで表示するダッシュボードはSSEの得意分野です。
-
サーバー監視ダッシュボード
- サーバーのCPU使用率、メモリ使用量、ネットワークトラフィックなどをリアルタイムでグラフ表示します。バックグラウンドで値を生成し、定期的にクライアントに送信します。
-
株価・仮想通貨の価格トラッカー
- 特定の銘柄の価格が変動するたびに、サーバーが最新価格をクライアントに送信します。価格が上がれば緑、下がれば赤で表示するなど、視覚的な変化を付けると効果的です。
-
Webサイトのリアルタイムアクセス解析
- 「現在の訪問者数」「リアルタイムのページビュー」「どのページが見られているか」といった情報をリアルタイムで更新する管理者向けダッシュボード。
-
スポーツの試合速報
- 野球やサッカーなどの試合で、得点が入ったり、状況が変化したりするたびに、スコアボードやテキスト速報を更新します。
-
天気情報のリアルタイム更新
- 特定の地域の気温、湿度、風速などをセンサーやAPIから取得し、リアルタイムでダッシュボードに表示します。
ユーザーのアクションを他のユーザーに即座に伝えるシナリオです。
-
ライブコメントフィード
- 動画配信やブログ記事の下にあるコメント欄に、新しいコメントが投稿されたらページをリロードせずに自動で追加表示します。
-
「〇〇さんが入力中です...」の表示
- チャットアプリで、相手が文字を入力し始めたことをサーバーが検知し、他の参加者に「入力中」イベントを送信します。(双方向通信が不要な場合に有効)
-
アクティビティーストリーム(通知フィード)
- SNSで「〇〇さんがあなたの投稿に『いいね!』しました」「△△さんがあなたをフォローしました」といった通知をリアルタイムで表示します。
-
ライブリアクションカウンター
- ライブ配信中に視聴者が送った「ハート」や「いいね」のアイコンを画面上に表示し、合計カウントをリアルタイムで増やしていくデモ。
ビジネスシーンでの実用的な利用例です。
-
新規注文のリアルタイム通知
- ECサイトの管理者向け画面で、新しい注文が入るたびに「新規注文がありました!」という通知をポップアップやリストで表示します。
-
在庫数のリアルタイム更新
- 人気商品の商品ページで、他のユーザーが購入するたびに在庫数がリアルタイムで減っていく様子を表示します。
-
オンラインオークションの入札状況
- 現在の最高入札額と入札者情報が、新しい入札があるたびに全参加者の画面で即座に更新されます。
-
配達状況のリアルタイム追跡
- フードデリバリーや配送サービスで、配達員の現在位置を地図上でリアルタイムに更新します。
時間のかかる処理の状況をユーザーに伝えるのに役立ちます。
-
ファイルのアップロード・変換処理のプログレスバー
- ユーザーがアップロードした動画のエンコードなど、サーバー側での時間のかかる処理の進捗状況(例: 10%完了... 50%完了...)をリアルタイムで伝えます。
-
大規模データ処理のパイプライン状況
- 「データ収集中 → データ整形中 → 分析実行中 → 完了」といった複数のステップからなる処理の現在のステータスをリアルタイムで表示します。
-
サーバーログのライブストリーミング
- サーバー上で生成されるログを、Webブラウザの画面にリアルタイムで流し続けます。開発者向けのツールとして非常に有用です。
物理世界のデータをWebに繋げるデモです。
-
スマートホームのセンサーダッシュボード
- 部屋の温度センサー、ドアの開閉センサー、人感センサーなどの状態変化をリアルタイムでWeb画面に反映させます。
-
工場の生産ライン監視
- ベルトコンベアの稼働状況や、機器の温度・圧力といったセンサーデータをリアルタイムで可視化します。
ユーザー参加型の楽しいデモです。
-
リアルタイム投票・アンケート
- セミナーやイベントで、参加者が投票すると、その結果がリアルタイムで棒グラフなどに反映されていく様子を見せます。
-
オンラインクイズの進行
- クイズの司会者が「次の問題です」とボタンを押すと、全参加者の画面に新しい問題がSSE経由で一斉に配信されます。
これらのアイデアの中から、デモの目的や対象者に合わせて最適なものを選んでみてください。SSEの「手軽さ」と「リアルタイム性」を視覚的に分かりやすく表現することが、良いデモを作る上での鍵となります。
(返答終了理由: STOP)
ユーザー: codepenなどでservice workerの利用のデモをすることは可能ですか?
SSEをservice workerでシミュレートしようと思ってます(service workerに拘っては居ません。フロントエンドのみで完結するSSEのデモが作成したいのです)。
AI: はい、非常に良い質問ですね。結論から言うと、CodePen上でService Workerを使ってSSEをシミュレートするのは、非常に難しい、または現実的ではありません。
しかし、ご要望の「フロントエンドのみで完結するSSEのデモ」を作成するための、より優れた代替案がいくつかあります。
まず、なぜ難しいのかを簡単に説明します。
- セキュリティとオリジンの問題: Service Workerは強力な機能のため、HTTPSで提供されるページでしか動作しません。また、スクリプトが提供される「オリジン」(ドメイン)に強く紐づきます。CodePenはプレビューを
iframe内で、特殊なオリジン(例:cdpn.io)で実行するため、Service Workerの登録とスコープ(どの範囲のリクエストを制御するか)の管理が非常に複雑になります。 - ファイル構造: Service Workerは通常、独立したファイル(例:
sw.js)としてサイトのルートに配置する必要があります。CodePenのUIはHTML/CSS/JSの3つのパネルに分かれており、このようなファイルベースの構造を直感的に作ることができません。
これらの理由から、Service Workerのデモ自体がCodePen上では困難であり、その上でSSEのシミュレーションという応用的なことを行うのは、さらに難易度が高くなります。
Service Workerに固執せず、「フロントエンド完結」という目的を達成するための、よりシンプルで効果的な方法を提案します。
これが最も手軽で、CodePen上で完結させるのに最適な方法です。本物のEventSource APIを使わずに、同じような振る舞いをするJavaScriptクラスを自作します。
デモのアイデア:
- 擬似的なサーバープッシュ:
setIntervalやsetTimeoutを使い、クラスの内部で定期的にイベントを発生させます。 - 同じインターフェース: 本物の
EventSourceと同じように、addEventListener('message', ...)やaddEventListener('customEvent', ...)といったメソッドを持たせます。 - イベントオブジェクトの再現: イベントリスナーに渡すオブジェクトも、本物と同じように
{ data: '...' }という形式にします。
この方法の利点:
- 完全フロントエンド完結: 外部ライブラリもサーバーも不要。CodePenのJSパネルだけで完結します。
- シンプルで分かりやすい: SSEの「クライアント側がどうイベントを受け取るか」という部分に焦点を当てて、ロジックをシンプルに説明できます。
- 確実な動作: Service Workerのような環境依存の問題が一切なく、どのブラウザでも安定して動作します。
デモシナリオの例:
- 「サーバーに接続」ボタンを押すと、自作の
MockEventSourceクラスのインスタンスを生成。 - クラス内部で
setIntervalが1秒ごとに動き出す。 - タイマーが発火するたびに、「新しいニュースです: ...」や「現在の株価: ...」のようなランダムなメッセージを生成。
addEventListenerで登録しておいた関数を呼び出し、画面のリストにメッセージを追加していく。- 「切断」ボタンで
closeメソッドを呼び出し、setIntervalを停止させる。
もし「どうしても本物のService Workerを使ってみたい」ということであれば、CodePenの代わりにStackBlitzやCodeSandboxを利用することをお勧めします。
これらのサービスは、CodePenよりも高機能なオンラインIDEであり、以下のような利点があります。
- 本物のファイルシステム:
index.htmlやsw.jsといったファイルを個別に作成・編集できます。 - 仮想開発サーバー: バックグラウンドでNode.jsベースのサーバーが動いており、Service Workerの登録や動作がローカル開発環境に非常に近いです。
- より忠実なデモ: Service Workerがリクエストを傍受(intercept)し、ストリーミングレスポンスを返すという、より現実に近いSSEのシミュレーションが可能です。
この方法でのデモシナリオ:
- クライアント側のJS(
main.js)でnew EventSource('/sse-endpoint')を実行。 - Service Worker(
sw.js)でfetchイベントをリッスンし、/sse-endpointへのリクエストを傍受する。 - 傍受したリクエストに対し、
ReadableStreamを使ってレスポンスを生成する。 - そのストリームに対して、
setIntervalなどで定期的にdata: ...\n\nという形式の文字列を書き込んでいく。
あなたの目的に合わせて、以下のように選ぶのが良いでしょう。
| 目的 | おすすめの方法 | 特徴 |
|---|---|---|
| 手軽さとフロントエンド完結を最優先したい (CodePenで作りたい) |
方法1:EventSourceの模倣クラス |
最も簡単で確実。SSEのクライアント側の挙動をデモするのに最適。 |
| Service Workerの動作を忠実にデモしたい (本物の技術を使いたい) |
方法2:StackBlitzやCodeSandbox | 現実の環境に近い形で、Service WorkerによるSSEシミュレーションが可能。 |
まずは「方法1」でSSEのコンセプトを視覚的に表現するデモを作成し、もしService Worker自体の挙動に興味が湧いたら「方法2」に挑戦してみるのが良いステップだと思います。
(返答終了理由: STOP)
ユーザー: ではwasmとpostMessageで外部サーバーをシミュレートする方法はないですか?
AI:
はい、非常に面白いアイデアです!
結論から言うと、はい、WASMとpostMessage(特にWeb Workerと組み合わせて)で外部サーバーをシミュレートすることは技術的に可能です。
ただし、それがSSEのデモとして最適かどうかは、デモの目的によって変わります。このアプローチの長所と短所、そしてより良い代替案について詳しく解説します。
これは、ブラウザ内で「サーバー役」と「クライアント役」を完全に分離する高度なシミュレーションです。
-
Web Worker(サーバー役の環境):
- まず、Web Worker(例:
server-worker.js)を作成します。Web WorkerはメインのUIスレッドとは独立して動くため、重い処理を行っても画面が固まりません。これは、サーバーが別プロセスで動いている様子を擬似的に再現するのに最適です。
- まず、Web Worker(例:
-
WASMモジュール(サーバー役のロジック):
- C++、Rust、Goなどの言語で、データを定期的に生成するロジックを記述します。(例: 1秒ごとにカウンターをインクリメントしてその値を返す、など)
- これをWASMにコンパイルします。
- Web Workerの中で、このWASMモジュールを読み込み、インスタンス化します。
-
postMessage(通信手段):- Web Worker内で
setIntervalなどを使い、定期的にWASMモジュールの関数を呼び出してデータを生成させます。 - 生成されたデータを、
self.postMessage(data)を使ってメインスレッド(クライアント役)に送信します。
- Web Worker内で
-
メインスレッド(クライアント役):
- メインのJavaScriptコードで、
new Worker('server-worker.js')としてWorkerを起動します。 worker.addEventListener('message', event => { ... })を使って、Workerから送られてくるデータ(サーバーからのプッシュ通知の代わり)を受け取ります。- 受け取ったデータを使ってDOMを更新します。(例: リアルタイムチャートを更新する、リストに新しい項目を追加する、など)
- メインのJavaScriptコードで、
- 完全な非同期性の再現: Web Workerを使うことで、メインUIスレッドを全くブロックせずにデータソースを動かせます。これは、ネットワーク通信が非同期であるという実世界の状況を非常に忠実に再現します。
- 複雑なロジックのシミュレーション: もしデモのデータ生成ロジックが非常に複雑な場合(例: 物理シミュレーションの結果、ゲームの盤面の更新、リアルタイムなデータ分析など)、計算性能の高いWASMで実装するのは非常に有効です。
- 技術的な面白さ: WASM、Web Worker、
postMessageというモダンなWeb技術を組み合わせた、非常に高度で興味深いデモになります。
- 過剰な複雑さ(Overkill): 単純な「1秒ごとに時刻を送る」といったSSEのデモには、WASMは明らかに過剰スペックです。WASMのコンパイルやJSとの連携(グルーコード)の準備が必要になり、SSEの本質を見せるという目的から焦点がずれてしまう可能性があります。
- SSEのAPIを直接使っていない: この方法では、クライアント側は
new EventSource()を使いません。代わりにworker.addEventListener('message', ...)を使います。つまり、SSEの「結果」はシミュレートできますが、SSEの「使い方(API)」そのもののデモにはなりません。 これはデモの目的を考える上で非常に重要な点です。 - ビルド環境が必要: WASMを使うには、通常C++やRustのコンパイラと関連ツール(Emscriptenやwasm-packなど)が必要です。CodePenのような手軽な環境でサッと作るのには向いていません。(StackBlitzなど、より高機能な環境が必要になります)
あなたの「フロントエンドのみで完結させたい」という要望と、「SSEのデモ」という目的を考えると、よりバランスの取れた素晴らしい方法があります。
これは、WASMの複雑さを排除し、かつSSEのAPIを忠実に再現するエレガントな方法です。
ステップ:
-
サーバー役のWorker (JavaScriptのみ)
- Web Worker (
sse-worker.js) を作成します。 - WASMは使わず、素のJavaScriptで
setIntervalを使って定期的にデータを生成し、self.postMessage()で送信します。これで十分です。
// sse-worker.js let count = 0; setInterval(() => { self.postMessage({ data: `サーバーからのメッセージ ${count++}` }); }, 1000);
- Web Worker (
-
クライアント側に「アダプタークラス」を作成
- ここがポイントです。メインスレッドに、本物の
EventSourceと全く同じインターフェースを持つクラスを作成します。
// main.js class WorkerEventSource { constructor(workerPath) { this.worker = new Worker(workerPath); this.listeners = {}; this.worker.onmessage = (event) => { // 'message' という名前のイベントを模倣してディスパッチ if (this.listeners['message']) { this.listeners['message'].forEach(callback => callback(event)); } }; } addEventListener(eventName, callback) { if (!this.listeners[eventName]) { this.listeners[eventName] = []; } this.listeners[eventName].push(callback); } close() { this.worker.terminate(); } }
- ここがポイントです。メインスレッドに、本物の
-
デモのアプリケーションコード
- デモのコードでは、この自作クラスを本物の
EventSourceと全く同じように使います。
// main.js (続き) const sseSource = new WorkerEventSource('sse-worker.js'); sseSource.addEventListener('message', (e) => { console.log('受信:', e.data); // ここでDOMを更新する const li = document.createElement('li'); li.textContent = e.data.data; // workerからのオブジェクトを取得 document.getElementById('events').appendChild(li); }); // 10秒後に接続を閉じるデモ setTimeout(() => { sseSource.close(); console.log('SSE接続を閉じました。'); }, 10000);
- デモのコードでは、この自作クラスを本物の
| アプローチ | 手軽さ | SSEの再現度 | 技術的複雑さ | おすすめの状況 |
|---|---|---|---|---|
| WASM + Worker | 低 | △ (結果のみ) | 高 | データ生成ロジックが非常に重く、WASMの利用自体がデモの目的である場合。 |
| Worker + アダプター | 中 | ◎ (APIも再現) | 中 | 今回最もおすすめ。 フロントエンド完結で、SSEのAPIの使い方を忠実に、かつ明確にデモできる。 |
JS setInterval のみ(前回の回答) |
高 | 〇 (APIを模倣) | 低 | 最も手軽にSSEの概念を視覚化したい場合。Web Workerのセットアップも不要。 |
結論として、WASMを使うアイデアは技術的に可能で非常に面白いですが、SSEのデモとしては複雑すぎることが多いです。代わりに「Web Worker + EventSourceアダプター」方式を採用すると、フロントエンドだけで完結しつつ、SSEのAPIの使い方まで含めた、非常に高品質で教育的なデモを作成できます。
(返答終了理由: STOP)
ユーザー: いいですね。web workerとpostMessageはcodepen上で利用可能ですか?
AI: はい、素晴らしい質問です!
結論から言うと、はい、Web WorkerとpostMessageはCodePen上で利用可能です。
ただし、CodePenのファイル構造(HTML/CSS/JSの3パネル)の特性上、ローカルで開発するときのように別ファイル(例: worker.js)を作成して読み込むことはできません。
その代わりに、JavaScriptのコードを文字列として扱い、それをBlobとURL.createObjectURL()を使ってその場で「ファイル化」するという、非常にエレガントなテクニックを使います。
この方法は、自己完結型のデモを作成するのに最適です。
まず、Worker内部で実行したい処理を、テンプレートリテラル(``)を使って文字列として記述します。
const workerCode = `
// --- ここから下がWorkerの内部で動くコード ---
let count = 0;
// 1秒ごとにメッセージを送信する (SSEのシミュレーション)
setInterval(() => {
// postMessageを使ってメインスレッドにデータを送る
self.postMessage({ data: \`サーバーからのメッセージ \${count++}\` });
}, 1000);
// --- ここまでがWorkerのコード ---
`;ポイントは、このコード全体がworkerCodeという一つの変数(文字列)に格納されていることです。
次に、この文字列をBlobオブジェクトに変換します。Blobは、ファイルのようなデータのかたまりを表現するオブジェクトです。MIMEタイプを'application/javascript'に指定します。
const blob = new Blob([workerCode], { type: 'application/javascript' });URL.createObjectURL()メソッドを使って、ブラウザのメモリ上にあるBlobを指し示す、一時的なURLを生成します。このURLが、あたかも外部ファイル(worker.js)へのパスのように振る舞います。
const workerURL = URL.createObjectURL(blob);最後に、生成したURLをnew Worker()の引数に渡して、Workerを通常通り起動します。
const worker = new Worker(workerURL);以下は、CodePenのJSパネルにそのまま貼り付けて動作させることができる、完全なサンプルです。HTMLパネルには<ul id="events"></ul>とだけ記述してください。
HTML:
<h2>SSEシミュレーション (Web Worker)</h2>
<p>Workerから1秒ごとにメッセージが届きます。</p>
<ul id="events"></ul>JavaScript:
// --- ステップ1: Workerのコードを文字列として定義 ---
const workerCode = `
let count = 0;
// 1秒ごとにメッセージを送信
setInterval(() => {
self.postMessage({ message: \`Workerからのメッセージ No.\${count++}\` });
}, 1000);
`;
// --- ステップ2: 文字列からBlobを作成 ---
const blob = new Blob([workerCode], { type: 'application/javascript' });
// --- ステップ3: Blobから一時的なURLを生成 ---
const workerURL = URL.createObjectURL(blob);
// --- ステップ4: 生成したURLでWorkerを起動 ---
console.log('Workerを起動します...');
const worker = new Worker(workerURL);
// --- メインスレッド側でWorkerからのメッセージを受け取る ---
const eventList = document.getElementById('events');
worker.addEventListener('message', (event) => {
console.log('メインスレッドが受信:', event.data);
const newLi = document.createElement('li');
newLi.textContent = event.data.message; // Workerから送られたメッセージ
eventList.appendChild(newLi);
});
// デモ用に10秒後にWorkerを停止
setTimeout(() => {
worker.terminate(); // Workerを停止
console.log('Workerを停止しました。');
const newLi = document.createElement('li');
newLi.textContent = '--- 接続終了 ---';
newLi.style.color = 'red';
eventList.appendChild(newLi);
}, 10000);- CodePenでWeb Workerは使えますか?
- はい、使えます。
- どうやって?
- Workerのコードを文字列で用意し、
BlobとURL.createObjectURL()を使って動的にURLを生成して読み込みます。
- Workerのコードを文字列で用意し、
- メリットは?
- 自己完結: HTML/CSS/JSの各パネルだけで完結し、外部ファイルが不要なため、デモの共有が非常に簡単です。
- 環境構築不要: CodePenを開くだけで、すぐに試せます。
このテクニックを使えば、あなたが考えている「Web Workerを使ったフロントエンド完結型のSSEデモ」をCodePen上で完璧に実現できます。ぜひ試してみてください!
(返答終了理由: STOP)
ユーザー: それではそのデモ用のhtmlを生成してください。index.htmlにやってることの説明を書いてください。スマホで開くことを考えてます。デモはLLMのthinkingとアウトプットの遅延をシミュレートしたものにしてください。
AI:
はい、承知いたしました。
Web WorkerとpostMessageを利用して、LLM(大規模言語モデル)の思考と応答の遅延をシミュレートするデモ用のHTMLファイルを生成します。
このHTMLファイルは単体で完結しており、スマートフォンでの表示にも配慮したレスポンシブデザインになっています。ファイルに保存してブラウザで開くか、そのままCodePenのHTMLパネルに貼り付けても動作します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LLM応答シミュレーションデモ</title>
<style>
/* 基本スタイルとレスポンシブ対応 */
:root {
--bg-color: #f4f7f9;
--font-color: #333;
--primary-color: #007bff;
--card-bg: #ffffff;
--output-bg: #2d2d2d;
--output-color: #e0e0e0;
--border-color: #e0e0e0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
padding: 1rem;
background-color: var(--bg-color);
color: var(--font-color);
line-height: 1.6;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 1rem;
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
h1, h2 {
border-bottom: 2px solid var(--primary-color);
padding-bottom: 0.5rem;
margin-top: 1.5rem;
}
p, li {
font-size: 1rem;
}
code {
background-color: #e9ecef;
padding: 0.2em 0.4em;
border-radius: 3px;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
ul {
padding-left: 1.5rem;
}
/* デモUIのスタイル */
.demo-area {
margin-top: 2rem;
}
.generate-button {
display: block;
width: 100%;
padding: 0.8rem 1rem;
font-size: 1.1rem;
font-weight: bold;
color: #fff;
background-color: var(--primary-color);
border: none;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s, transform 0.1s;
}
.generate-button:hover:not(:disabled) {
background-color: #0056b3;
}
.generate-button:active:not(:disabled) {
transform: scale(0.98);
}
.generate-button:disabled {
background-color: #999;
cursor: not-allowed;
}
.status {
margin-top: 1rem;
font-weight: bold;
text-align: center;
height: 1.5rem; /* 高さを固定してレイアウトが崩れないようにする */
}
.output-container {
margin-top: 1rem;
background-color: var(--output-bg);
color: var(--output-color);
border-radius: 6px;
padding: 1rem;
min-height: 150px;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
white-space: pre-wrap; /* テキストの折り返しを有効にする */
word-wrap: break-word;
}
/* タイピングカーソルのアニメーション */
.cursor {
display: inline-block;
width: 10px;
height: 1.2em;
background-color: var(--output-color);
animation: blink 1s step-end infinite;
vertical-align: bottom;
}
@keyframes blink {
from, to { background-color: transparent; }
50% { background-color: var(--output-color); }
}
</style>
</head>
<body>
<main class="container">
<h1>LLM応答シミュレーションデモ</h1>
<h2>このデモについて</h2>
<p>
このページは、ChatGPTのような大規模言語モデル(LLM)が、まるでリアルタイムで文章をタイピングしているかのように応答を生成する様子をシミュレートしたものです。
</p>
<p>
このデモは、サーバーを使わず、<strong>すべてブラウザの中だけで完結</strong>しています。
</p>
<h3>技術的な仕組み</h3>
<ul>
<li>
<strong>Web Worker:</strong> LLMの役割を担う「サーバー」を擬似的に再現するために使用しています。Web Workerはメインの処理とは別のスレッドで動くため、重い処理を行ってもページの操作が固まることはありません。
</li>
<li>
<strong>postMessage:</strong> メインページ(クライアント役)とWeb Worker(サーバー役)が通信するための仕組みです。Workerは生成したテキストの断片を<code>postMessage</code>で少しずつメインページに送信します。
</li>
<li>
<strong>Blob と URL.createObjectURL:</strong> CodePenのような単一ファイル環境でWeb Workerを動かすためのテクニックです。Worker用のJavaScriptコードを文字列として定義し、その場で一時的な「ファイル」としてURLを生成して読み込んでいます。
</li>
<li>
<strong>SSE (Server-Sent Events) のシミュレーション:</strong> 本来サーバーからクライアントへ一方向にデータを送り続けるSSEの挙動を、Web Workerからの<code>postMessage</code>で擬似的に再現しています。
</li>
</ul>
<div class="demo-area">
<h2>デモ</h2>
<button id="generateBtn" class="generate-button">文章を生成する</button>
<div id="status" class="status"></div>
<div class="output-container">
<span id="output"></span><span id="cursor" class="cursor" style="display: none;"></span>
</div>
</div>
</main>
<script>
// --- DOM要素の取得 ---
const generateBtn = document.getElementById('generateBtn');
const outputSpan = document.getElementById('output');
const statusDiv = document.getElementById('status');
const cursorSpan = document.getElementById('cursor');
// --- 1. Web Workerのコードを文字列として定義 ---
// このコードがバックグラウンドで実行される
const workerCode = `
// LLMが生成するサンプルテキスト
const sampleText = "Web Workerは、メインのUIスレッドとは独立したバックグラウンドスレッドでスクリプトを実行するためのシンプルな手段を提供します。これにより、UIを固まらせることなく、時間のかかる処理を実行できます。このデモでは、Web WorkerがLLMの役割を担い、postMessageを介して一文字ずつテキストを送信することで、リアルタイムのストリーミング出力をシミュレートしています。";
// メインスレッドからのメッセージを受け取るリスナー
self.onmessage = function(event) {
if (event.data.command === 'GENERATE') {
generateTextStream();
}
};
function generateTextStream() {
let currentIndex = 0;
// 最初に「思考中」をシミュレートするための遅延
self.postMessage({ type: 'STATUS', payload: '思考中...' });
const thinkingTime = 1000 + Math.random() * 1000; // 1〜2秒のランダムな思考時間
setTimeout(() => {
self.postMessage({ type: 'STATUS', payload: '生成中...' });
// テキストを1文字ずつ送信するインターバル処理
const intervalId = setInterval(() => {
if (currentIndex < sampleText.length) {
const char = sampleText[currentIndex];
// メインスレッドに文字データを送信
self.postMessage({ type: 'CHUNK', payload: char });
currentIndex++;
} else {
// すべての文字を送信し終わったら完了メッセージを送信
clearInterval(intervalId);
self.postMessage({ type: 'DONE' });
self.postMessage({ type: 'STATUS', payload: '完了!' });
}
}, 50 + Math.random() * 100); // 50ms〜150msのランダムなタイピング速度
}, thinkingTime);
}
`;
// --- 2. 文字列からBlobとURLを生成 ---
const blob = new Blob([workerCode], { type: 'application/javascript' });
const workerURL = URL.createObjectURL(blob);
// --- 3. Workerを起動 ---
const worker = new Worker(workerURL);
// --- 4. ボタンのクリックイベントリスナー ---
generateBtn.addEventListener('click', () => {
// UIを初期化
outputSpan.textContent = '';
statusDiv.textContent = '';
generateBtn.disabled = true; // ボタンを無効化
cursorSpan.style.display = 'inline-block'; // カーソルを表示
// Workerに生成開始のコマンドを送信
worker.postMessage({ command: 'GENERATE' });
});
// --- 5. Workerからのメッセージを受け取るリスナー ---
worker.addEventListener('message', (event) => {
const { type, payload } = event.data;
switch(type) {
case 'STATUS':
// ステータス表示を更新
statusDiv.textContent = payload;
break;
case 'CHUNK':
// 受け取った文字を出力エリアに追記
outputSpan.textContent += payload;
break;
case 'DONE':
// 生成が完了したらボタンを再度有効化
generateBtn.disabled = false;
cursorSpan.style.display = 'none'; // カーソルを非表示
break;
}
});
</script>
</body>
</html>
はい、承知いたしました。
より本気でSSE (Server-Sent Events) をシミュレートするために、本物の
EventSourceAPIとほぼ同じインターフェースを持つクラスを実装します。これにより、デモのアプリケーションコードは、まるで実際のSSEサーバーと通信しているかのように記述できます。Web WorkerがSSEサーバーの役割を、自作の
MockEventSourceクラスがブラウザのEventSourceAPIの役割を担います。生成するHTMLは、前回同様に単一ファイルで完結し、スマートフォンでの閲覧にも最適化されています。
index.html