// Importamos las funciones necesarias de Transformers.js import { pipeline, RawImage } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.1'; // Módulo de Estado Global de la Aplicación const AppState = { isWaking: false, isAwake: false, user: { name: 'Che' }, ai: { name: 'Synapse' }, activeMode: 'eco', isProcessing: false, isSpeaking: false, models: { detector: null, textGenerator: null }, dom: {}, analysisInterval: null, voice: null }; // =================================================================== // MÓDULO 0: PERSONALIDAD Y VOZ (EL ALMA ARGENTINA) // =================================================================== const Prompts_AR = { greeting: (aiName) => `¡Listo, acá estoy! Soy ${aiName}. Apuntá la cámara y vemos qué onda.`, eco: (object, isNew) => isNew ? `Che, eso parece un/a ${object}. ¿Sabés la posta para reciclarlo? Te cuento: ` : `Otra vez un/a ${object}, ¡qué copado! Nos estamos volviendo expertos en esto, ¿eh? `, root: (object, isNew) => isNew ? `Mirá qué bueno, un/a ${object}. Te tiro un dato de color sobre esto que seguro no sabías: ` : `¡De nuevo por acá, ${object}! Me encanta que sigamos explorando juntos. `, serenity: `Bueno, pará un poco la moto. Se nota que estás a mil. Tomate un toque para vos. Concentrate en tu respiración... inhalá... y exhalá despacito...`, mirrorLog: (source, reason) => `A esta hora, en modo "${source}", te sugerí algo porque ${reason}.` }; const VoiceManager = { synth: window.speechSynthesis, utterance: new SpeechSynthesisUtterance(), async loadVoices() { return new Promise(resolve => { let voices = this.synth.getVoices(); if (voices.length) { this.findBestVoice(voices); resolve(); return; } this.synth.onvoiceschanged = () => { voices = this.synth.getVoices(); this.findBestVoice(voices); resolve(); }; }); }, findBestVoice(voices) { let bestVoice = voices.find(v => v.lang === 'es-AR'); if (!bestVoice) bestVoice = voices.find(v => v.lang === 'es-US'); if (!bestVoice) bestVoice = voices.find(v => v.lang.startsWith('es-')); if (bestVoice) { console.log(`Voz seleccionada: ${bestVoice.name} (${bestVoice.lang})`); AppState.voice = bestVoice; } else { console.warn("No se encontró una voz en español. La experiencia de voz puede no ser óptima."); } }, speak(text) { if (!AppState.voice) { console.log("Hablando (sin voz seleccionada):", text); return; } if (this.synth.speaking) this.synth.cancel(); this.utterance.text = text; this.utterance.voice = AppState.voice; this.utterance.lang = AppState.voice.lang; this.utterance.rate = 1.0; this.utterance.pitch = 1.0; this.utterance.onstart = () => AppState.isSpeaking = true; this.utterance.onend = () => AppState.isSpeaking = false; this.synth.speak(this.utterance); } }; // =================================================================== // MÓDULO 1: GESTIÓN DE CICLO DE VIDA, USUARIO Y MEMORIA // =================================================================== const LifecycleManager = { init() { this.cacheDom(); this.bindEventListeners(); this.checkUserAndMemory(); }, cacheDom() { AppState.dom = { welcomeScreen: document.getElementById('welcome-screen'), appContainer: document.getElementById('app-container'), loadingStatus: document.getElementById('loading-status'), userCreation: document.getElementById('user-creation'), usernameInput: document.getElementById('username-input'), ainameInput: document.getElementById('ainame-input'), createUserBtn: document.getElementById('create-user-btn'), wakeAiBtn: document.getElementById('wake-ai-btn'), cameraFeed: document.getElementById('camera-feed'), statusIndicator: document.getElementById('status-indicator'), statusText: document.getElementById('status-text'), outputContainer: document.getElementById('output-container'), outputText: document.getElementById('output-text'), body: document.getElementById('synapse-body'), settingsScreen: document.getElementById('settings-screen'), mirrorScreen: document.getElementById('consciousness-mirror'), mirrorLog: document.getElementById('mirror-log'), settingsBtn: document.getElementById('settings-btn'), saveSettingsBtn: document.getElementById('save-settings-btn'), mirrorToggleBtn: document.getElementById('mirror-toggle-btn'), closePanelBtns: document.querySelectorAll('.close-panel-btn'), controlBtns: document.querySelectorAll('.control-btn') }; }, bindEventListeners() { AppState.dom.createUserBtn.addEventListener('click', () => this.createUser()); AppState.dom.wakeAiBtn.addEventListener('click', () => this.wakeUpAI()); AppState.dom.settingsBtn.addEventListener('click', () => this.togglePanel('settingsScreen', true)); AppState.dom.mirrorToggleBtn.addEventListener('click', () => { this.togglePanel('settingsScreen', false); this.togglePanel('mirrorScreen', true); }); AppState.dom.closePanelBtns.forEach(btn => btn.addEventListener('click', (e) => { e.target.closest('.modal-panel').classList.remove('visible'); })); AppState.dom.controlBtns.forEach(btn => btn.addEventListener('click', () => this.setActiveMode(btn.dataset.mode))); AppState.dom.saveSettingsBtn.addEventListener('click', () => this.saveSettings()); }, async checkUserAndMemory() { await VoiceManager.loadVoices(); const user = localStorage.getItem('synapseUser'); const ai = localStorage.getItem('synapseAI'); if (user && ai) { AppState.user = JSON.parse(user); AppState.ai = JSON.parse(ai); AppState.memory = JSON.parse(localStorage.getItem('synapseMemory') || '{"interactions":{}}'); AppState.dom.welcomeScreen.classList.remove('visible'); AppState.dom.appContainer.classList.add('visible'); this.updateOutput(`¡Hola ${AppState.user.name}! Soy ${AppState.ai.name}. Cuando quieras, despertame.`); } else { AppState.dom.welcomeScreen.classList.add('visible'); AppState.dom.appContainer.classList.remove('visible'); AppState.dom.loadingStatus.style.display = 'none'; AppState.dom.userCreation.style.display = 'block'; } }, createUser() { const username = AppState.dom.usernameInput.value.trim(); const ainame = AppState.dom.ainameInput.value.trim(); if (username && ainame) { AppState.user.name = username; AppState.ai.name = ainame; localStorage.setItem('synapseUser', JSON.stringify(AppState.user)); localStorage.setItem('synapseAI', JSON.stringify(AppState.ai)); localStorage.setItem('synapseMemory', JSON.stringify({ interactions: {} })); this.checkUserAndMemory(); } }, async wakeUpAI() { if (AppState.isAwake || AppState.isWaking) return; AppState.isWaking = true; this.updateOutput("¡Buenas! Dame un toque que me despierto...", true); AppState.dom.statusText.textContent = "Despertando..."; const cameraStarted = await CameraManager.start(); if (!cameraStarted) { this.updateOutput("No pude prender la cámara, che. Fijate los permisos y probá de nuevo.", true); AppState.isWaking = false; return; } AppState.dom.statusText.textContent = "Cargando modelos..."; await AI_Core.initModels(); AppState.isWaking = false; AppState.isAwake = true; AppState.dom.statusIndicator.querySelector('.status-dot').classList.remove('offline'); AppState.dom.controlBtns.forEach(btn => btn.disabled = false); const greeting = Prompts_AR.greeting(AppState.ai.name); this.updateOutput(greeting, true); if (AppState.analysisInterval) clearInterval(AppState.analysisInterval); AppState.analysisInterval = setInterval(() => this.runContinuousAnalysis(), 4000); }, togglePanel(panelId, show) { document.getElementById(panelId).classList.toggle('visible', show); }, setActiveMode(mode) { if (mode === 'serenity') { DigitalSerenity.triggerConsciousMoment(); return; } if (!AppState.isAwake || AppState.activeMode === mode) return; AppState.activeMode = mode; AppState.dom.controlBtns.forEach(btn => btn.classList.toggle('active', btn.dataset.mode === mode)); }, saveSettings() { const newAiName = document.getElementById('ainame-settings-input').value.trim(); if (newAiName) { AppState.ai.name = newAiName; localStorage.setItem('synapseAI', JSON.stringify(AppState.ai)); this.updateOutput(`¡Listo! A partir de ahora me llamo ${newAiName}.`, true); } this.togglePanel('settings-screen', false); }, runContinuousAnalysis() { if (!AppState.isAwake || AppState.isProcessing || AppState.isSpeaking) return; AppState.isProcessing = true; AppState.dom.statusText.textContent = "Viendo..."; (async () => { try { const detectedObject = await AI_Core.detectObjects(); if (detectedObject && !MemoryManager.isRecent(detectedObject)) { const isNew = !MemoryManager.check(detectedObject); MemoryManager.add(detectedObject); let promptBase = ""; if (AppState.activeMode === 'eco') { promptBase = Prompts_AR.eco(detectedObject, isNew); } else if (AppState.activeMode === 'root') { promptBase = Prompts_AR.root(detectedObject, isNew); } const response = await AI_Core.generateText(promptBase); this.updateOutput(response, true); ConsciousnessMirror.log(AppState.activeMode, `vi un/a ${detectedObject} y te di una reflexión.`); } } catch (error) { console.error("Error en el ciclo de análisis:", error); } finally { AppState.isProcessing = false; if(AppState.isAwake) AppState.dom.statusText.textContent = "Atento"; } })(); }, updateOutput(text, speak = false) { AppState.dom.outputContainer.style.animation = 'none'; AppState.dom.outputContainer.offsetHeight; AppState.dom.outputContainer.style.animation = 'slideUp 0.5s ease-out'; AppState.dom.outputText.textContent = text; if (speak) VoiceManager.speak(text); } }; // =================================================================== // MÓDULO 2: NÚCLEO DE INTELIGENCIA ARTIFICIAL // =================================================================== const AI_Core = { async initModels() { LifecycleManager.updateOutput("Cargando modelo de visión...", false); AppState.models.detector = await pipeline('object-detection', 'Xenova/detr-resnet-50'); LifecycleManager.updateOutput("Cargando modelo de lenguaje...", false); AppState.models.textGenerator = await pipeline('text-generation', 'Xenova/Phi-3-mini-4k-instruct-onnx-web'); }, async detectObjects() { if (!AppState.isAwake || !AppState.models.detector || AppState.dom.cameraFeed.paused || AppState.dom.cameraFeed.ended) return null; const image = await RawImage.fromElement(AppState.dom.cameraFeed); const objects = await AppState.models.detector(image, { threshold: 0.9, percentage: true }); const mainObject = objects.filter(obj => obj.score > 0.9).sort((a,b) => b.score - a.score)[0]; return mainObject ? mainObject.label : null; }, async generateText(prompt) { if (!AppState.models.textGenerator) return "El modelo de lenguaje no está listo."; const formattedPrompt = `<|user|>\nSos un asistente de IA argentino, amable y canchero. Respondé de forma concisa. ${prompt}<|end|>\n<|assistant|>\n`; const result = await AppState.models.textGenerator(formattedPrompt, { max_new_tokens: 80 }); return result[0].generated_text.split('<|assistant|>').pop().trim(); } }; // =================================================================== // MÓDULO 3: GESTORES DE CÁMARA, MEMORIA Y CARACTERÍSTICAS // =================================================================== const CameraManager = { async start() { try { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' }, audio: false }); AppState.dom.cameraFeed.srcObject = stream; return true; } catch (error) { console.error("Error al acceder a la cámara:", error); return false; } } }; const MemoryManager = { add(objectLabel) { AppState.memory.interactions[objectLabel] = { timestamp: Date.now(), count: (AppState.memory.interactions[objectLabel]?.count || 0) + 1 }; localStorage.setItem('synapseMemory', JSON.stringify(AppState.memory)); }, check(objectLabel) { return AppState.memory.interactions[objectLabel] !== undefined; }, isRecent(objectLabel) { const interaction = AppState.memory.interactions[objectLabel]; return interaction && (Date.now() - interaction.timestamp < 30000); } }; const DigitalSerenity = { triggerConsciousMoment() { const text = Prompts_AR.serenity; LifecycleManager.updateOutput(text, true); AppState.dom.body.classList.add('serenity-nudge'); if ('vibrate' in navigator) navigator.vibrate([4000, 1000, 6000]); ConsciousnessMirror.log('Modo Zen', `se activó un momento de calma.`); setTimeout(() => AppState.dom.body.classList.remove('serenity-nudge'), 11000); } }; const ConsciousnessMirror = { log(source, reason) { const logEntry = { source, reason, timestamp: new Date().toLocaleTimeString() }; let logs = JSON.parse(localStorage.getItem('synapseLogs') || '[]'); logs.unshift(logEntry); logs = logs.slice(0, 20); localStorage.setItem('synapseLogs', JSON.stringify(logs)); this.render(); }, render() { const logs = JSON.parse(localStorage.getItem('synapseLogs') || '[]'); AppState.dom.mirrorLog.innerHTML = logs.map(entry => `
${entry.source} (${entry.timestamp})

${Prompts_AR.mirrorLog(entry.source, entry.reason)}

`).join(''); } }; // =================================================================== // PUNTO DE ENTRADA PRINCIPAL // =================================================================== window.onload = () => { LifecycleManager.init(); };