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:
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user