Spaces:
Running
Running
Upload index.js with huggingface_hub
Browse files
index.js
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class DeviceControlApp {
|
| 2 |
+
constructor() {
|
| 3 |
+
this.model = null;
|
| 4 |
+
this.isModelLoading = false;
|
| 5 |
+
this.currentPattern = null;
|
| 6 |
+
this.animationFrame = null;
|
| 7 |
+
|
| 8 |
+
this.initializeElements();
|
| 9 |
+
this.attachEventListeners();
|
| 10 |
+
this.initializeModel();
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
initializeElements() {
|
| 14 |
+
// Command processing
|
| 15 |
+
this.commandInput = document.getElementById('commandInput');
|
| 16 |
+
this.processCommandBtn = document.getElementById('processCommand');
|
| 17 |
+
this.commandResult = document.getElementById('commandResult');
|
| 18 |
+
this.patternOutput = document.getElementById('patternOutput');
|
| 19 |
+
|
| 20 |
+
// Pattern generator
|
| 21 |
+
this.patternType = document.getElementById('patternType');
|
| 22 |
+
this.duration = document.getElementById('duration');
|
| 23 |
+
this.durationValue = document.getElementById('durationValue');
|
| 24 |
+
this.intensity = document.getElementById('intensity');
|
| 25 |
+
this.intensityValue = document.getElementById('intensityValue');
|
| 26 |
+
this.generatePatternBtn = document.getElementById('generatePattern');
|
| 27 |
+
this.generatedPattern = document.getElementById('generatedPattern');
|
| 28 |
+
this.patternCanvas = document.getElementById('patternCanvas');
|
| 29 |
+
this.patternStats = document.getElementById('patternStats');
|
| 30 |
+
|
| 31 |
+
// Status
|
| 32 |
+
this.statusIndicator = document.getElementById('statusIndicator');
|
| 33 |
+
this.statusText = document.getElementById('statusText');
|
| 34 |
+
this.modelStatus = document.getElementById('modelStatus');
|
| 35 |
+
this.loadingBar = document.getElementById('loadingBar');
|
| 36 |
+
this.loadingProgress = document.getElementById('loadingProgress');
|
| 37 |
+
this.loadingText = document.getElementById('loadingText');
|
| 38 |
+
|
| 39 |
+
// Safety settings
|
| 40 |
+
this.maxIntensity = document.getElementById('maxIntensity');
|
| 41 |
+
this.autoStop = document.getElementById('autoStop');
|
| 42 |
+
this.gradualChanges = document.getElementById('gradualChanges');
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
attachEventListeners() {
|
| 46 |
+
this.processCommandBtn.addEventListener('click', () => this.processCommand());
|
| 47 |
+
this.generatePatternBtn.addEventListener('click', () => this.generatePattern());
|
| 48 |
+
|
| 49 |
+
this.duration.addEventListener('input', (e) => {
|
| 50 |
+
this.durationValue.textContent = e.target.value;
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
this.intensity.addEventListener('input', (e) => {
|
| 54 |
+
this.intensityValue.textContent = e.target.value;
|
| 55 |
+
});
|
| 56 |
+
|
| 57 |
+
// Safety settings
|
| 58 |
+
this.maxIntensity.addEventListener('change', () => this.updateSafetyLimits());
|
| 59 |
+
this.autoStop.addEventListener('change', () => this.updateSafetyLimits());
|
| 60 |
+
this.gradualChanges.addEventListener('change', () => this.updateSafetyLimits());
|
| 61 |
+
|
| 62 |
+
// Enter key for command input
|
| 63 |
+
this.commandInput.addEventListener('keydown', (e) => {
|
| 64 |
+
if (e.key === 'Enter' && e.ctrlKey) {
|
| 65 |
+
this.processCommand();
|
| 66 |
+
}
|
| 67 |
+
});
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
async initializeModel() {
|
| 71 |
+
if (this.isModelLoading) return;
|
| 72 |
+
|
| 73 |
+
this.isModelLoading = true;
|
| 74 |
+
this.updateStatus('loading', 'Loading AI model...');
|
| 75 |
+
this.showLoadingBar(true);
|
| 76 |
+
|
| 77 |
+
try {
|
| 78 |
+
// Use a text generation model for natural language processing
|
| 79 |
+
const modelName = 'Xenova/distilgpt2'; // Lightweight model for fast loading
|
| 80 |
+
|
| 81 |
+
this.model = await pipeline('text-generation', modelName, {
|
| 82 |
+
progress_callback: (progress) => {
|
| 83 |
+
const percentage = Math.round(progress.progress * 100);
|
| 84 |
+
this.updateLoadingProgress(percentage, `Loading model: ${percentage}%`);
|
| 85 |
+
}
|
| 86 |
+
});
|
| 87 |
+
|
| 88 |
+
this.modelStatus.textContent = `Model: ${modelName}`;
|
| 89 |
+
this.updateStatus('ready', 'AI model loaded successfully');
|
| 90 |
+
this.enableButtons(true);
|
| 91 |
+
} catch (error) {
|
| 92 |
+
console.error('Error loading model:', error);
|
| 93 |
+
this.updateStatus('error', 'Failed to load AI model');
|
| 94 |
+
this.modelStatus.textContent = 'Model: Offline';
|
| 95 |
+
|
| 96 |
+
// Enable basic functionality without AI
|
| 97 |
+
this.enableButtons(true);
|
| 98 |
+
} finally {
|
| 99 |
+
this.isModelLoading = false;
|
| 100 |
+
this.showLoadingBar(false);
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
async processCommand() {
|
| 105 |
+
const command = this.commandInput.value.trim();
|
| 106 |
+
if (!command) {
|
| 107 |
+
this.showNotification('Please enter a command', 'warning');
|
| 108 |
+
return;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
this.updateStatus('processing', 'Processing command...');
|
| 112 |
+
this.processCommandBtn.disabled = true;
|
| 113 |
+
|
| 114 |
+
try {
|
| 115 |
+
let pattern;
|
| 116 |
+
|
| 117 |
+
if (this.model) {
|
| 118 |
+
// Use AI to interpret natural language
|
| 119 |
+
const prompt = `Convert this command to device pattern parameters: "${command}". Format as JSON with intensity, pattern, duration fields.`;
|
| 120 |
+
const result = await this.model(prompt, {
|
| 121 |
+
max_new_tokens: 100,
|
| 122 |
+
temperature: 0.7,
|
| 123 |
+
do_sample: true,
|
| 124 |
+
});
|
| 125 |
+
|
| 126 |
+
pattern = this.parseAIResponse(result[0].generated_text);
|
| 127 |
+
} else {
|
| 128 |
+
// Fallback to rule-based parsing
|
| 129 |
+
pattern = this.parseCommandRuleBased(command);
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
// Apply safety limits
|
| 133 |
+
pattern = this.applySafetyLimits(pattern);
|
| 134 |
+
|
| 135 |
+
// Display result
|
| 136 |
+
this.displayPattern(pattern);
|
| 137 |
+
this.commandResult.classList.remove('hidden');
|
| 138 |
+
|
| 139 |
+
// Visualize pattern
|
| 140 |
+
this.visualizePattern(pattern);
|
| 141 |
+
|
| 142 |
+
this.updateStatus('success', 'Command processed successfully');
|
| 143 |
+
|
| 144 |
+
} catch (error) {
|
| 145 |
+
console.error('Error processing command:', error);
|
| 146 |
+
this.updateStatus('error', 'Failed to process command');
|
| 147 |
+
this.showNotification('Error processing command', 'error');
|
| 148 |
+
} finally {
|
| 149 |
+
this.processCommandBtn.disabled = false;
|
| 150 |
+
}
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
parseAIResponse(response) {
|
| 154 |
+
try {
|
| 155 |
+
// Extract JSON from the response
|
| 156 |
+
const jsonMatch = response.match(/\{[^}]+\}/);
|
| 157 |
+
if (jsonMatch) {
|
| 158 |
+
return JSON.parse(jsonMatch[0]);
|
| 159 |
+
}
|
| 160 |
+
} catch (e) {
|
| 161 |
+
console.error('Failed to parse AI response:', e);
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
// Fallback to default pattern
|
| 165 |
+
return this.getDefaultPattern();
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
parseCommandRuleBased(command) {
|
| 169 |
+
const lowerCommand = command.toLowerCase();
|
| 170 |
+
const pattern = this.getDefaultPattern();
|
| 171 |
+
|
| 172 |
+
// Parse intensity
|
| 173 |
+
if (lowerCommand.includes('high') || lowerCommand.includes('strong')) {
|
| 174 |
+
pattern.intensity = 80;
|
| 175 |
+
} else if (lowerCommand.includes('low') || lowerCommand.includes('gentle') || lowerCommand.includes('soft')) {
|
| 176 |
+
pattern.intensity = 30;
|
| 177 |
+
} else if (lowerCommand.includes('medium')) {
|
| 178 |
+
pattern.intensity = 50;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
// Parse pattern type
|
| 182 |
+
if (lowerCommand.includes('wave')) {
|
| 183 |
+
pattern.pattern = 'wave';
|
| 184 |
+
} else if (lowerCommand.includes('pulse')) {
|
| 185 |
+
pattern.pattern = 'pulse';
|
| 186 |
+
} else if (lowerCommand.includes('ramp')) {
|
| 187 |
+
pattern.pattern = 'ramp';
|
| 188 |
+
} else if (lowerCommand.includes('random')) {
|
| 189 |
+
pattern.pattern = 'random';
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
// Parse duration
|
| 193 |
+
const durationMatch = command.match(/(\d+)\s*(second|minute|min|s|m)/i);
|
| 194 |
+
if (durationMatch) {
|
| 195 |
+
let value = parseInt(durationMatch[1]);
|
| 196 |
+
if (durationMatch[2].toLowerCase().startsWith('m')) {
|
| 197 |
+
value *= 60;
|
| 198 |
+
}
|
| 199 |
+
pattern.duration = Math.min(Math.max(value, 5), 60);
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
return pattern;
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
getDefaultPattern() {
|
| 206 |
+
return {
|
| 207 |
+
intensity: parseInt(this.intensity.value),
|
| 208 |
+
pattern: this.patternType.value,
|
| 209 |
+
duration: parseInt(this.duration.value)
|
| 210 |
+
};
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
generatePattern() {
|
| 214 |
+
this.updateStatus('generating', 'Generating pattern...');
|
| 215 |
+
|
| 216 |
+
const pattern = {
|
| 217 |
+
intensity: parseInt(this.intensity.value),
|
| 218 |
+
pattern: this.patternType.value,
|
| 219 |
+
duration: parseInt(this.duration.value)
|
| 220 |
+
};
|
| 221 |
+
|
| 222 |
+
// Apply safety limits
|
| 223 |
+
const safePattern = this.applySafetyLimits(pattern);
|
| 224 |
+
|
| 225 |
+
// Generate pattern data
|
| 226 |
+
const patternData = this.generatePatternData(safePattern);
|
| 227 |
+
|
| 228 |
+
// Visualize
|
| 229 |
+
this.visualizePatternData(patternData, safePattern);
|
| 230 |
+
this.generatedPattern.classList.remove('hidden');
|
| 231 |
+
|
| 232 |
+
this.updateStatus('success', 'Pattern generated successfully');
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
generatePatternData(pattern) {
|
| 236 |
+
const steps = pattern.duration * 10; // 10 steps per second
|
| 237 |
+
const data = [];
|
| 238 |
+
|
| 239 |
+
for (let i = 0; i < steps; i++) {
|
| 240 |
+
let value = 0;
|
| 241 |
+
const t = i / steps;
|
| 242 |
+
|
| 243 |
+
switch (pattern.pattern) {
|
| 244 |
+
case 'wave':
|
| 245 |
+
value = Math.sin(t * Math.PI * 4) * pattern.intensity;
|
| 246 |
+
break;
|
| 247 |
+
case 'pulse':
|
| 248 |
+
value = (Math.sin(t * Math.PI * 8) > 0) ? pattern.intensity : 0;
|
| 249 |
+
break;
|
| 250 |
+
case 'ramp':
|
| 251 |
+
value = t * pattern.intensity;
|
| 252 |
+
break;
|
| 253 |
+
case 'random':
|
| 254 |
+
value = Math.random() * pattern.intensity;
|
| 255 |
+
break;
|
| 256 |
+
case 'custom':
|
| 257 |
+
// Generate interesting custom pattern
|
| 258 |
+
value = (Math.sin(t * Math.PI * 6) * 0.5 + Math.sin(t * Math.PI * 13) * 0.3) * pattern.intensity;
|
| 259 |
+
break;
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
data.push(Math.max(0, Math.min(100, value + pattern.intensity / 2)));
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
return data;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
visualizePattern(pattern) {
|
| 269 |
+
const patternData = this.generatePatternData(pattern);
|
| 270 |
+
this.visualizePatternData(patternData, pattern);
|
| 271 |
+
this.generatedPattern.classList.remove('hidden');
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
visualizePatternData(data, pattern) {
|
| 275 |
+
const ctx = this.patternCanvas.getContext('2d');
|
| 276 |
+
const width = this.patternCanvas.width;
|
| 277 |
+
const height = this.patternCanvas.height;
|
| 278 |
+
|
| 279 |
+
// Clear canvas
|
| 280 |
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
|
| 281 |
+
ctx.fillRect(0, 0, width, height);
|
| 282 |
+
|
| 283 |
+
// Draw grid
|
| 284 |
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
|
| 285 |
+
ctx.lineWidth = 1;
|
| 286 |
+
for (let i = 0; i <= 4; i++) {
|
| 287 |
+
const y = (height / 4) * i;
|
| 288 |
+
ctx.beginPath();
|
| 289 |
+
ctx.moveTo(0, y);
|
| 290 |
+
ctx.lineTo(width, y);
|
| 291 |
+
ctx.stroke();
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
// Draw pattern
|
| 295 |
+
ctx.strokeStyle = 'rgba(168, 85, 247, 0.8)';
|
| 296 |
+
ctx.lineWidth = 2;
|
| 297 |
+
ctx.beginPath();
|
| 298 |
+
|
| 299 |
+
data.forEach((value, index) => {
|
| 300 |
+
const x = (index / data.length) * width;
|
| 301 |
+
const y = height - (value / 100) * height;
|
| 302 |
+
|
| 303 |
+
if (index === 0) {
|
| 304 |
+
ctx.moveTo(x, y);
|
| 305 |
+
} else {
|
| 306 |
+
ctx.lineTo(x, y);
|
| 307 |
+
}
|
| 308 |
+
});
|
| 309 |
+
|
| 310 |
+
ctx.stroke();
|
| 311 |
+
|
| 312 |
+
// Add glow effect
|
| 313 |
+
ctx.shadowColor = 'rgba(168, 85, 247, 0.5)';
|
| 314 |
+
ctx.shadowBlur = 10;
|
| 315 |
+
ctx.stroke();
|
| 316 |
+
|
| 317 |
+
// Update stats
|
| 318 |
+
const avgIntensity = data.reduce((a, b) => a + b, 0) / data.length;
|
| 319 |
+
const maxIntensity = Math.max(...data);
|
| 320 |
+
const minIntensity = Math.min(...data);
|
| 321 |
+
|
| 322 |
+
this.patternStats.textContent = `Avg: ${avgIntensity.toFixed(1)}% | Max: ${maxIntensity.toFixed(1)}% | Min: ${minIntensity.toFixed(1)}%`;
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
applySafetyLimits(pattern) {
|
| 326 |
+
const safePattern = { ...pattern };
|
| 327 |
+
|
| 328 |
+
// Apply max intensity limit
|
| 329 |
+
if (this.maxIntensity.checked) {
|
| 330 |
+
safePattern.intensity = Math.min(safePattern.intensity, 80);
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
// Apply duration limit
|
| 334 |
+
if (this.autoStop.checked) {
|
| 335 |
+
safePattern.duration = Math.min(safePattern.duration, 30);
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
// Ensure minimum values
|
| 339 |
+
safePattern.intensity = Math.max(safePattern.intensity, 0);
|
| 340 |
+
safePattern.duration = Math.max(safePattern.duration, 5);
|
| 341 |
+
|
| 342 |
+
return safePattern;
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
updateSafetyLimits() {
|
| 346 |
+
// Update intensity display if max limit is enabled
|
| 347 |
+
if (this.maxIntensity.checked) {
|
| 348 |
+
const currentIntensity = parseInt(this.intensity.value);
|
| 349 |
+
if (currentIntensity > 80) {
|
| 350 |
+
this.intensity.value = 80;
|
| 351 |
+
this.intensityValue.textContent = 80;
|
| 352 |
+
}
|
| 353 |
+
this.intensity.max = 80;
|
| 354 |
+
} else {
|
| 355 |
+
this.intensity.max = 100;
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
// Update duration display if auto-stop is enabled
|
| 359 |
+
if (this.autoStop.checked) {
|
| 360 |
+
const currentDuration = parseInt(this.duration.value);
|
| 361 |
+
if (currentDuration > 30) {
|
| 362 |
+
this.duration.value = 30;
|
| 363 |
+
this.durationValue.textContent = 30;
|
| 364 |
+
}
|
| 365 |
+
this.duration.max = 30;
|
| 366 |
+
} else {
|
| 367 |
+
this.duration.max = 60;
|
| 368 |
+
}
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
displayPattern(pattern) {
|
| 372 |
+
this.patternOutput.textContent = JSON.stringify(pattern, null, 2);
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
updateStatus(type, message) {
|
| 376 |
+
this.statusText.textContent = message;
|
| 377 |
+
|
| 378 |
+
// Update indicator color
|
| 379 |
+
this.statusIndicator.className = 'w-3 h-3 rounded-full ';
|
| 380 |
+
switch (type) {
|
| 381 |
+
case 'ready':
|
| 382 |
+
this.statusIndicator.className += 'bg-green-400 animate-pulse';
|
| 383 |
+
break;
|
| 384 |
+
case 'loading':
|
| 385 |
+
case 'processing':
|
| 386 |
+
case 'generating':
|
| 387 |
+
this.statusIndicator.className += 'bg-yellow-400 animate-pulse';
|
| 388 |
+
break;
|
| 389 |
+
case 'error':
|
| 390 |
+
this.statusIndicator.className += 'bg-red-400';
|
| 391 |
+
break;
|
| 392 |
+
case 'success':
|
| 393 |
+
this.statusIndicator.className += 'bg-blue-400 animate-pulse';
|
| 394 |
+
break;
|
| 395 |
+
}
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
showLoadingBar(show) {
|
| 399 |
+
if (show) {
|
| 400 |
+
this.loadingBar.classList.remove('hidden');
|
| 401 |
+
} else {
|
| 402 |
+
this.loadingBar.classList.add('hidden');
|
| 403 |
+
}
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
updateLoadingProgress(percentage, text) {
|
| 407 |
+
this.loadingProgress.style.width = `${percentage}%`;
|
| 408 |
+
this.loadingText.textContent = text;
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
enableButtons(enabled) {
|
| 412 |
+
this.processCommandBtn.disabled = !enabled;
|
| 413 |
+
this.generatePatternBtn.disabled = !enabled;
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
showNotification(message, type = 'info') {
|
| 417 |
+
// Create notification element
|
| 418 |
+
const notification = document.createElement('div');
|
| 419 |
+
notification.className = `fixed top-4 right-4 p-4 rounded-lg shadow-lg transform transition-all duration-300 z-50`;
|
| 420 |
+
|
| 421 |
+
// Style based on type
|
| 422 |
+
switch (type) {
|
| 423 |
+
case 'error':
|
| 424 |
+
notification.className += ' bg-red-500 text-white';
|
| 425 |
+
break;
|
| 426 |
+
case 'warning':
|
| 427 |
+
notification.className += ' bg-yellow-500 text-white';
|
| 428 |
+
break;
|
| 429 |
+
case 'success':
|
| 430 |
+
notification.className += ' bg-green-500 text-white';
|
| 431 |
+
break;
|
| 432 |
+
default:
|
| 433 |
+
notification.className += ' bg-blue-500 text-white';
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
notification.textContent = message;
|
| 437 |
+
document.body.appendChild(notification);
|
| 438 |
+
|
| 439 |
+
// Animate in
|
| 440 |
+
setTimeout(() => {
|
| 441 |
+
notification.style.transform = 'translateX(0)';
|
| 442 |
+
}, 10);
|
| 443 |
+
|
| 444 |
+
// Remove after 3 seconds
|
| 445 |
+
setTimeout(() => {
|
| 446 |
+
notification.style.transform = 'translateX(400px)';
|
| 447 |
+
setTimeout(() => {
|
| 448 |
+
document.body.removeChild(notification);
|
| 449 |
+
}, 300);
|
| 450 |
+
}, 3000);
|
| 451 |
+
}
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
// Toggle checkbox styles
|
| 455 |
+
const style = document.createElement('style');
|
| 456 |
+
style.textContent = `
|
| 457 |
+
.toggle-checkbox {
|
| 458 |
+
appearance: none;
|
| 459 |
+
width: 44px;
|
| 460 |
+
height: 24px;
|
| 461 |
+
background: rgba(255, 255, 255, 0.2);
|
| 462 |
+
border-radius: 12px;
|
| 463 |
+
position: relative;
|
| 464 |
+
cursor: pointer;
|
| 465 |
+
transition: background 0.3s;
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
.toggle-checkbox:checked {
|
| 469 |
+
background: linear-gradient(to right, #9333ea, #ec4899);
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
.toggle-checkbox::after {
|
| 473 |
+
content: '';
|
| 474 |
+
position: absolute;
|
| 475 |
+
width: 20px;
|
| 476 |
+
height: 20px;
|
| 477 |
+
background: white;
|
| 478 |
+
border-radius: 50%;
|
| 479 |
+
top: 2px;
|
| 480 |
+
left: 2px;
|
| 481 |
+
transition: transform 0.3s;
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
.toggle-checkbox:checked::after {
|
| 485 |
+
transform: translateX(20px);
|
| 486 |
+
}
|
| 487 |
+
`;
|
| 488 |
+
document.head.appendChild(style);
|
| 489 |
+
|
| 490 |
+
// Initialize app when DOM is ready
|
| 491 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 492 |
+
new DeviceControlApp();
|
| 493 |
+
});
|