2026-01-31 音声通知統合
音声通知統合 検討メモ
概要
Ghostrunnerの処理(plan/command実行)完了時に、OpenAI Realtime APIを使って音声で通知する機能。通知後も音声で対話を続けられる。
要件
- トリガー: 処理完了時 + エラー発生時
- 通知内容: 状況に応じた音声応答 + 対話可能
- UI: メインページにトグル追加
- 接続タイミング: トグルONで即接続
案A: MVP(推奨)
概要
メインページにシンプルなトグルを追加し、既存の useOpenAIRealtime フックを活用。
UI設計
[Project path] [Command dropdown] [Args input]
[PR workflow toggle] [音声通知 toggle] ← 新規追加
[Submit button]
トグルON時:
- Realtime APIに自動接続
- 処理完了/エラー時に音声で通知
- マイクボタンを押すと対話モードに移行
実装方針
-
フック改修:
useOpenAIRealtimeに「テキスト送信」機能を追加sendText(message: string)で任意のテキストをAIに送信- AIが音声で応答
-
ページ統合:
page.tsxでフックを使用handleStreamEventのcomplete/errorでsendText呼び出し- 完了時:
「処理が完了しました。結果は ${summary} です。詳細を聞きますか?」 - エラー時:
「エラーが発生しました。${error}。どうしますか?」
-
UIコンポーネント: トグル + 状態表示
- 接続状態インジケーター(小さいドット)
- マイクボタン(対話したい時に押す)
コンポーネント構成
page.tsx
├── CommandForm
│ └── VoiceNotificationToggle(新規)
├── ProgressContainer
└── VoiceNotificationWidget(新規)
├── 接続状態表示
├── マイクボタン
└── useOpenAIRealtime
エフェメラルキー有効期限対策
OpenAIのエフェメラルキーは発行から約1分で失効。対策:
-
WebSocket切断時に自動再接続
oncloseイベントで新しいトークンを取得して再接続- 指数バックオフで最大3回リトライ
-
接続維持用のping(オプション)
- 30秒ごとに空のメッセージを送信して接続維持
変更ファイル一覧
| ファイル | 変更内容 |
|---|---|
frontend/src/hooks/useOpenAIRealtime.ts | sendText 関数追加、自動再接続ロジック |
frontend/src/components/VoiceNotificationWidget.tsx | 新規作成: トグル + 状態表示 + マイク |
frontend/src/app/page.tsx | ウィジェット統合、完了/エラー時の通知呼び出し |
実装ステップ
useOpenAIRealtimeにsendText関数を追加- 自動再接続ロジックを追加
VoiceNotificationWidgetコンポーネント作成page.tsxに統合- 完了/エラー時の通知メッセージ実装
案B: 高機能版(将来拡張)
案Aの拡張として、以下を追加:
- フローティングウィジェット: 画面隅に常時表示
- 音声コマンド: 「承認して」「キャンセル」などの音声操作
- 会話履歴: 過去の通知と会話を表示
- 複数AI対応: OpenAI / Gemini の切り替え
懸念点と対策
| 懸念点 | 対策 |
|---|---|
| エフェメラルキーの有効期限(約1分) | WebSocket切断時に自動再接続 |
| 常時接続のリソース消費 | トグルOFFで完全切断 |
| マイク権限 | 最初は音声出力のみ、マイクボタン押下で入力開始 |
| API料金 | 待機中は会話がないのでほぼゼロ、通知時のみ課金 |
| ブラウザバックグラウンド時 | WebSocket維持、ただし音声再生は制限される可能性 |
通知メッセージ例
処理完了時
処理が完了しました。PRを作成しました。URLは xxx です。
何か質問がありますか?
エラー発生時
エラーが発生しました。ビルドに失敗したようです。
詳細を確認しますか?
質問待ち状態
Claudeから質問があります。
「認証方式はどれにしますか?OAuth、JWT、セッションから選んでください」
どれにしますか?
次のステップ
/planで詳細な実装計画を作成useOpenAIRealtimeの改修- UIコンポーネント実装
- 統合テスト
参考
- 現在の処理完了検知:
page.tsxのhandleStreamEventでcompleteイベント - 既存の音声AI:
/openai-realtimeページ、useOpenAIRealtimeフック