Update static/main.js
Browse files- static/main.js +233 -51
static/main.js
CHANGED
|
@@ -1,24 +1,40 @@
|
|
| 1 |
-
// Importamos las funciones necesarias
|
| 2 |
import { pipeline, RawImage } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.1';
|
| 3 |
|
| 4 |
-
// Estado Global de la Aplicación
|
| 5 |
const AppState = {
|
| 6 |
-
isWaking: false,
|
| 7 |
-
isAwake: false,
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
};
|
| 11 |
|
| 12 |
// ===================================================================
|
| 13 |
-
// MÓDULO 0: PERSONALIDAD Y VOZ (EL ALMA ARGENTINA
|
| 14 |
// ===================================================================
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
const VoiceManager = {
|
| 18 |
synth: window.speechSynthesis,
|
| 19 |
utterance: new SpeechSynthesisUtterance(),
|
| 20 |
|
| 21 |
-
// ¡NUEVO MÉTODO INTELIGENTE PARA CARGAR VOCES!
|
| 22 |
async loadVoices() {
|
| 23 |
return new Promise(resolve => {
|
| 24 |
let voices = this.synth.getVoices();
|
|
@@ -36,11 +52,8 @@ const VoiceManager = {
|
|
| 36 |
},
|
| 37 |
|
| 38 |
findBestVoice(voices) {
|
| 39 |
-
// 1. Prioridad máxima: 'es-AR'
|
| 40 |
let bestVoice = voices.find(v => v.lang === 'es-AR');
|
| 41 |
-
// 2. Si no, buscar 'es-US' (suele ser neutra y de buena calidad)
|
| 42 |
if (!bestVoice) bestVoice = voices.find(v => v.lang === 'es-US');
|
| 43 |
-
// 3. Si no, cualquier voz en español
|
| 44 |
if (!bestVoice) bestVoice = voices.find(v => v.lang.startsWith('es-'));
|
| 45 |
|
| 46 |
if (bestVoice) {
|
|
@@ -59,7 +72,7 @@ const VoiceManager = {
|
|
| 59 |
if (this.synth.speaking) this.synth.cancel();
|
| 60 |
|
| 61 |
this.utterance.text = text;
|
| 62 |
-
this.utterance.voice = AppState.voice;
|
| 63 |
this.utterance.lang = AppState.voice.lang;
|
| 64 |
this.utterance.rate = 1.0;
|
| 65 |
this.utterance.pitch = 1.0;
|
|
@@ -71,59 +84,108 @@ const VoiceManager = {
|
|
| 71 |
};
|
| 72 |
|
| 73 |
// ===================================================================
|
| 74 |
-
// MÓDULO 1: GESTIÓN DE CICLO DE VIDA
|
| 75 |
// ===================================================================
|
| 76 |
const LifecycleManager = {
|
| 77 |
init() {
|
| 78 |
-
this.cacheDom();
|
| 79 |
-
this.bindEventListeners();
|
| 80 |
-
this.checkUserAndMemory();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
},
|
| 82 |
|
| 83 |
async checkUserAndMemory() {
|
| 84 |
-
await VoiceManager.loadVoices();
|
| 85 |
const user = localStorage.getItem('synapseUser');
|
| 86 |
-
|
| 87 |
-
if (user) {
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
AppState.
|
| 91 |
-
|
| 92 |
-
AppState.dom.appContainer.classList.add('visible'); // Mostramos la app principal, pero deshabilitada
|
| 93 |
AppState.dom.welcomeScreen.classList.remove('visible');
|
|
|
|
|
|
|
|
|
|
| 94 |
} else {
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
| 96 |
}
|
| 97 |
},
|
| 98 |
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
AppState.dom.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
},
|
| 103 |
|
| 104 |
-
// ¡LA NUEVA FUNCIÓN CLAVE PARA EL ARRANQUE!
|
| 105 |
async wakeUpAI() {
|
| 106 |
if (AppState.isAwake || AppState.isWaking) return;
|
| 107 |
|
| 108 |
AppState.isWaking = true;
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
// 1. Pedir permiso de cámara PRIMERO
|
| 113 |
-
AppState.dom.statusText.textContent = "Pidiendo permiso...";
|
| 114 |
const cameraStarted = await CameraManager.start();
|
| 115 |
if (!cameraStarted) {
|
| 116 |
-
|
| 117 |
AppState.isWaking = false;
|
| 118 |
-
AppState.dom.wakeAiBtn.style.display = 'block';
|
| 119 |
return;
|
| 120 |
}
|
| 121 |
|
| 122 |
-
// 2. Cargar modelos de IA DESPUÉS de tener la cámara
|
| 123 |
AppState.dom.statusText.textContent = "Cargando modelos...";
|
| 124 |
await AI_Core.initModels();
|
| 125 |
|
| 126 |
-
// 3. ¡IA Despierta y Lista!
|
| 127 |
AppState.isWaking = false;
|
| 128 |
AppState.isAwake = true;
|
| 129 |
AppState.dom.statusIndicator.querySelector('.status-dot').classList.remove('offline');
|
|
@@ -132,48 +194,168 @@ const LifecycleManager = {
|
|
| 132 |
const greeting = Prompts_AR.greeting(AppState.ai.name);
|
| 133 |
this.updateOutput(greeting, true);
|
| 134 |
|
| 135 |
-
// 4. Iniciar el Ciclo de Conciencia Permanente
|
| 136 |
if (AppState.analysisInterval) clearInterval(AppState.analysisInterval);
|
| 137 |
-
AppState.analysisInterval = setInterval(this.runContinuousAnalysis, 4000);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
},
|
| 139 |
|
| 140 |
-
// ... El resto de funciones de LifecycleManager (setActiveMode, etc.) con pequeños ajustes ...
|
| 141 |
setActiveMode(mode) {
|
| 142 |
-
if (
|
| 143 |
-
|
| 144 |
return;
|
| 145 |
}
|
| 146 |
-
|
|
|
|
|
|
|
|
|
|
| 147 |
},
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
runContinuousAnalysis() {
|
| 150 |
if (!AppState.isAwake || AppState.isProcessing || AppState.isSpeaking) return;
|
|
|
|
|
|
|
| 151 |
AppState.dom.statusText.textContent = "Viendo...";
|
| 152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
}
|
| 154 |
};
|
| 155 |
|
| 156 |
// ===================================================================
|
| 157 |
-
// MÓDULO
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
// ===================================================================
|
| 159 |
const CameraManager = {
|
| 160 |
async start() {
|
| 161 |
try {
|
| 162 |
const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' }, audio: false });
|
| 163 |
AppState.dom.cameraFeed.srcObject = stream;
|
| 164 |
-
return true;
|
| 165 |
} catch (error) {
|
| 166 |
console.error("Error al acceder a la cámara:", error);
|
| 167 |
-
return false;
|
| 168 |
}
|
| 169 |
}
|
| 170 |
};
|
| 171 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
-
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
|
|
|
| 176 |
// PUNTO DE ENTRADA PRINCIPAL
|
|
|
|
| 177 |
window.onload = () => {
|
| 178 |
LifecycleManager.init();
|
| 179 |
};
|
|
|
|
| 1 |
+
// Importamos las funciones necesarias de Transformers.js
|
| 2 |
import { pipeline, RawImage } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.1';
|
| 3 |
|
| 4 |
+
// Módulo de Estado Global de la Aplicación
|
| 5 |
const AppState = {
|
| 6 |
+
isWaking: false,
|
| 7 |
+
isAwake: false,
|
| 8 |
+
user: { name: 'Che' },
|
| 9 |
+
ai: { name: 'Synapse' },
|
| 10 |
+
activeMode: 'eco',
|
| 11 |
+
isProcessing: false,
|
| 12 |
+
isSpeaking: false,
|
| 13 |
+
models: { detector: null, textGenerator: null },
|
| 14 |
+
dom: {},
|
| 15 |
+
analysisInterval: null,
|
| 16 |
+
voice: null
|
| 17 |
};
|
| 18 |
|
| 19 |
// ===================================================================
|
| 20 |
+
// MÓDULO 0: PERSONALIDAD Y VOZ (EL ALMA ARGENTINA)
|
| 21 |
// ===================================================================
|
| 22 |
+
const Prompts_AR = {
|
| 23 |
+
greeting: (aiName) => `¡Listo, acá estoy! Soy ${aiName}. Apuntá la cámara y vemos qué onda.`,
|
| 24 |
+
eco: (object, isNew) => isNew ?
|
| 25 |
+
`Che, eso parece un/a ${object}. ¿Sabés la posta para reciclarlo? Te cuento: ` :
|
| 26 |
+
`Otra vez un/a ${object}, ¡qué copado! Nos estamos volviendo expertos en esto, ¿eh? `,
|
| 27 |
+
root: (object, isNew) => isNew ?
|
| 28 |
+
`Mirá qué bueno, un/a ${object}. Te tiro un dato de color sobre esto que seguro no sabías: ` :
|
| 29 |
+
`¡De nuevo por acá, ${object}! Me encanta que sigamos explorando juntos. `,
|
| 30 |
+
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...`,
|
| 31 |
+
mirrorLog: (source, reason) => `A esta hora, en modo "${source}", te sugerí algo porque ${reason}.`
|
| 32 |
+
};
|
| 33 |
|
| 34 |
const VoiceManager = {
|
| 35 |
synth: window.speechSynthesis,
|
| 36 |
utterance: new SpeechSynthesisUtterance(),
|
| 37 |
|
|
|
|
| 38 |
async loadVoices() {
|
| 39 |
return new Promise(resolve => {
|
| 40 |
let voices = this.synth.getVoices();
|
|
|
|
| 52 |
},
|
| 53 |
|
| 54 |
findBestVoice(voices) {
|
|
|
|
| 55 |
let bestVoice = voices.find(v => v.lang === 'es-AR');
|
|
|
|
| 56 |
if (!bestVoice) bestVoice = voices.find(v => v.lang === 'es-US');
|
|
|
|
| 57 |
if (!bestVoice) bestVoice = voices.find(v => v.lang.startsWith('es-'));
|
| 58 |
|
| 59 |
if (bestVoice) {
|
|
|
|
| 72 |
if (this.synth.speaking) this.synth.cancel();
|
| 73 |
|
| 74 |
this.utterance.text = text;
|
| 75 |
+
this.utterance.voice = AppState.voice;
|
| 76 |
this.utterance.lang = AppState.voice.lang;
|
| 77 |
this.utterance.rate = 1.0;
|
| 78 |
this.utterance.pitch = 1.0;
|
|
|
|
| 84 |
};
|
| 85 |
|
| 86 |
// ===================================================================
|
| 87 |
+
// MÓDULO 1: GESTIÓN DE CICLO DE VIDA, USUARIO Y MEMORIA
|
| 88 |
// ===================================================================
|
| 89 |
const LifecycleManager = {
|
| 90 |
init() {
|
| 91 |
+
this.cacheDom();
|
| 92 |
+
this.bindEventListeners();
|
| 93 |
+
this.checkUserAndMemory();
|
| 94 |
+
},
|
| 95 |
+
|
| 96 |
+
cacheDom() {
|
| 97 |
+
AppState.dom = {
|
| 98 |
+
welcomeScreen: document.getElementById('welcome-screen'),
|
| 99 |
+
appContainer: document.getElementById('app-container'),
|
| 100 |
+
loadingStatus: document.getElementById('loading-status'),
|
| 101 |
+
userCreation: document.getElementById('user-creation'),
|
| 102 |
+
usernameInput: document.getElementById('username-input'),
|
| 103 |
+
ainameInput: document.getElementById('ainame-input'),
|
| 104 |
+
createUserBtn: document.getElementById('create-user-btn'),
|
| 105 |
+
wakeAiBtn: document.getElementById('wake-ai-btn'),
|
| 106 |
+
cameraFeed: document.getElementById('camera-feed'),
|
| 107 |
+
statusIndicator: document.getElementById('status-indicator'),
|
| 108 |
+
statusText: document.getElementById('status-text'),
|
| 109 |
+
outputContainer: document.getElementById('output-container'),
|
| 110 |
+
outputText: document.getElementById('output-text'),
|
| 111 |
+
body: document.getElementById('synapse-body'),
|
| 112 |
+
settingsScreen: document.getElementById('settings-screen'),
|
| 113 |
+
mirrorScreen: document.getElementById('consciousness-mirror'),
|
| 114 |
+
mirrorLog: document.getElementById('mirror-log'),
|
| 115 |
+
settingsBtn: document.getElementById('settings-btn'),
|
| 116 |
+
saveSettingsBtn: document.getElementById('save-settings-btn'),
|
| 117 |
+
mirrorToggleBtn: document.getElementById('mirror-toggle-btn'),
|
| 118 |
+
closePanelBtns: document.querySelectorAll('.close-panel-btn'),
|
| 119 |
+
controlBtns: document.querySelectorAll('.control-btn')
|
| 120 |
+
};
|
| 121 |
+
},
|
| 122 |
+
|
| 123 |
+
bindEventListeners() {
|
| 124 |
+
AppState.dom.createUserBtn.addEventListener('click', () => this.createUser());
|
| 125 |
+
AppState.dom.wakeAiBtn.addEventListener('click', () => this.wakeUpAI());
|
| 126 |
+
AppState.dom.settingsBtn.addEventListener('click', () => this.togglePanel('settingsScreen', true));
|
| 127 |
+
AppState.dom.mirrorToggleBtn.addEventListener('click', () => {
|
| 128 |
+
this.togglePanel('settingsScreen', false);
|
| 129 |
+
this.togglePanel('mirrorScreen', true);
|
| 130 |
+
});
|
| 131 |
+
AppState.dom.closePanelBtns.forEach(btn => btn.addEventListener('click', (e) => {
|
| 132 |
+
e.target.closest('.modal-panel').classList.remove('visible');
|
| 133 |
+
}));
|
| 134 |
+
AppState.dom.controlBtns.forEach(btn => btn.addEventListener('click', () => this.setActiveMode(btn.dataset.mode)));
|
| 135 |
+
AppState.dom.saveSettingsBtn.addEventListener('click', () => this.saveSettings());
|
| 136 |
},
|
| 137 |
|
| 138 |
async checkUserAndMemory() {
|
| 139 |
+
await VoiceManager.loadVoices();
|
| 140 |
const user = localStorage.getItem('synapseUser');
|
| 141 |
+
const ai = localStorage.getItem('synapseAI');
|
| 142 |
+
if (user && ai) {
|
| 143 |
+
AppState.user = JSON.parse(user);
|
| 144 |
+
AppState.ai = JSON.parse(ai);
|
| 145 |
+
AppState.memory = JSON.parse(localStorage.getItem('synapseMemory') || '{"interactions":{}}');
|
| 146 |
+
|
|
|
|
| 147 |
AppState.dom.welcomeScreen.classList.remove('visible');
|
| 148 |
+
AppState.dom.appContainer.classList.add('visible');
|
| 149 |
+
this.updateOutput(`¡Hola ${AppState.user.name}! Soy ${AppState.ai.name}. Cuando quieras, despertame.`);
|
| 150 |
+
|
| 151 |
} else {
|
| 152 |
+
AppState.dom.welcomeScreen.classList.add('visible');
|
| 153 |
+
AppState.dom.appContainer.classList.remove('visible');
|
| 154 |
+
AppState.dom.loadingStatus.style.display = 'none';
|
| 155 |
+
AppState.dom.userCreation.style.display = 'block';
|
| 156 |
}
|
| 157 |
},
|
| 158 |
|
| 159 |
+
createUser() {
|
| 160 |
+
const username = AppState.dom.usernameInput.value.trim();
|
| 161 |
+
const ainame = AppState.dom.ainameInput.value.trim();
|
| 162 |
+
if (username && ainame) {
|
| 163 |
+
AppState.user.name = username;
|
| 164 |
+
AppState.ai.name = ainame;
|
| 165 |
+
localStorage.setItem('synapseUser', JSON.stringify(AppState.user));
|
| 166 |
+
localStorage.setItem('synapseAI', JSON.stringify(AppState.ai));
|
| 167 |
+
localStorage.setItem('synapseMemory', JSON.stringify({ interactions: {} }));
|
| 168 |
+
this.checkUserAndMemory();
|
| 169 |
+
}
|
| 170 |
},
|
| 171 |
|
|
|
|
| 172 |
async wakeUpAI() {
|
| 173 |
if (AppState.isAwake || AppState.isWaking) return;
|
| 174 |
|
| 175 |
AppState.isWaking = true;
|
| 176 |
+
this.updateOutput("¡Buenas! Dame un toque que me despierto...", true);
|
| 177 |
+
AppState.dom.statusText.textContent = "Despertando...";
|
| 178 |
+
|
|
|
|
|
|
|
| 179 |
const cameraStarted = await CameraManager.start();
|
| 180 |
if (!cameraStarted) {
|
| 181 |
+
this.updateOutput("No pude prender la cámara, che. Fijate los permisos y probá de nuevo.", true);
|
| 182 |
AppState.isWaking = false;
|
|
|
|
| 183 |
return;
|
| 184 |
}
|
| 185 |
|
|
|
|
| 186 |
AppState.dom.statusText.textContent = "Cargando modelos...";
|
| 187 |
await AI_Core.initModels();
|
| 188 |
|
|
|
|
| 189 |
AppState.isWaking = false;
|
| 190 |
AppState.isAwake = true;
|
| 191 |
AppState.dom.statusIndicator.querySelector('.status-dot').classList.remove('offline');
|
|
|
|
| 194 |
const greeting = Prompts_AR.greeting(AppState.ai.name);
|
| 195 |
this.updateOutput(greeting, true);
|
| 196 |
|
|
|
|
| 197 |
if (AppState.analysisInterval) clearInterval(AppState.analysisInterval);
|
| 198 |
+
AppState.analysisInterval = setInterval(() => this.runContinuousAnalysis(), 4000);
|
| 199 |
+
},
|
| 200 |
+
|
| 201 |
+
togglePanel(panelId, show) {
|
| 202 |
+
document.getElementById(panelId).classList.toggle('visible', show);
|
| 203 |
},
|
| 204 |
|
|
|
|
| 205 |
setActiveMode(mode) {
|
| 206 |
+
if (mode === 'serenity') {
|
| 207 |
+
DigitalSerenity.triggerConsciousMoment();
|
| 208 |
return;
|
| 209 |
}
|
| 210 |
+
if (!AppState.isAwake || AppState.activeMode === mode) return;
|
| 211 |
+
|
| 212 |
+
AppState.activeMode = mode;
|
| 213 |
+
AppState.dom.controlBtns.forEach(btn => btn.classList.toggle('active', btn.dataset.mode === mode));
|
| 214 |
},
|
| 215 |
|
| 216 |
+
saveSettings() {
|
| 217 |
+
const newAiName = document.getElementById('ainame-settings-input').value.trim();
|
| 218 |
+
if (newAiName) {
|
| 219 |
+
AppState.ai.name = newAiName;
|
| 220 |
+
localStorage.setItem('synapseAI', JSON.stringify(AppState.ai));
|
| 221 |
+
this.updateOutput(`¡Listo! A partir de ahora me llamo ${newAiName}.`, true);
|
| 222 |
+
}
|
| 223 |
+
this.togglePanel('settings-screen', false);
|
| 224 |
+
},
|
| 225 |
+
|
| 226 |
runContinuousAnalysis() {
|
| 227 |
if (!AppState.isAwake || AppState.isProcessing || AppState.isSpeaking) return;
|
| 228 |
+
|
| 229 |
+
AppState.isProcessing = true;
|
| 230 |
AppState.dom.statusText.textContent = "Viendo...";
|
| 231 |
+
|
| 232 |
+
(async () => {
|
| 233 |
+
try {
|
| 234 |
+
const detectedObject = await AI_Core.detectObjects();
|
| 235 |
+
if (detectedObject && !MemoryManager.isRecent(detectedObject)) {
|
| 236 |
+
const isNew = !MemoryManager.check(detectedObject);
|
| 237 |
+
MemoryManager.add(detectedObject);
|
| 238 |
+
|
| 239 |
+
let promptBase = "";
|
| 240 |
+
if (AppState.activeMode === 'eco') {
|
| 241 |
+
promptBase = Prompts_AR.eco(detectedObject, isNew);
|
| 242 |
+
} else if (AppState.activeMode === 'root') {
|
| 243 |
+
promptBase = Prompts_AR.root(detectedObject, isNew);
|
| 244 |
+
}
|
| 245 |
+
const response = await AI_Core.generateText(promptBase);
|
| 246 |
+
this.updateOutput(response, true);
|
| 247 |
+
ConsciousnessMirror.log(AppState.activeMode, `vi un/a ${detectedObject} y te di una reflexión.`);
|
| 248 |
+
}
|
| 249 |
+
} catch (error) {
|
| 250 |
+
console.error("Error en el ciclo de análisis:", error);
|
| 251 |
+
} finally {
|
| 252 |
+
AppState.isProcessing = false;
|
| 253 |
+
if(AppState.isAwake) AppState.dom.statusText.textContent = "Atento";
|
| 254 |
+
}
|
| 255 |
+
})();
|
| 256 |
+
},
|
| 257 |
+
|
| 258 |
+
updateOutput(text, speak = false) {
|
| 259 |
+
AppState.dom.outputContainer.style.animation = 'none';
|
| 260 |
+
AppState.dom.outputContainer.offsetHeight;
|
| 261 |
+
AppState.dom.outputContainer.style.animation = 'slideUp 0.5s ease-out';
|
| 262 |
+
AppState.dom.outputText.textContent = text;
|
| 263 |
+
if (speak) VoiceManager.speak(text);
|
| 264 |
}
|
| 265 |
};
|
| 266 |
|
| 267 |
// ===================================================================
|
| 268 |
+
// MÓDULO 2: NÚCLEO DE INTELIGENCIA ARTIFICIAL
|
| 269 |
+
// ===================================================================
|
| 270 |
+
const AI_Core = {
|
| 271 |
+
async initModels() {
|
| 272 |
+
LifecycleManager.updateOutput("Cargando modelo de visión...", false);
|
| 273 |
+
AppState.models.detector = await pipeline('object-detection', 'Xenova/detr-resnet-50');
|
| 274 |
+
LifecycleManager.updateOutput("Cargando modelo de lenguaje...", false);
|
| 275 |
+
AppState.models.textGenerator = await pipeline('text-generation', 'Xenova/Phi-3-mini-4k-instruct-onnx-web');
|
| 276 |
+
},
|
| 277 |
+
async detectObjects() {
|
| 278 |
+
if (!AppState.isAwake || !AppState.models.detector || AppState.dom.cameraFeed.paused || AppState.dom.cameraFeed.ended) return null;
|
| 279 |
+
const image = await RawImage.fromElement(AppState.dom.cameraFeed);
|
| 280 |
+
const objects = await AppState.models.detector(image, { threshold: 0.9, percentage: true });
|
| 281 |
+
const mainObject = objects.filter(obj => obj.score > 0.9).sort((a,b) => b.score - a.score)[0];
|
| 282 |
+
return mainObject ? mainObject.label : null;
|
| 283 |
+
},
|
| 284 |
+
async generateText(prompt) {
|
| 285 |
+
if (!AppState.models.textGenerator) return "El modelo de lenguaje no está listo.";
|
| 286 |
+
const formattedPrompt = `<|user|>\nSos un asistente de IA argentino, amable y canchero. Respondé de forma concisa. ${prompt}<|end|>\n<|assistant|>\n`;
|
| 287 |
+
const result = await AppState.models.textGenerator(formattedPrompt, { max_new_tokens: 80 });
|
| 288 |
+
return result[0].generated_text.split('<|assistant|>').pop().trim();
|
| 289 |
+
}
|
| 290 |
+
};
|
| 291 |
+
|
| 292 |
+
// ===================================================================
|
| 293 |
+
// MÓDULO 3: GESTORES DE CÁMARA, MEMORIA Y CARACTERÍSTICAS
|
| 294 |
// ===================================================================
|
| 295 |
const CameraManager = {
|
| 296 |
async start() {
|
| 297 |
try {
|
| 298 |
const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' }, audio: false });
|
| 299 |
AppState.dom.cameraFeed.srcObject = stream;
|
| 300 |
+
return true;
|
| 301 |
} catch (error) {
|
| 302 |
console.error("Error al acceder a la cámara:", error);
|
| 303 |
+
return false;
|
| 304 |
}
|
| 305 |
}
|
| 306 |
};
|
| 307 |
|
| 308 |
+
const MemoryManager = {
|
| 309 |
+
add(objectLabel) {
|
| 310 |
+
AppState.memory.interactions[objectLabel] = {
|
| 311 |
+
timestamp: Date.now(),
|
| 312 |
+
count: (AppState.memory.interactions[objectLabel]?.count || 0) + 1
|
| 313 |
+
};
|
| 314 |
+
localStorage.setItem('synapseMemory', JSON.stringify(AppState.memory));
|
| 315 |
+
},
|
| 316 |
+
check(objectLabel) {
|
| 317 |
+
return AppState.memory.interactions[objectLabel] !== undefined;
|
| 318 |
+
},
|
| 319 |
+
isRecent(objectLabel) {
|
| 320 |
+
const interaction = AppState.memory.interactions[objectLabel];
|
| 321 |
+
return interaction && (Date.now() - interaction.timestamp < 30000);
|
| 322 |
+
}
|
| 323 |
+
};
|
| 324 |
+
|
| 325 |
+
const DigitalSerenity = {
|
| 326 |
+
triggerConsciousMoment() {
|
| 327 |
+
const text = Prompts_AR.serenity;
|
| 328 |
+
LifecycleManager.updateOutput(text, true);
|
| 329 |
+
AppState.dom.body.classList.add('serenity-nudge');
|
| 330 |
+
if ('vibrate' in navigator) navigator.vibrate([4000, 1000, 6000]);
|
| 331 |
+
ConsciousnessMirror.log('Modo Zen', `se activó un momento de calma.`);
|
| 332 |
+
setTimeout(() => AppState.dom.body.classList.remove('serenity-nudge'), 11000);
|
| 333 |
+
}
|
| 334 |
+
};
|
| 335 |
|
| 336 |
+
const ConsciousnessMirror = {
|
| 337 |
+
log(source, reason) {
|
| 338 |
+
const logEntry = { source, reason, timestamp: new Date().toLocaleTimeString() };
|
| 339 |
+
let logs = JSON.parse(localStorage.getItem('synapseLogs') || '[]');
|
| 340 |
+
logs.unshift(logEntry);
|
| 341 |
+
logs = logs.slice(0, 20);
|
| 342 |
+
localStorage.setItem('synapseLogs', JSON.stringify(logs));
|
| 343 |
+
this.render();
|
| 344 |
+
},
|
| 345 |
+
render() {
|
| 346 |
+
const logs = JSON.parse(localStorage.getItem('synapseLogs') || '[]');
|
| 347 |
+
AppState.dom.mirrorLog.innerHTML = logs.map(entry => `
|
| 348 |
+
<div class="log-entry">
|
| 349 |
+
<strong>${entry.source} (${entry.timestamp})</strong>
|
| 350 |
+
<p>${Prompts_AR.mirrorLog(entry.source, entry.reason)}</p>
|
| 351 |
+
</div>
|
| 352 |
+
`).join('');
|
| 353 |
+
}
|
| 354 |
+
};
|
| 355 |
|
| 356 |
+
// ===================================================================
|
| 357 |
// PUNTO DE ENTRADA PRINCIPAL
|
| 358 |
+
// ===================================================================
|
| 359 |
window.onload = () => {
|
| 360 |
LifecycleManager.init();
|
| 361 |
};
|