feat: WebSocket команды — раздельная громкость mic/snd + запись аудио + обновлённый web-client

- satellite.py: разделён _runtime_volume на _runtime_mic_volume и _runtime_snd_volume
- satellite.py: добавлены команды start_recording/stop_recording (WAV через base64)
- satellite.py: метод _create_wav_from_buffer() для создания WAV из буфера
- web-client: добавлен слайдер громкости микрофона (MIC+/MIC-)
- web-client: добавлены кнопки записи и воспроизведения аудио
- web-client: улучшенный лог событий и отладочные console.log
- docs: websocket-server-plan.md сокращён с 1205 до ~80 строк

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-05-25 17:58:37 +03:00
co-authored by Qwen-Coder
parent 61294485ad
commit 2b4e6f824b
7 changed files with 2017 additions and 1209 deletions
+128
View File
@@ -0,0 +1,128 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>NOVA Voice Assistant</title>
<link rel="stylesheet" href="styles.css">
<script src="script.js" defer></script>
</head>
<body>
<div class="app-container">
<!-- Верхняя панель -->
<header class="app-header">
<div class="logo">
<span class="logo-icon">🎙️</span>
<span class="logo-text">NOVA Assistant</span>
</div>
<div class="connection-status">
<div class="led" id="statusLed"></div>
<span class="status-text" id="statusLabel">disconnected</span>
<button id="connectBtn" class="btn-outline">⟳ Connect</button>
</div>
</header>
<!-- Блок настроек WebSocket -->
<div class="settings-card glass">
<div class="settings-row">
<input type="text" id="wsUrlInput" value="ws://192.168.200.111:8675" placeholder="ws://ip:port" class="input-url">
<button id="connectBtnCard" class="btn-primary">Connect</button>
<label class="auto-connect">
<input type="checkbox" id="autoConnect" checked> Auto-connect
</label>
</div>
</div>
<!-- Основная сетка -->
<div class="dashboard">
<!-- Левая колонка: голосовой аватар -->
<div class="card voice-card glass">
<div class="card-header">
<span class="header-icon">🎤</span>
<h3>Voice Core</h3>
</div>
<div class="avatar-container">
<div class="orb" id="orbCore">
<div class="orb-pulse"></div>
<div class="orb-inner">🎙️</div>
</div>
<div class="state-badge" id="stateText">Idle</div>
</div>
<!-- Визуализатор аудио (волны) -->
<div class="viz-wrapper">
<canvas id="audioCanvas" width="500" height="80"></canvas>
</div>
<!-- RMS-метр -->
<div class="rms-container">
<div class="rms-label">RMS Level</div>
<div class="rms-bar">
<div class="rms-fill" id="rmsFill"></div>
</div>
<div class="rms-value" id="rmsValue">0.00</div>
</div>
<!-- Регулировка громкости -->
<div class="volume-control">
<span>🔊 Volume</span>
<input type="range" id="volumeSlider" min="0" max="1" step="0.01" value="0.8">
<span id="volumeValue" class="volume-percent">80%</span>
</div>
<div class="button-group">
<button id="muteBtn" class="btn-secondary">🔇 Mute</button>
<button id="unmuteBtn" class="btn-secondary">🎤 Unmute</button>
</div>
</div>
<!-- Центр: команды и транскрипция -->
<div class="card commands-card glass">
<div class="card-header">
<span class="header-icon">🕹️</span>
<h3>Commands & Transcripts</h3>
</div>
<div class="button-group">
<button id="startListeningBtn" class="btn-primary">🎧 Start Listen</button>
<button id="stopPipelineBtn" class="btn-danger">⏹️ Stop Pipeline</button>
<button id="stopTimerBtn" class="btn-secondary">⏲️ Stop Timer</button>
</div>
<div class="button-group">
<button id="volUpBtn" class="btn-secondary">➕ Vol+</button>
<button id="volDownBtn" class="btn-secondary">➖ Vol-</button>
<button id="requestRmsBtn" class="btn-secondary">📊 Request RMS</button>
</div>
<div class="button-group">
<button id="singlePressBtn" class="btn-outline">Single</button>
<button id="doublePressBtn" class="btn-outline">Double</button>
<button id="triplePressBtn" class="btn-outline">Triple</button>
<button id="longPressBtn" class="btn-outline">Long</button>
</div>
<div class="transcript-area">
<div class="transcript-item">
<div class="transcript-label">You said:</div>
<div id="lastStt" class="transcript-text">—</div>
</div>
<div class="transcript-item">
<div class="transcript-label">Assistant said:</div>
<div id="lastTts" class="transcript-text">—</div>
</div>
</div>
</div>
<!-- Правая колонка: телеметрия и логи -->
<div class="card telemetry-card glass">
<div class="card-header">
<span class="header-icon">📡</span>
<h3>Telemetry & Events</h3>
</div>
<div class="status-grid">
<div class="status-chip"><span>🔇 Muted:</span> <strong id="mutedStatus">—</strong></div>
<div class="status-chip"><span>🏠 HA:</span> <strong id="haStatus">—</strong></div>
<div class="status-chip"><span>🎚️ Vol:</span> <strong id="liveVol">—</strong></div>
</div>
<div class="event-feed" id="eventFeed">
<div class="event-item">⬤ Ready to connect...</div>
</div>
<button id="clearEvents" class="btn-secondary full-width">Clear log</button>
</div>
</div>
</div>
</body>
</html>