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