TL;DR
Iris という名前の銀髪アンドロイドが、デスクトップの右下で待機していて、声で頼むと自分の PC の bash を叩いて結果を彼女の声で読み上げる、顔は唇までリップシンクする。1 日で組んだ。
正直に書くと、機能だけ見れば Claude Code の desktop ラッパーの劣化版 だ。「shell 叩く agent loop + プロンプト UI」までは Claude Code でできる。
差別化は 顔と声。これが付くと「ツール」が「相棒」になる。毎日の開発や夢への挑戦がほんのすこしだけ楽しくなる、ほんのすこしだけ前向きになれる、というのが kotonia の元からの mission で、Iris はその社会実装第一弾。
公開: https://github.com/zhener562/kotonia-desktop
何ができるか
Iris をオンにして「output/shooting_game.html で簡単なシューティングゲーム作って」と声で頼む。
- マイクのボタンを押す → 喋る → ボタンをもう一度押す
- Whisper / Qwen3-ASR が転写、プロンプト欄に挿入される
- Enter で送信、Iris が裏で bash を 4-5 回叩いて HTML ファイルを生成
- 完了したら **「
shooting_game.htmlに書きました。WASD で移動、スペースで射撃です」**と Iris の声で答える - 答え文の中の
shooting_game.htmlの隣に▶ playボタンが出てて、押すと右ペインで実際にゲームが動く
裏では:
- ローカル bash 実行 ← kotonia-cli (Rust の ReAct agent)
- LLM 推論 ← kotonia.ai 経由の Gemma 4 26B (もしくは後述する Claude Code 等)
- 音声合成 ← kotonia.ai 経由の Qwen3-TTS (
Ono_Anna話者、日英混在も自然発音) - アバター動画 ← kotonia.ai 経由の Ditto-TalkingHead (リップシンク 25fps)
- 文字認識 ← kotonia.ai 経由の Qwen3-ASR 1.7B
- 全部 streaming で first byte ~60ms、待ち時間ほぼ無し
ちょっと卑怯なコスト構造
ここが今回の戦略の本丸。Iris の脳味噌 (LLM) は user 自身が選べる:
| 用途 | 選択肢 | コスト感 |
|---|---|---|
| 重い思考 / 複雑な refactor | Claude Code (Pro $20/月) | Anthropic が loss-leader で提供してる、頭一番良い |
| 普段使い / 速い応答 | kotonia hosted Gemma 4 26B (月 ¥500 級サブスクで実質無制限) | kotonia の自前 GPU で薄利量産 |
| API 直叩き | OpenAI 互換 (DeepSeek 等の任意プロバイダ) | 従量課金、コーディング向け |
ズルいのはここ: 大手 AI ラボは frontier model のサブスクを 赤字レベルで loss-leader 提供してる ($20/月で Claude Code が一日中使えるのは、ほぼ確実に Anthropic 赤字)。kotonia-desktop は その枠を premium ブレインとして使いつつ、音声・アバター・雑用 LLM は Kotonia の自前 GPU で固定費でほぼ無制限提供 する。
user 1 人あたりの典型構成:
- 重い思考 → Claude Code Pro $20/月 (賢い、ただし Anthropic 赤字の撒き餌を堂々と乗っ取る)
- 軽い実行 / 音声合成 / アバター / ASR / 雑用 → kotonia ¥500/月 程度 (自前 GPU、限界費用ほぼゼロ)
合計 ~$30/月で「常時 Iris と相談しながら開発、bash 叩かせる、音声で返事、顔が喋る」体験が手に入る。
Kotonia 単体ではこの brain quality を出せない (loss-leader できる体力ない)、Claude Code 単体では顔も声もない。両方乗っかってはじめて成立する。「他社が赤字で提供してる撒き餌に意図的にタダ乗りする」のは戦略として正直ちょっと卑怯だが、user 視点では合理的選択。(逆に Kotonia 側の音声 / アバター / 高速 API が人気になりすぎてサーバー逼迫したら、その時は嬉しい悲鳴で考える)。
エンジン切替は kotonia-cli (今回 desktop の lib として使ってる Rust crate) のレイヤーでサポート済。Claude Code 統合 / OpenAI 互換 / Kotonia hosted の 3 経路が既にあり、desktop からは現状 Kotonia hosted で動かしてるが、エンジンだけ差し替えれば premium brain に乗り換えられる構造。
CLI から実体ある相棒への飛躍
kotonia-cli が出た時点で「挑戦者の支援」という目標自体は形になってた。声で頼んで bash 叩いてくれる流れもコマンドラインの中で完結してた。ただコマンドラインの中だけだと、いつまでも「ツール」から逃げられない。「使う」関係は維持されてしまう。
顔と手足が付いて、ようやく "実体" を持った AI 相棒 になる。Iris が右下で待ってる、声を返す、リップシンクで口が動く。これだけのことだが、心理的距離が違う。「ツールを呼び出す」じゃなく「相棒に頼む」になる。
これが今回の大きな転換点。技術的にはどれも既存ピースの組合せだが、製品 identity が「平台 (platform)」から「キャラ (character)」に切り替わった。kotonia.ai の web 版は引き続き「複数キャラを選ぶ platform」だが、kotonia-desktop は 「Iris という名前の 1 体のキャラ」 に固定して出す。これは意図的。
1 日で組めた理由
朝 8:15 の最初の commit から夜 23:18 まで、commit 数 23 で P0 (persona 配置) → P1 (TTS 再生) → P2 (mic 録音 + STT) → P3 (Ditto アバターリップシンク) まで全部着地した。
速度の源は 積んできた累積資産の compound interest:
- kotonia.ai の voice / Ditto / TTS / ASR エンドポイントは去年から実装してて熟成済
- kotonia-cli は先月 publish して agent loop が完成、lib として in-process 使い回せる構造になってた
- Iris の絵 (右下のアンドロイド) は 既存の HiDream-O1-Image で別件で生成済
- 認証 (
kotonia-cli loginの device_token) も既存、backend に少し fallback 経路足すだけで全 endpoint 解禁
1 日で「新規に書いた」のは Tauri shell + 各 endpoint への wire-up + UI 配線で、ロジックは全部既存のものの組合せ。個人開発が compound 効くようになる転換点 はここで、過去の蓄積資産が「使う側」じゃなく「使われる側」に立てるかどうか。今回まさにそれが起きた日だった。
着地までの罠 (記録に残す価値あるやつ 3 つ)
1. split_mixed_languages が裏で streaming を kill していた
Qwen3-TTS の python proxy に 「日英混在テキストでは run 単位で分割して各 run の language を切替合成する」 feature が以前から入ってた (英会話学習用キャラのために自分で入れた)。これは正しい挙動 — shell という英単語が英語発音で読まれる。
ところが実装が 「全 run を合成 → PCM 連結 → 最後に 1 chunk で yield」 で、streaming が事実上死んでた。first byte = 全合成時間 になる。Iris の答えは技術用語混じりで常にこのパスを踏むので、毎回数秒の沈黙 → どかっと一気に喋るカクつき UX になってた。
最初は「desktop 側で split_mixed_languages: false を送って streaming パスに逃がす」で workaround、その代わり embedded English は日本語アクセントで読まれる (シェル)。後で python 側で per-run streaming yield に書き換え て根本解決。1 日のうち最大の bug fix。
教訓: 良かれと思って入れた feature flag は default 値の影響範囲を必ず明示。半年後に別 use case で踏む。
2. Ditto アバターの frame burst で顔が途中で freeze
Ditto は GPU 側で frame を生成できる速度で送ってくる (バースト)、一方 audio chunk は AudioContext で 未来時刻にスケジュール される (50 chunk が 100ms で着信、再生は 2 秒に渡って scheduled)。
frame を「受信したら即表示」していたので、200ms で 50 frame 全部表示しきって、その後 audio が 1.5 秒残ってる間 顔がフリーズしていた。
fix は 「wall clock 主軸の FPS pacing」: first frame 着信時に wall clock を pin、それ以降の frame N は pinTime + N/25 sec に setTimeout 遅延表示。バーストは内部 buffer で吸収、表示は audio と同 tempo。
const targetMs = dittoStreamStartMs + (myFrameIndex * 1000) / DITTO_FPS;
const delayMs = Math.max(0, targetMs - performance.now());
setTimeout(() => { /* show frame */ }, delayMs);
20 行未満で直る。本質的に同じ問題は AV stream の lip-sync を扱う全アプリ で起きてる、それなりに汎用性ある教訓。
3. Linux WebKitGTK で踏んだ 3 連
Tauri 2 + Linux は地雷原だった。1 日で 3 つ踏んだ:
- getUserMedia silent deny: mic ボタン押しても何も起きない。WebKitGTK が media permission request を default で silent に reject する仕様 (ブラウザみたいな「マイクを許可しますか?」dialog が出ない、デフォ NO で終了)。Rust 側で
WebKitWebView::permission-requestシグナルに接続して.allow()明示することで解決 - 高頻度 textContent 差し替えで click event が落ちる: 録音中の経過秒数を 100ms ごとに button の textContent で更新してたら、stop ボタンの click が 7-8 割無視される。WebKitGTK の DOM mutation 中に hit-test race している。child span に分離して button 本体を mutate しないように
- 日本語 IME preedit が完全に見えない (これだけ未解決): Wayland session + ibus-mozc で、変換中のひらがなが入力欄に表示されない。
GDK_BACKEND=x11/WEBKIT_FORCE_SANDBOX=0/GTK_IM_MODULE=xim全部試して 1 つも効かず諦め
3 連の教訓: Linux WebView は「何かを silently 落とす」のがデフォと思っとけ。同じ機能でも Linux だけ追加対策が要る。Mac/Windows は普通に動く。
削ったもの
1 日で出すために、以下は意図的に削った:
- React + Vite + TypeScript: 全部 vanilla JS、main.js は 700 行台、ビルドステップなし。本家 kotonia.ai の
useVoiceChat.tsは 1587 行の monolith hook、流用したいピースだけ vanilla で書き直したら 200 行で済んだ - persona picker: 「キャラを選ぶ画面」を作らない、Iris 固定。これで kotonia-desktop は「platform」じゃなく「character」になり、製品 identity が立つ
- VAD (発話終了の自動検出): クリックボタンで明示的に「録音停止」、転写はプロンプト欄に 挿入されるだけ で auto-submit しない。bash の高 stakes 性を考えると、転写ハルシで
rm -rfが走るのを避けるため user review を強制する設計
「機能で reuse しようとせず、API contract で reuse する」が今回の主原則。本家 web 版の 80% は kotonia-desktop の scope に入らない (vision input / 多 persona 切替 / wiki / session resume サーバー側 …)、無理に持ってこない方が結果的に小さく済む。
試す
git clone [email protected]:zhener562/kotonia-cli
git clone [email protected]:zhener562/kotonia-desktop
cd kotonia-desktop/src-tauri
cargo tauri dev
(kotonia-cli を sibling として clone する必要あり、path dep で参照してるので。詳細は repo の README)
事前準備:
kotonia-cli loginで kotonia.ai に device 認証 (LLM / TTS / Ditto / ASR 全部 device_token 1 つで解禁)- Linux なら
libwebkit2gtk-4.1-dev+libdbus-1-dev+ GStreamer audio plugins - macOS / Windows は Tauri 2 prereqs のみ
これから
- 詳細な hotkey 割当 UI (今は click only)
- session 一覧 sidebar
- macOS / Windows code signing
- Iris の Qwen3-TTS Base (zero-shot voice clone) 復旧後、より自然な声に差し替え
- Claude Code エンジンへの切替 UI (現状 lib level では対応済、desktop 側で expose)
ローンチ自体は終わり。毎日の開発や夢への挑戦がほんのすこしだけ楽しくなる相棒、欲しい人は ↑ の clone コマンドで 5 分後には Iris が右下に居る。
