refactor: update web-client files - replace old HTML/JS/CSS with new droidAssist implementation

- Remove old: assist.html, index.html, script.js, styles.css, AI robot.lottie
- Add new: droidAssist.html, droidAssist.css, droidAssist.js, animation.lottie
- Restructure web-client files into html/ directory
This commit is contained in:
2026-05-28 13:19:00 +03:00
parent 8a4686c800
commit 3cc8bb8aef
8 changed files with 702 additions and 1491 deletions
+244
View File
@@ -0,0 +1,244 @@
import { DotLottie } from 'https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-web@0.38.0/+esm';
// ==================== КОНФИГУРАЦИЯ ====================
const WS_URL = 'ws://192.168.200.111:8675';
const NUM_PARTICLES = 30;
const LOTTIE_SRC = 'animation.lottie';
// ==================== СЕГМЕНТЫ АНИМАЦИИ ====================
const segments = {
idle: { start: 0, end: 29 },
yes: { start: 31, end: 105 },
no: { start: 106, end: 180 },
alert: { start: 181, end: 270 },
thinking: { start: 271, end: 390 },
jump: { start: 391, end: 479 }
};
// ==================== СОСТОЯНИЕ ====================
let ws = null;
let isConnected = false;
let currentVolume = 0.8;
let dotLottie = null;
let currentState = 'idle';
let animationReady = false;
// ==================== DOM ====================
const statusDot = document.getElementById('statusDot');
const statusLabel = document.getElementById('statusLabel');
const wrapper = document.getElementById('animationWrapper');
const statusText = document.getElementById('statusText');
const volumeSlider = document.getElementById('volumeSlider');
const volumeValue = document.getElementById('volumeValue');
const volUpBtn = document.getElementById('volUpBtn');
const volDownBtn = document.getElementById('volDownBtn');
const transcriptText = document.getElementById('transcriptText');
// ==================== ЧАСТИЦЫ ====================
function initParticles() {
const particles = document.getElementById('particles');
for (let i = 0; i < NUM_PARTICLES; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.style.left = Math.random() * 100 + '%';
particle.style.animationDelay = Math.random() * 20 + 's';
particle.style.animationDuration = (15 + Math.random() * 10) + 's';
particles.appendChild(particle);
}
}
// ==================== УПРАВЛЕНИЕ АНИМАЦИЕЙ ====================
window.goToState = function(stateName, statusLabelText) {
if (!animationReady || !dotLottie) return;
currentState = stateName;
const seg = segments[stateName];
if (!seg) return;
dotLottie.stop();
dotLottie.setSegment(seg.start, seg.end);
if (stateName === 'idle') {
dotLottie.setSpeed(0.1);
} else {
dotLottie.setSpeed(1);
}
dotLottie.setLoop(stateName === 'idle');
dotLottie.play();
if (statusLabelText !== undefined) {
statusText.textContent = statusLabelText;
}
// Обновляем класс для свечения фона
wrapper.className = `animation-wrapper ${stateName}`;
};
// ==================== МАППИНГ СОБЫТИЙ SATELLITE → АНИМАЦИЯ ====================
function handleSatelliteEvent(eventType, eventData) {
const data = eventData || {};
switch (eventType) {
// ─── yes: СЛУШАЮ, ГОВОРЮ, АКТИВЕН ───
case 'voice-started':
case 'stt-start':
goToState('yes', 'СЛУШАЮ');
break;
case 'tts-start':
case 'audio-start':
goToState('yes', 'ГОВОРЮ');
break;
case 'streaming-started':
goToState('yes', 'АКТИВЕН');
break;
// ─── thinking: ОБРАБОТКА ───
case 'voice-stopped':
case 'stt-stop':
goToState('thinking', 'ОБРАБОТКА');
break;
// ─── idle: В ОЖИДАНИИ ───
case 'played':
case 'tts-stop':
case 'audio-stop':
goToState('idle', 'В ОЖИДАНИИ');
break;
// ─── no: ОШИБКА, СПЛЮ ───
case 'satellite-disconnected':
case 'error':
goToState('no', 'ОШИБКА');
break;
case 'pause-satellite':
goToState('no', 'СПЛЮ...');
break;
// ─── jump: ПРИВЕТ!!! ───
case 'detection':
goToState('jump', 'ПРИВЕТ!!!');
break;
// ─── transcript / synthesize ───
case 'transcript':
if (data.text) {
transcriptText.textContent = `"${data.text}"`;
goToState('yes');
}
break;
case 'synthesize':
if (data.text) {
transcriptText.textContent = `"${data.text}"`;
goToState('yes');
}
break;
// ─── audio-level ───
case 'audio-level':
break;
default:
if (eventType !== 'audio-level') {
console.log('Unhandled event:', eventType, data);
}
break;
}
}
// ==================== WEBSOCKET ====================
function connectWebSocket() {
ws = new WebSocket(WS_URL);
ws.onopen = () => {
isConnected = true;
statusDot.classList.add('connected');
statusLabel.textContent = 'Подключено';
console.log('✅ WebSocket connected');
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
const eventType = data.type || data.event;
const eventData = data.data || {};
if (eventType != "audio-level") {
console.info('EventType:', eventType);
console.info('EventData:', eventData);
}
handleSatelliteEvent(eventType, eventData);
} catch (e) {
console.error('Parse error:', e);
}
};
ws.onerror = (error) => console.error('WebSocket error:', error);
ws.onclose = () => {
isConnected = false;
statusDot.classList.remove('connected');
statusLabel.textContent = 'Отключено';
handleSatelliteEvent('satellite-disconnected');
console.log('❌ WebSocket disconnected');
setTimeout(connectWebSocket, 3000);
};
}
// ==================== ГРОМКОСТЬ ====================
function updateVolume(newVolume) {
currentVolume = Math.max(0, Math.min(1, newVolume));
volumeSlider.value = Math.round(currentVolume * 100);
volumeValue.textContent = Math.round(currentVolume * 100) + '%';
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
command: 'set_volume',
data: { level: currentVolume }
}));
}
}
// ==================== ИНИЦИАЛИЗАЦИЯ ====================
async function init() {
const canvas = document.getElementById('lottie-canvas');
const overlay = document.getElementById('loadingOverlay');
initParticles();
connectWebSocket();
dotLottie = new DotLottie({
canvas: canvas,
src: LOTTIE_SRC,
autoplay: false,
loop: true,
renderConfig: { preserveAspectRatio: 'xMidYMid meet' }
});
dotLottie.addEventListener('load', () => {
animationReady = true;
overlay.classList.add('hidden');
goToState('idle', 'В ОЖИДАНИИ');
});
dotLottie.addEventListener('complete', () => {
if (currentState !== 'idle') {
goToState('idle', 'В ОЖИДАНИИ');
}
});
// Volume controls
volumeSlider.addEventListener('input', (e) => updateVolume(e.target.value / 100));
volUpBtn.addEventListener('click', () => updateVolume(currentVolume + 0.1));
volDownBtn.addEventListener('click', () => updateVolume(currentVolume - 0.1));
}
// Запуск после загрузки страницы
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}