MindCell ができるまで — ブラウザだけで LLM を動かすチャット設計
@mlc-ai/web-llm を WebGPU 上で動かして、 ローカル推論の AI チャットをブラウザ完結で実装した Webサービスの設計記録。 ストリーミング応答、モデルロード進捗、対応外環境の扱いまで解説します。
MindCell ができるまで — ブラウザだけで LLM を動かすチャット設計
MindCell は、AI チャットを 会話をサーバーに送らずに 動かす Webサービスです。 WebGPU + @mlc-ai/web-llm でローカル LLM をブラウザ内推論し、 メッセージはあなたのデバイスから一歩も出ません。 メディア処理 7本に続く、 ラボ 8本目。 今回はテキスト生成という別軸での 「ブラウザ完結」 実証です。
なぜこの形にしたか
AI チャットに入力する内容は、 個人的だったり機密だったりすることが多い。 仕事のメール、健康相談、家族のこと、契約書のドラフト。 これらが外部 API を経由する設計は、本来は正しくない。
WebGPU が Chrome 113+ で安定し、 @mlc-ai/web-llm が量子化済みモデル (Qwen 2.5 0.5B など) を高水準 API で扱える今、 ローカル LLM を Web サービスとして出す素地が整っています。 「LLM もブラウザだけで動く」 という事実を、 そのまま製品にしたのが MindCell。
visual direction
ニューラル・パスウェイ × インディゴ + シアン。
- 深い真夜中のインディゴ (
#07091a) を背景に、 インディゴ (#6366f1) と電気シアン (#22d3ee) のグラデーションをアクセント - 「思考の回路が光る」 メタファー
- 既存7本 (mint / amber / violet / cobalt / lime / gold / rose) と完全に別の色相 + 異なる温度感
実装の見どころ
1. 高水準 API でロードを1行化
@mlc-ai/web-llm の CreateMLCEngine は、 モデルID と進捗コールバックを渡すだけでエンジンを返してくれる。 量子化済みモデルが Hugging Face CDN から並列ダウンロードされ、 IndexedDB に自動キャッシュ。 2回目以降はオフラインで動きます。
const engine = await CreateMLCEngine("Qwen2.5-0.5B-Instruct-q4f16_1-MLC", {
initProgressCallback: (p) => onProgress({ text: p.text, progress: p.progress }),
})
2. ストリーミング応答
OpenAI 互換の API で stream: true を指定すると、 AsyncIterable でトークンが届きます。 これを React の setMessages に渡すと、 タイプライター風の応答 UI が成立。
const stream = await engine.chat.completions.create({
messages: [...],
stream: true,
})
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content
if (delta) yield delta
}
UI 側は generator を for await で受けて、 ステート更新時に最後のメッセージのみ書き換える設計。 余計な再レンダリングを避けています。
3. WebGPU 非対応の検出と honest fallback
WebGPU は Chrome / Edge / Opera のデスクトップ最新版が最も安定。 Safari と Firefox は実験的または未対応。 起動時に navigator.gpu の有無を見て、 非対応なら専用パネルで対処法を案内します。
{webGpuOk === false ? (
<UnsupportedPanel>
Chrome / Edge / Opera で再アクセスしてください
</UnsupportedPanel>
) : null}
ここで「動くフリ」 をしないことが重要。 text-pluck のロゴ事件で得た教訓を踏襲しています。
4. モデル切替
Qwen 0.5B (~400MB) / Llama 1B (~900MB) / Phi 3.5 mini (~2.2GB) をプルダウンで切替。 切替時は既存エンジンを unload() してから新規ロード、 という Promise キャッシュ管理を runner 側に閉じ込めています。
5. System prompt 編集
折りたたみ式の editor で system prompt を即時変更可能。 ユーザーが「丁寧な翻訳者として振る舞って」 のような指示を入れて、 そのまま会話を始められる。 系列の text-pluck や bg-snap の preset プリセットと同じ流儀で、 「設定が見える時だけ触れる」 UI に統一しています。
苦労したところ
- WebGPU 普及度の現実: モバイル Safari は iOS 18+ でフラグ必要、Firefox は Nightly のみ。 「対応していないブラウザでは何もできない」 のを正直に出す UX が必須でした。
- モデル初回ロードの巨大さ: ~400MB は Wi-Fi 前提です。 モバイル回線では離脱可能性が高い。 FAQ で明示し、 progress バーで透明化することで対処。
- 量子化モデル ID の命名: WebLLM 公式が使う
Qwen2.5-0.5B-Instruct-q4f16_1-MLCのような長いID をUI に出さないよう、 ラベルと容量の組合せで見せる構造にしました。
今後の拡張
- 会話履歴の保存 / エクスポート: localStorage + JSON ダウンロード
- 添付ファイル対応: text-pluck (OCR) や voice-scribe (文字起こし) の出力を直接食わせる導線
- 複数モデル並列: Phi で詳細応答、 Qwen で素早い相槌、のような役割分担
- WebGPU 不在時の WASM fallback: 速度は出ないが動かす選択肢
- マルチターン要約: 長い会話を別モデルで要約してコンテキスト圧縮
このサービスから言える事
ラボの 8本柱:
- voice-scribe / clip-cast / bg-snap / text-pluck / pdf-anvil / pixel-lift / pic-flip / mind-cell
メディア処理 7本に続いて、 今度はテキスト生成系もブラウザ完結に来ました。 つまり「AI 処理を全部ブラウザで」 という主張が、 認識系 + 変換系 + 生成系の3カテゴリで実証された状態。 ここまで来ると、 「サーバーに送らないと使えない」 という SaaS の前提自体が、 そろそろ更新時期に入っている、 と言えそうです。
Next Action
読んだあとに、そのまま使って確かめる。
この開発ログは MindCell をどう育てているかの記録です。読んだらそのままサービス本体へ戻って、価値を確かめられるようにしています。