沈黙ブレイカーくん


OEC
メンバー
user
おいし もとき
@motokioishi
作品ページ
``` --- ### Step 3: JavaScriptファイル作成 `public/meeting.js` を作成: ```javascript // ========== 設定 ========== const SILENCE_THRESHOLD = 5000; // 5秒無音で発火 const VOLUME_THRESHOLD = 0.01; // 音量閾値 const POLL_INTERVAL = 2000; // 2秒ごとにチェック // ========== 状態管理 ========== let silenceTimer = null; let audioContext = null; let analyser = null; let roomId = 'room-' + Date.now(); let isProcessing = false; // ========== 初期化 ========== async function init() { updateStatus('🎙️ マイクアクセス要求中...'); document.getElementById('roomId').textContent = roomId; try { const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } }); setupAudioAnalysis(stream); updateStatus('✅ 沈黙検知中(5秒無音でAI発動)'); startPolling(); } catch (err) { console.error('Microphone error:', err); updateStatus('❌ マイクアクセス失敗: ' + err.message); } } // ========== 音声解析セットアップ ========== function setupAudioAnalysis(stream) { audioContext = new (window.AudioContext || window.webkitAudioContext)(); analyser = audioContext.createAnalyser(); const source = audioContext.createMediaStreamSource(stream); source.connect(analyser); analyser.fftSize = 256; analyser.smoothingTimeConstant = 0.8; const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); // 音量チェックループ function checkVolume() { analyser.getByteFrequencyData(dataArray); // 平均音量を計算(0-1の範囲) const average = dataArray.reduce((a, b) => a + b) / bufferLength / 255; // 音量バーを更新 document.getElementById('volumeLevel').style.width = (average * 100) + '%'; if (average < VOLUME_THRESHOLD) { // 無音状態 if (!silenceTimer && !isProcessing) { console.log('🤫 沈黙開始...'); silenceTimer = setTimeout(() => { onSilenceDetected(); }, SILENCE_THRESHOLD); } } else { // 音声あり if (silenceTimer) { console.log('🗣️ 音声検知、タイマーリセット'); clearTimeout(silenceTimer); silenceTimer = null; } } requestAnimationFrame(checkVolume); } checkVolume(); } // ========== 沈黙検知時の処理 ========== async function onSilenceDetected() { if (isProcessing) { console.log('⏳ 処理中のためスキップ'); return; } isProcessing = true; console.log('🚨 沈黙を検知!サーバーに通知します'); updateStatus('🤫 沈黙検知!Gemini AIに相談中...'); try { const response = await fetch('/api/silence', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ roomId: roomId, event: 'silence', silenceSec: SILENCE_THRESHOLD / 1000, timestamp: Date.now() }) }); const result = await response.json(); console.log('✅ サーバー応答:', result); if (result.sent === false) { updateStatus('⏳ クールダウン中(10秒待機)'); } else if (result.text) { showOverlay(result.text); } } catch (err) { console.error('❌ 通知エラー:', err); updateStatus('❌ 通知エラー: ' + err.message); } finally { silenceTimer = null; setTimeout(() => { isProcessing = false; }, 2000); } } // ========== メッセージをポーリング(予備) ========== function startPolling() { // 将来的にWebSocketやSSEで置き換え可能 console.log('📡 ポーリング開始'); } // ========== オーバーレイ表示 ========== function showOverlay(text) { const overlay = document.getElementById('overlay'); overlay.textContent = text; overlay.style.display = 'block'; updateStatus('💬 Gemini AI発言: ' + text); // 音声読み上げ(ブラウザのTTS) if ('speechSynthesis' in window) { const utterance = new SpeechSynthesisUtterance(text); utterance.lang = 'ja-JP'; utterance.rate = 1.0; speechSynthesis.speak(utterance); } // 5秒後に消す setTimeout(() => { overlay.style.display = 'none'; updateStatus('✅ 沈黙検知中(次の沈黙を待機)'); }, 5000); } // ========== ステータス更新 ========== function updateStatus(message) { const statusEl = document.getElementById('status'); statusEl.textContent = message; console.log('[Status]', message); } // ========== エラーハンドリング ========== window.addEventListener('error', (event) => { console.error('Global error:', event.error); updateStatus('❌ エラー: ' + event.error.message); }); // ========== ページ読み込み時に開始 ========== window.addEventListener('load', () => { console.log('🚀 沈黙ブレイカー起動'); console.log('🤖 AI: Google Gemini 1.5 Flash'); init(); }); ``` --- ## ✅ 動作確認(15分) ### Step 1: サーバー起動 ```bash # ターミナル1: サーバー npm start # 出力例: # 🚀 沈黙ブレイカーサーバー起動 # 📡 ポート: 3000 # 🌐 URL: http://localhost:3000 # ✅ Gemini API: 設定済み # ✅ Vonage API: 設定済み ``` ```bash # ターミナル2: ngrok ngrok http 3000 # 表示されたURLを.envに設定 ``` --- ### Step 2: ブラウザでアクセス ``` http://localhost:3000 ``` --- ### Step 3: テストシナリオ #### テスト1: マイク許可 1. ブラウザがマイク許可を求める 2. 「許可」をクリック 3. Status: "✅ 沈黙検知中" と表示 4. 音量バーが動けばOK #### テスト2: 沈黙検知 1. 5秒間何も話さない(PCの音も消す) 2. Status: "🤫 沈黙検知!Gemini AIに相談中..." に変わる 3. サーバーログに以下が表示: ``` 🤫 沈黙開始... 🚨 沈黙検知!Gemini AIで台詞を生成します 💬 Gemini応答: そろそろ意見を聞きたいですね 🤔 ``` 4. 画面中央にAIの一言が表示される 5. ブラウザが音声で読み上げる #### テスト3: レート制限 1. すぐにもう一度5秒沈黙 2. Status: "⏳ クールダウン中(10秒待機)" と表示 3. サーバーログ: "⏳ クールダウン中、スキップ" --- ### Step 4: Gemini API使用状況確認 1. https://aistudio.google.com/app/apikey にアクセス 2. 使用量が増えていることを確認 3. 無料枠(1,500リクエスト/月)の範囲内か確認 --- ## 🐛 トラブルシューティング ### エラー1: Gemini API エラー **症状:** ``` ❌ Gemini エラー: API_KEY_INVALID ``` **解決策:** 1. `.env` のGEMINI_API_KEYが正しいか確認 2. https://aistudio.google.com/app/apikey でキーを再確認 3. キーの先頭に`AIzaSy`があるか確認 --- ### エラー2: マイクが認識されない **症状:** ``` ❌ マイクアクセス失敗: Permission denied ``` **解決策:** 1. HTTPSまたはlocalhostで開いているか確認 2. ブラウザのマイク設定を確認 - Chrome: `chrome://settings/content/microphone` 3. 他のアプリがマイクを使用していないか確認 --- ### エラー3: Vonage private.key not found **症状:** ``` Error: ENOENT: no such file or directory, open './private.key' ``` **解決策:** ```bash # private.keyがプロジェクトルートにあるか確認 ls -la private.key # なければVonageからダウンロードし直す ``` --- ### エラー4: ngrok URLが期限切れ **症状:** Webhookが届かない **解決策:** ```bash # ngrokは8時間で期限切れ # 新しいURLを取得 ngrok http 3000 # .envとVonageダッシュボードを更新 ``` --- ## 💰 コスト試算 ### 月間1,000回使用の場合 | 項目 | 料金 | |------|------| | **Gemini API** | **完全無料**(1,500回/月まで) | | Vonage初回クレジット | €2(約$2) | | **合計** | **$0〜$2** | ### 完全無料で実現可能! ✅ Gemini: 月1,500リクエスト無料 ✅ 開発環境: 全て無料ツール ✅ Vonage: 初回クレジットで数百回テスト可能 --- ## 🚀 次のステップ 完成後のオプション機能: - [ ] Vonage Video APIと統合(実際のビデオ会議) - [ ] 感情分析(ポジティブ/ネガティブ判定) - [ ] 会議の文脈を考慮(議題に沿った発言) - [ ] 発言履歴のダッシュボード - [ ] Slack通知機能 - [ ] 複数言語対応 --- ## 📚 参考リンク - **Gemini API**: https://ai.google.dev/tutorials/get_started_web - **Vonage Voice API**: https://developer.vonage.com/voice/voice-api/overview - **Vonage Video API**: https://tokbox.com/developer/guides/ --- **実装完了です!🎉** 質問や詰まった箇所があればお知らせください!