diff --git a/web-client/client.html b/web-client/client.html index 08e617e..cb4c5e6 100644 --- a/web-client/client.html +++ b/web-client/client.html @@ -428,66 +428,112 @@ addEventToLog(`📤 cmd: ${command}`); } - // Обработчик входящих событий LVA + // Обработчик входящих событий от satellite function handleIncomingMessage(ev) { try { const json = JSON.parse(ev.data); - if(json.event === 'snapshot') { - updateUIFromSnapshot(json.data); - addEventToLog('📸 Снэпшот получен'); + const eventType = json.type || json.event; + + // Audio level events + if(eventType === 'audio-level') { + if(json.data && json.data.level !== undefined) { + updateRmsMeter(json.data.level); + } } - else if(json.event === 'wake_word_detected') { - stateText.innerText = '🔍 WAKE WORD'; - orb.style.animation = 'pulse-glow 0.5s'; - setTimeout(()=> orb.style.animation = '', 500); - addEventToLog('🔥 Пробуждение (wake word)'); - } - else if(json.event === 'listening') { + + // Voice events + else if(eventType === 'voice-started') { stateText.innerText = '🎙️ LISTENING...'; document.body.classList.add('listening-active'); - addEventToLog('🎤 Микрофон активен'); + addEventToLog('🎤 Голос обнаружен'); } - else if(json.event === 'processing') { + else if(eventType === 'voice-stopped') { stateText.innerText = '🧠 PROCESSING'; - addEventToLog('⚙️ Обработка речи...'); + document.body.classList.remove('listening-active'); + addEventToLog('💭 Обработка речи...'); } - else if(json.event === 'tts_start') { + + // Transcript events + else if(eventType === 'transcript') { + if(json.data && json.data.text) { + lastSttSpan.innerText = json.data.text; + addEventToLog(`🎤 "${json.data.text}"`); + } + } + + // TTS events + else if(eventType === 'synthesize') { + if(json.data && json.data.text) { + lastTtsSpan.innerText = json.data.text; + } + } + else if(eventType === 'audio-start') { stateText.innerText = '🔊 SPEAKING'; addEventToLog('🗣️ Ассистент отвечает (TTS)'); } - else if(json.event === 'tts_end') { + else if(eventType === 'audio-stop' || eventType === 'played') { if(stateText.innerText === '🔊 SPEAKING') stateText.innerText = 'IDLE'; addEventToLog('🔇 TTS завершён'); } - else if(json.event === 'timer_ringing') { + + // Timer events + else if(eventType === 'timer-started') { + addEventToLog('⏱️ Таймер запущен'); + } + else if(eventType === 'timer-finished') { addEventToLog('⏰ ТАЙМЕР СРАБОТАЛ!'); stateText.innerText = '⏰ TIMER'; } - else if(json.event === 'stt_text') { - if(json.data && json.data.text) lastSttSpan.innerText = json.data.text; - addEventToLog(`🎤 Распознано: ${json.data?.text || ''}`); + + // State events + else if(eventType === 'streaming-started') { + stateText.innerText = '🎙️ STREAMING'; + addEventToLog('🔄 Стриминг начался'); } - else if(json.event === 'tts_text') { - if(json.data && json.data.text) lastTtsSpan.innerText = json.data.text; + else if(eventType === 'streaming-stopped') { + if(stateText.innerText === '🎙️ STREAMING') stateText.innerText = 'IDLE'; + addEventToLog('⏹️ Стриминг остановлен'); } - else if(json.event === 'state_change') { - if(json.data?.state) stateText.innerText = json.data.state.toUpperCase(); - addEventToLog(`🔄 состояние: ${json.data?.state}`); + + // Satellite state + else if(eventType === 'satellite-connected') { + haConnected = true; + haStatusSpan.innerText = '⚡ connected'; + addEventToLog('🔌 Satellite подключён'); } + else if(eventType === 'satellite-disconnected') { + haConnected = false; + haStatusSpan.innerText = '⚠️ offline'; + addEventToLog('🔌 Satellite отключён'); + } + + // Errors + else if(eventType === 'error') { + addEventToLog(`❌ Ошибка: ${json.data?.message || 'неизвестная'}`); + } + + // Unknown events else { - addEventToLog(`📡 событие: ${json.event}`); + addEventToLog(`📡 событие: ${eventType}`); } - if(json.data?.volume !== undefined) { - currentVolume = json.data.volume; - volumeSlider.value = currentVolume; - volumeValueSpan.innerText = currentVolume.toFixed(2); - liveVolSpan.innerText = Math.round(currentVolume*100)+'%'; + + // Update UI from event data + if(json.data) { + if(json.data.volume !== undefined) { + currentVolume = json.data.volume; + volumeSlider.value = currentVolume; + volumeValueSpan.innerText = currentVolume.toFixed(2); + liveVolSpan.innerText = Math.round(currentVolume*100)+'%'; + } + if(json.data.muted !== undefined) { + mutedState = json.data.muted; + mutedStatusSpan.innerText = mutedState ? '🔇 muted' : '🎤 unmuted'; + } } - if(json.data?.muted !== undefined) { - mutedState = json.data.muted; - mutedStatusSpan.innerText = mutedState ? '🔇 muted' : '🎤 unmuted'; - } - } catch(e) { console.warn(e); } + } catch(e) { + console.warn('Parse error:', e); + addEventToLog(`⚠️ Ошибка парсинга: ${e.message}`); + } } // Визуализация микрофона (если доступен) — рисуем абстракцию ритма @@ -580,28 +626,49 @@ }; } - // кнопки управления + // Кнопки управления document.getElementById('connectBtn').addEventListener('click', connectWebSocket); - document.getElementById('startListeningBtn').addEventListener('click', () => sendCommand('start_listening')); - document.getElementById('stopPipelineBtn').addEventListener('click', () => sendCommand('stop_pipeline')); - document.getElementById('stopTimerBtn').addEventListener('click', () => sendCommand('stop_timer_ringing')); - document.getElementById('muteBtn').addEventListener('click', () => sendCommand('mute_microphone')); - document.getElementById('unmuteBtn').addEventListener('click', () => sendCommand('unmute_microphone')); - document.getElementById('volUpBtn').addEventListener('click', () => sendCommand('volume_up')); - document.getElementById('volDownBtn').addEventListener('click', () => sendCommand('volume_down')); - document.getElementById('pauseMediaBtn').addEventListener('click', () => sendCommand('pause_media_player')); - document.getElementById('resumeMediaBtn').addEventListener('click', () => sendCommand('resume_media_player')); - document.getElementById('stopMediaBtn').addEventListener('click', () => sendCommand('stop_media_player')); - document.getElementById('singlePressBtn').addEventListener('click', () => sendCommand('single_press')); - document.getElementById('doublePressBtn').addEventListener('click', () => sendCommand('double_press')); - document.getElementById('triplePressBtn').addEventListener('click', () => sendCommand('triple_press')); - document.getElementById('longPressBtn').addEventListener('click', () => sendCommand('long_press')); - document.getElementById('clearEvents').addEventListener('click', () => { while(eventFeedDiv.children.length > 1) eventFeedDiv.removeChild(eventFeedDiv.lastChild); addEventToLog('🧹 Лог очищен'); }); + + // Volume control + document.getElementById('startListeningBtn').addEventListener('click', () => sendCommand('set_volume', { level: 1.0 })); + document.getElementById('stopPipelineBtn').addEventListener('click', () => sendCommand('mute', {})); + document.getElementById('stopTimerBtn').addEventListener('click', () => sendCommand('unmute', {})); + document.getElementById('muteBtn').addEventListener('click', () => sendCommand('mute', {})); + document.getElementById('unmuteBtn').addEventListener('click', () => sendCommand('unmute', {})); + + document.getElementById('volUpBtn').addEventListener('click', () => { + const newVol = Math.min(1.0, currentVolume + 0.1); + sendCommand('set_volume', { level: newVol }); + volumeSlider.value = newVol; + volumeValueSpan.innerText = newVol.toFixed(2); + }); + + document.getElementById('volDownBtn').addEventListener('click', () => { + const newVol = Math.max(0.0, currentVolume - 0.1); + sendCommand('set_volume', { level: newVol }); + volumeSlider.value = newVol; + volumeValueSpan.innerText = newVol.toFixed(2); + }); + + document.getElementById('pauseMediaBtn').addEventListener('click', () => sendCommand('play_sound', { path: '/home/sirmrak/projects/satellite-dev/sounds/awake.wav' })); + document.getElementById('resumeMediaBtn').addEventListener('click', () => sendCommand('play_sound', { path: '/home/sirmrak/projects/satellite-dev/sounds/done.wav' })); + document.getElementById('stopMediaBtn').addEventListener('click', () => sendCommand('trigger_event', { event_type: 'stop', data: {} })); + + document.getElementById('singlePressBtn').addEventListener('click', () => sendCommand('trigger_event', { event_type: 'single_press', data: {} })); + document.getElementById('doublePressBtn').addEventListener('click', () => sendCommand('trigger_event', { event_type: 'double_press', data: {} })); + document.getElementById('triplePressBtn').addEventListener('click', () => sendCommand('trigger_event', { event_type: 'triple_press', data: {} })); + document.getElementById('longPressBtn').addEventListener('click', () => sendCommand('trigger_event', { event_type: 'long_press', data: {} })); + + document.getElementById('clearEvents').addEventListener('click', () => { + while(eventFeedDiv.children.length > 1) eventFeedDiv.removeChild(eventFeedDiv.lastChild); + addEventToLog('🧹 Лог очищен'); + }); + volumeSlider.addEventListener('input', (e) => { const val = parseFloat(e.target.value); volumeValueSpan.innerText = val.toFixed(2); currentVolume = val; - sendCommand('set_volume', { volume: val }); + sendCommand('set_volume', { level: val }); }); // автоподключение при загрузке (опционально) diff --git a/examples/websocket-service/start_web_server.py b/web-client/start_server.py similarity index 100% rename from examples/websocket-service/start_web_server.py rename to web-client/start_server.py