const { useState, useEffect, useMemo } = React; // --- SOUND ENGINE (Web Audio API) --- const SoundEngine = (() => { let ctx = null; const getCtx = () => { if (!ctx) ctx = new (window.AudioContext || window.webkitAudioContext)(); if (ctx.state === 'suspended') ctx.resume(); return ctx; }; const playTone = (freq, duration, vol = 0.12, type = 'sine') => { try { const c = getCtx(); const osc = c.createOscillator(); const gain = c.createGain(); osc.type = type; osc.frequency.setValueAtTime(freq, c.currentTime); gain.gain.setValueAtTime(vol, c.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, c.currentTime + duration); osc.connect(gain); gain.connect(c.destination); osc.start(c.currentTime); osc.stop(c.currentTime + duration); } catch (e) {} }; return { hover: () => playTone(880, 0.08, 0.06, 'sine'), click: () => { playTone(660, 0.06, 0.1, 'sine'); setTimeout(() => playTone(990, 0.08, 0.08, 'sine'), 50); }, spin: () => { for (let i = 0; i < 6; i++) { setTimeout(() => playTone(300 + i * 120, 0.12, 0.04 + i * 0.01, 'triangle'), i * 80); } }, copy: () => { playTone(523, 0.1, 0.08, 'sine'); setTimeout(() => playTone(659, 0.1, 0.08, 'sine'), 80); setTimeout(() => playTone(784, 0.15, 0.1, 'sine'), 160); }, washStart: () => { // Washing machine start: low rumble + beep playTone(120, 0.3, 0.08, 'sawtooth'); setTimeout(() => playTone(220, 0.2, 0.06, 'triangle'), 100); setTimeout(() => playTone(880, 0.15, 0.1, 'sine'), 350); setTimeout(() => playTone(880, 0.15, 0.1, 'sine'), 550); }, knobTurn: () => playTone(440, 0.05, 0.08, 'triangle'), }; })(); // --- ICONE SVG --- const IconCar = ({ size = 24, className = "" }) => ( ); const IconRefreshCw = ({ size = 24, className = "" }) => ( ); const IconCopy = ({ size = 24, className = "" }) => ( ); const IconCheck = ({ size = 24, className = "" }) => ( ); const IconChevronRight = ({ size = 24, className = "" }) => ( ); const IconInfo = ({ size = 24, className = "" }) => ( ); const IconMessageSquare = ({ size = 24, className = "" }) => ( ); // --- CONFIGURAZIONE PROMPT CONDIVISA --- const PROMPT_CONFIG = window.PROMPT_CONFIG; const SIMULATOR_CONFIG = PROMPT_CONFIG.simulator; const OPTIMIZER_CONFIG = PROMPT_CONFIG.optimizer; const PLUTCHIK_EMOTIONS = SIMULATOR_CONFIG.options.emotions; const SALES_PHASES = SIMULATOR_CONFIG.options.phases; const CHANNELS = SIMULATOR_CONFIG.options.channels; const VEHICLES = SIMULATOR_CONFIG.options.vehicles; const MSG_TYPES = OPTIMIZER_CONFIG.options.messageTypes; const STYLES = OPTIMIZER_CONFIG.options.styles; const SIMULATOR_DEFAULTS = SIMULATOR_CONFIG.options.defaults; const SIMULATOR_SHORT_LABELS = SIMULATOR_CONFIG.options.shortLabels; const OPTIMIZER_DEFAULTS = OPTIMIZER_CONFIG.options.defaults; // --- FUNZIONI DI SUPPORTO MATEMATICO (SVG) --- const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => { const angleInRadians = (angleInDegrees * Math.PI) / 180.0; return { x: centerX + radius * Math.cos(angleInRadians), y: centerY + radius * Math.sin(angleInRadians) }; }; const getAnnularSectorPath = (cx, cy, r1, r2, startAngle, endAngle) => { const startOuter = polarToCartesian(cx, cy, r2, endAngle); const endOuter = polarToCartesian(cx, cy, r2, startAngle); const startInner = polarToCartesian(cx, cy, r1, endAngle); const endInner = polarToCartesian(cx, cy, r1, startAngle); const largeArcFlag = endAngle - startAngle <= 180 ? '0' : '1'; return ['M', startOuter.x, startOuter.y, 'A', r2, r2, 0, largeArcFlag, 0, endOuter.x, endOuter.y, 'L', endInner.x, endInner.y, 'A', r1, r1, 0, largeArcFlag, 1, startInner.x, startInner.y, 'Z'].join(' '); }; const normalizeAngle = (angle) => ((angle % 360) + 360) % 360; const getTargetRotation = (index, totalItems) => { const itemAngle = (index + 0.5) * (360 / totalItems); return 270 - itemAngle; }; const calculateShortestSpin = (currentRot, targetRotMod360, forceSpin = false) => { const curMod = normalizeAngle(currentRot); let diff = targetRotMod360 - curMod; if (!forceSpin) { if (diff > 180) diff -= 360; if (diff < -180) diff += 360; return currentRot + diff; } else { if (diff < 0) diff += 360; return currentRot + diff + (360 * 3); } }; // ============================================================ // COMPONENTE OTTIMIZZATORE (Lavatrice) // ============================================================ function OptimizerTab() { const [msgType, setMsgType] = useState(OPTIMIZER_DEFAULTS.messageType); const [style, setStyle] = useState(OPTIMIZER_DEFAULTS.style); const [message, setMessage] = useState(''); const [objective, setObjective] = useState(''); const [mode, setMode] = useState(null); // 'fix' | 'feedback' const [isWashing, setIsWashing] = useState(false); const [generatedPrompt, setGeneratedPrompt] = useState(''); const [copied, setCopied] = useState(false); const [showPrompt, setShowPrompt] = useState(false); const canStart = message.trim().length >= OPTIMIZER_DEFAULTS.minMessageLength; const generatePrompt = (actionMode) => { const typeLabel = MSG_TYPES.find(t => t.id === msgType)?.label || msgType; const styleLabel = STYLES.find(s => s.id === style)?.label || style; const obj = objective.trim() || OPTIMIZER_CONFIG.defaultObjective; return OPTIMIZER_CONFIG.buildPrompt({ mode: actionMode, message: message.trim(), typeLabel, styleLabel, objective: obj, }); }; const handleStart = (actionMode) => { if (!canStart) return; SoundEngine.washStart(); setMode(actionMode); setIsWashing(true); setShowPrompt(false); setTimeout(() => { const prompt = generatePrompt(actionMode); setGeneratedPrompt(prompt); setIsWashing(false); setShowPrompt(true); }, 2000); }; const copyToClipboard = async () => { try { if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(generatedPrompt); } else { const textArea = document.createElement("textarea"); textArea.value = generatedPrompt; textArea.style.position = "fixed"; textArea.style.opacity = "0"; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); } SoundEngine.copy(); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { console.error("Errore durante la copia:", err); } }; // --- Knob Selector component --- const KnobSelector = ({ items, value, onChange, label, renderItem }) => (
{items.map(item => { const isActive = (item.id || item) === value; return ( ); })}
); return (
{/* COLONNA SINISTRA - Input & Lavatrice */}
{/* Textarea messaggio */}