class DeviceControlApp { constructor() { this.model = null; this.isModelLoading = false; this.currentPattern = null; this.animationFrame = null; this.initializeElements(); this.attachEventListeners(); this.initializeModel(); } initializeElements() { // Command processing this.commandInput = document.getElementById('commandInput'); this.processCommandBtn = document.getElementById('processCommand'); this.commandResult = document.getElementById('commandResult'); this.patternOutput = document.getElementById('patternOutput'); // Pattern generator this.patternType = document.getElementById('patternType'); this.duration = document.getElementById('duration'); this.durationValue = document.getElementById('durationValue'); this.intensity = document.getElementById('intensity'); this.intensityValue = document.getElementById('intensityValue'); this.generatePatternBtn = document.getElementById('generatePattern'); this.generatedPattern = document.getElementById('generatedPattern'); this.patternCanvas = document.getElementById('patternCanvas'); this.patternStats = document.getElementById('patternStats'); // Status this.statusIndicator = document.getElementById('statusIndicator'); this.statusText = document.getElementById('statusText'); this.modelStatus = document.getElementById('modelStatus'); this.loadingBar = document.getElementById('loadingBar'); this.loadingProgress = document.getElementById('loadingProgress'); this.loadingText = document.getElementById('loadingText'); // Safety settings this.maxIntensity = document.getElementById('maxIntensity'); this.autoStop = document.getElementById('autoStop'); this.gradualChanges = document.getElementById('gradualChanges'); } attachEventListeners() { this.processCommandBtn.addEventListener('click', () => this.processCommand()); this.generatePatternBtn.addEventListener('click', () => this.generatePattern()); this.duration.addEventListener('input', (e) => { this.durationValue.textContent = e.target.value; }); this.intensity.addEventListener('input', (e) => { this.intensityValue.textContent = e.target.value; }); // Safety settings this.maxIntensity.addEventListener('change', () => this.updateSafetyLimits()); this.autoStop.addEventListener('change', () => this.updateSafetyLimits()); this.gradualChanges.addEventListener('change', () => this.updateSafetyLimits()); // Enter key for command input this.commandInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.ctrlKey) { this.processCommand(); } }); } async initializeModel() { if (this.isModelLoading) return; this.isModelLoading = true; this.updateStatus('loading', 'Loading AI model...'); this.showLoadingBar(true); try { // Use a text generation model for natural language processing const modelName = 'Xenova/distilgpt2'; // Lightweight model for fast loading this.model = await pipeline('text-generation', modelName, { progress_callback: (progress) => { const percentage = Math.round(progress.progress * 100); this.updateLoadingProgress(percentage, `Loading model: ${percentage}%`); } }); this.modelStatus.textContent = `Model: ${modelName}`; this.updateStatus('ready', 'AI model loaded successfully'); this.enableButtons(true); } catch (error) { console.error('Error loading model:', error); this.updateStatus('error', 'Failed to load AI model'); this.modelStatus.textContent = 'Model: Offline'; // Enable basic functionality without AI this.enableButtons(true); } finally { this.isModelLoading = false; this.showLoadingBar(false); } } async processCommand() { const command = this.commandInput.value.trim(); if (!command) { this.showNotification('Please enter a command', 'warning'); return; } this.updateStatus('processing', 'Processing command...'); this.processCommandBtn.disabled = true; try { let pattern; if (this.model) { // Use AI to interpret natural language const prompt = `Convert this command to device pattern parameters: "${command}". Format as JSON with intensity, pattern, duration fields.`; const result = await this.model(prompt, { max_new_tokens: 100, temperature: 0.7, do_sample: true, }); pattern = this.parseAIResponse(result[0].generated_text); } else { // Fallback to rule-based parsing pattern = this.parseCommandRuleBased(command); } // Apply safety limits pattern = this.applySafetyLimits(pattern); // Display result this.displayPattern(pattern); this.commandResult.classList.remove('hidden'); // Visualize pattern this.visualizePattern(pattern); this.updateStatus('success', 'Command processed successfully'); } catch (error) { console.error('Error processing command:', error); this.updateStatus('error', 'Failed to process command'); this.showNotification('Error processing command', 'error'); } finally { this.processCommandBtn.disabled = false; } } parseAIResponse(response) { try { // Extract JSON from the response const jsonMatch = response.match(/\{[^}]+\}/); if (jsonMatch) { return JSON.parse(jsonMatch[0]); } } catch (e) { console.error('Failed to parse AI response:', e); } // Fallback to default pattern return this.getDefaultPattern(); } parseCommandRuleBased(command) { const lowerCommand = command.toLowerCase(); const pattern = this.getDefaultPattern(); // Parse intensity if (lowerCommand.includes('high') || lowerCommand.includes('strong')) { pattern.intensity = 80; } else if (lowerCommand.includes('low') || lowerCommand.includes('gentle') || lowerCommand.includes('soft')) { pattern.intensity = 30; } else if (lowerCommand.includes('medium')) { pattern.intensity = 50; } // Parse pattern type if (lowerCommand.includes('wave')) { pattern.pattern = 'wave'; } else if (lowerCommand.includes('pulse')) { pattern.pattern = 'pulse'; } else if (lowerCommand.includes('ramp')) { pattern.pattern = 'ramp'; } else if (lowerCommand.includes('random')) { pattern.pattern = 'random'; } // Parse duration const durationMatch = command.match(/(\d+)\s*(second|minute|min|s|m)/i); if (durationMatch) { let value = parseInt(durationMatch[1]); if (durationMatch[2].toLowerCase().startsWith('m')) { value *= 60; } pattern.duration = Math.min(Math.max(value, 5), 60); } return pattern; } getDefaultPattern() { return { intensity: parseInt(this.intensity.value), pattern: this.patternType.value, duration: parseInt(this.duration.value) }; } generatePattern() { this.updateStatus('generating', 'Generating pattern...'); const pattern = { intensity: parseInt(this.intensity.value), pattern: this.patternType.value, duration: parseInt(this.duration.value) }; // Apply safety limits const safePattern = this.applySafetyLimits(pattern); // Generate pattern data const patternData = this.generatePatternData(safePattern); // Visualize this.visualizePatternData(patternData, safePattern); this.generatedPattern.classList.remove('hidden'); this.updateStatus('success', 'Pattern generated successfully'); } generatePatternData(pattern) { const steps = pattern.duration * 10; // 10 steps per second const data = []; for (let i = 0; i < steps; i++) { let value = 0; const t = i / steps; switch (pattern.pattern) { case 'wave': value = Math.sin(t * Math.PI * 4) * pattern.intensity; break; case 'pulse': value = (Math.sin(t * Math.PI * 8) > 0) ? pattern.intensity : 0; break; case 'ramp': value = t * pattern.intensity; break; case 'random': value = Math.random() * pattern.intensity; break; case 'custom': // Generate interesting custom pattern value = (Math.sin(t * Math.PI * 6) * 0.5 + Math.sin(t * Math.PI * 13) * 0.3) * pattern.intensity; break; } data.push(Math.max(0, Math.min(100, value + pattern.intensity / 2))); } return data; } visualizePattern(pattern) { const patternData = this.generatePatternData(pattern); this.visualizePatternData(patternData, pattern); this.generatedPattern.classList.remove('hidden'); } visualizePatternData(data, pattern) { const ctx = this.patternCanvas.getContext('2d'); const width = this.patternCanvas.width; const height = this.patternCanvas.height; // Clear canvas ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'; ctx.fillRect(0, 0, width, height); // Draw grid ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; ctx.lineWidth = 1; for (let i = 0; i <= 4; i++) { const y = (height / 4) * i; ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(width, y); ctx.stroke(); } // Draw pattern ctx.strokeStyle = 'rgba(168, 85, 247, 0.8)'; ctx.lineWidth = 2; ctx.beginPath(); data.forEach((value, index) => { const x = (index / data.length) * width; const y = height - (value / 100) * height; if (index === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } }); ctx.stroke(); // Add glow effect ctx.shadowColor = 'rgba(168, 85, 247, 0.5)'; ctx.shadowBlur = 10; ctx.stroke(); // Update stats const avgIntensity = data.reduce((a, b) => a + b, 0) / data.length; const maxIntensity = Math.max(...data); const minIntensity = Math.min(...data); this.patternStats.textContent = `Avg: ${avgIntensity.toFixed(1)}% | Max: ${maxIntensity.toFixed(1)}% | Min: ${minIntensity.toFixed(1)}%`; } applySafetyLimits(pattern) { const safePattern = { ...pattern }; // Apply max intensity limit if (this.maxIntensity.checked) { safePattern.intensity = Math.min(safePattern.intensity, 80); } // Apply duration limit if (this.autoStop.checked) { safePattern.duration = Math.min(safePattern.duration, 30); } // Ensure minimum values safePattern.intensity = Math.max(safePattern.intensity, 0); safePattern.duration = Math.max(safePattern.duration, 5); return safePattern; } updateSafetyLimits() { // Update intensity display if max limit is enabled if (this.maxIntensity.checked) { const currentIntensity = parseInt(this.intensity.value); if (currentIntensity > 80) { this.intensity.value = 80; this.intensityValue.textContent = 80; } this.intensity.max = 80; } else { this.intensity.max = 100; } // Update duration display if auto-stop is enabled if (this.autoStop.checked) { const currentDuration = parseInt(this.duration.value); if (currentDuration > 30) { this.duration.value = 30; this.durationValue.textContent = 30; } this.duration.max = 30; } else { this.duration.max = 60; } } displayPattern(pattern) { this.patternOutput.textContent = JSON.stringify(pattern, null, 2); } updateStatus(type, message) { this.statusText.textContent = message; // Update indicator color this.statusIndicator.className = 'w-3 h-3 rounded-full '; switch (type) { case 'ready': this.statusIndicator.className += 'bg-green-400 animate-pulse'; break; case 'loading': case 'processing': case 'generating': this.statusIndicator.className += 'bg-yellow-400 animate-pulse'; break; case 'error': this.statusIndicator.className += 'bg-red-400'; break; case 'success': this.statusIndicator.className += 'bg-blue-400 animate-pulse'; break; } } showLoadingBar(show) { if (show) { this.loadingBar.classList.remove('hidden'); } else { this.loadingBar.classList.add('hidden'); } } updateLoadingProgress(percentage, text) { this.loadingProgress.style.width = `${percentage}%`; this.loadingText.textContent = text; } enableButtons(enabled) { this.processCommandBtn.disabled = !enabled; this.generatePatternBtn.disabled = !enabled; } showNotification(message, type = 'info') { // Create notification element const notification = document.createElement('div'); notification.className = `fixed top-4 right-4 p-4 rounded-lg shadow-lg transform transition-all duration-300 z-50`; // Style based on type switch (type) { case 'error': notification.className += ' bg-red-500 text-white'; break; case 'warning': notification.className += ' bg-yellow-500 text-white'; break; case 'success': notification.className += ' bg-green-500 text-white'; break; default: notification.className += ' bg-blue-500 text-white'; } notification.textContent = message; document.body.appendChild(notification); // Animate in setTimeout(() => { notification.style.transform = 'translateX(0)'; }, 10); // Remove after 3 seconds setTimeout(() => { notification.style.transform = 'translateX(400px)'; setTimeout(() => { document.body.removeChild(notification); }, 300); }, 3000); } } // Toggle checkbox styles const style = document.createElement('style'); style.textContent = ` .toggle-checkbox { appearance: none; width: 44px; height: 24px; background: rgba(255, 255, 255, 0.2); border-radius: 12px; position: relative; cursor: pointer; transition: background 0.3s; } .toggle-checkbox:checked { background: linear-gradient(to right, #9333ea, #ec4899); } .toggle-checkbox::after { content: ''; position: absolute; width: 20px; height: 20px; background: white; border-radius: 50%; top: 2px; left: 2px; transition: transform 0.3s; } .toggle-checkbox:checked::after { transform: translateX(20px); } `; document.head.appendChild(style); // Initialize app when DOM is ready document.addEventListener('DOMContentLoaded', () => { new DeviceControlApp(); });