feat: Web Client — улучшенное подключение к WebSocket

Изменения:
1. Поле для ввода WebSocket адреса (вместо prompt)
   - wsUrlInput с значением по умолчанию
   - Сохраняется между перезагрузками

2. Кнопка CONNECT/DISCONNECT
   - Меняет текст и цвет при подключении
   - DISCONNECT красная кнопка
   - Работают обе кнопки (в шапке и в карточке)

3. Auto-connect
   - Чекбокс 'Auto-connect on load'
   - Автоматическое подключение при загрузке
   - Можно отключить

4. updateConnectButton()
   - Обновляет обе кнопки синхронно
   - 🔌 CONNECT / 🔌 DISCONNECT
This commit is contained in:
2026-05-21 19:56:51 +03:00
parent c17743d829
commit 9bfbcfd8a8
+90 -15
View File
@@ -279,7 +279,7 @@
<div class="glass-panel">
<!-- заголовок -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;">
<div style="font-family: 'Orbitron'; font-weight: 700; font-size: 1.6rem; letter-spacing: 2px; background: linear-gradient(135deg, #aaffff, #3b8eff); -webkit-background-clip: text; background-clip: text; color: transparent;">
⚡ Wyoming Droid Assistant ⚡
</div>
@@ -290,6 +290,25 @@
</div>
</div>
<!-- WebSocket Connection Settings -->
<div class="card" style="margin-bottom: 20px;">
<div class="card-header">
<span>🔌 WebSocket Connection</span>
</div>
<div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
<input type="text" id="wsUrlInput" value="ws://192.168.200.111:8675"
style="flex: 1; min-width: 200px; padding: 8px 12px; background: rgba(0,0,0,0.3); border: 1px solid rgba(0,255,255,0.3); border-radius: 8px; color: #eee; font-size: 14px;"
placeholder="ws://ip-address:8675">
<button id="connectBtnCard" class="neo-btn" style="min-width: 120px;">
🔌 CONNECT
</button>
<label class="checkbox-label" style="color: #aaa; cursor: pointer;">
<input type="checkbox" id="autoConnect" checked style="margin-right: 5px;">
Auto-connect on load
</label>
</div>
</div>
<div class="dashboard">
<!-- ЛЕВАЯ КОЛОНКА: Аватар + Аудиовизуализация и Состояние -->
<div class="card avatar-area">
@@ -643,29 +662,49 @@
drawSatelliteVisualization();
// Подключение микрофона для визуализации (опционально) — УДАЛЕНО
// Теперь используем RMS от satellite вместо локального микрофона
// WebSocket подключение
function connectWebSocket() {
const hostInput = prompt("Введите адрес LVA WebSocket (по умолчанию ws://192.168.200.111:8675):", "ws://192.168.200.111:8675");
if(!hostInput) return;
if(ws && ws.readyState === WebSocket.OPEN) ws.close();
ws = new WebSocket(hostInput);
// WebSocket подключение/отключение
function toggleConnection() {
const wsUrl = document.getElementById('wsUrlInput').value.trim();
if(!wsUrl) {
addEventToLog('❌ Введите WebSocket URL');
return;
}
// Если уже подключены — отключаемся
if(ws && ws.readyState === WebSocket.OPEN) {
ws.close();
return;
}
// Подключаемся
if(ws && ws.readyState === WebSocket.CONNECTING) {
ws.close();
return;
}
ws = new WebSocket(wsUrl);
ws.onopen = () => {
isConnected = true;
updateConnectButton();
statusLed.classList.add('connected');
statusLabel.innerText = 'connected';
addEventToLog(`✅ Соединение установлено с ${hostInput}`);
addEventToLog(`✅ Соединение установлено с ${wsUrl}`);
stateText.innerText = 'ONLINE';
orb.style.animation = 'pulse-glow 0.8s';
setTimeout(()=> orb.style.animation='',700);
// initMicVisualizer() — удалено, используем satellite RMS
};
ws.onmessage = handleIncomingMessage;
ws.onerror = (err) => { addEventToLog(`⚠️ WebSocket ошибка`); };
ws.onerror = (err) => {
addEventToLog(`⚠️ WebSocket ошибка`);
};
ws.onclose = () => {
isConnected = false;
updateConnectButton();
statusLed.classList.remove('connected');
statusLabel.innerText = 'disconnected';
addEventToLog(`🔌 Отключено`);
@@ -673,9 +712,45 @@
document.body.classList.remove('listening-active');
};
}
// Обновление кнопки подключения
function updateConnectButton() {
const btn = document.getElementById('connectBtn');
const btnCard = document.getElementById('connectBtnCard');
if(ws && ws.readyState === WebSocket.OPEN) {
if(btn) { btn.innerHTML = '🔌 DISCONNECT'; btn.classList.add('danger-btn'); }
if(btnCard) { btnCard.innerHTML = '🔌 DISCONNECT'; btnCard.classList.add('danger-btn'); }
} else {
if(btn) { btn.innerHTML = '⟳ CONNECT'; btn.classList.remove('danger-btn'); }
if(btnCard) { btnCard.innerHTML = '⟳ CONNECT'; btnCard.classList.remove('danger-btn'); }
}
}
// Кнопки управления
document.getElementById('connectBtn').addEventListener('click', connectWebSocket);
// Кнопки подключения
document.getElementById('connectBtn').addEventListener('click', toggleConnection);
document.getElementById('connectBtnCard').addEventListener('click', toggleConnection);
// Auto-connect при загрузке
window.addEventListener('load', () => {
addEventToLog('🤖 Wyoming Droid Assistant • готов к подключению');
// Инициализация RMS элементов после загрузки DOM
const rmsFillEl = document.getElementById('rmsFill');
const rmsValueEl = document.getElementById('rmsValue');
if (rmsFillEl && rmsValueEl) {
console.log('RMS meter initialized');
}
// Auto-connect если включена опция
const autoConnect = document.getElementById('autoConnect');
if(autoConnect && autoConnect.checked) {
setTimeout(() => {
addEventToLog('📡 Auto-connect...');
toggleConnection();
}, 500);
}
});
// Volume control
document.getElementById('startListeningBtn').addEventListener('click', () => sendCommand('set_volume', { level: 1.0 }));