Lukeetah commited on
Commit
0b72f2a
verified
1 Parent(s): e0939cf

Update static/main.js

Browse files
Files changed (1) hide show
  1. static/main.js +195 -201
static/main.js CHANGED
@@ -1,61 +1,109 @@
1
- // Importamos las funciones necesarias de la biblioteca 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 y Control de la Aplicaci贸n
5
  const AppState = {
6
  isInitialized: false,
7
- currentUser: null,
8
- activeMode: null, // 'eco', 'root', 'serenity'
 
9
  isProcessing: false,
10
- models: {
11
- detector: null,
12
- textGenerator: null,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  },
14
- dom: { // Referencias a elementos del DOM para f谩cil acceso
15
- welcomeScreen: document.getElementById('welcome-screen'),
16
- appContainer: document.getElementById('app-container'),
17
- loadingStatus: document.getElementById('loading-status'),
18
- userCreation: document.getElementById('user-creation'),
19
- usernameInput: document.getElementById('username-input'),
20
- createUserBtn: document.getElementById('create-user-btn'),
21
- welcomeUser: document.getElementById('welcome-user'),
22
- cameraFeed: document.getElementById('camera-feed'),
23
- outputText: document.getElementById('output-text'),
24
- body: document.getElementById('synapse-body'),
25
- mirror: document.getElementById('consciousness-mirror'),
26
- mirrorLog: document.getElementById('mirror-log'),
27
- mirrorToggleBtn: document.getElementById('mirror-toggle-btn'),
28
- closeMirrorBtn: document.getElementById('close-mirror-btn'),
29
- ecoBtn: document.getElementById('eco-btn'),
30
- rootBtn: document.getElementById('root-btn'),
31
- serenityBtn: document.getElementById('serenity-btn')
32
  }
33
  };
34
 
35
  // ===================================================================
36
- // M脫DULO 1: GESTI脫N DE USUARIO Y CICLO DE VIDA DE LA APP
37
  // ===================================================================
38
  const LifecycleManager = {
39
  init() {
40
- console.log("Synapse AI: Iniciando...");
41
  this.bindEventListeners();
42
- this.checkUser();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  },
44
 
45
  bindEventListeners() {
46
  AppState.dom.createUserBtn.addEventListener('click', () => this.createUser());
47
- AppState.dom.mirrorToggleBtn.addEventListener('click', () => ConsciousnessMirror.toggle(true));
48
- AppState.dom.closeMirrorBtn.addEventListener('click', () => ConsciousnessMirror.toggle(false));
49
- AppState.dom.ecoBtn.addEventListener('click', () => this.setActiveMode('eco'));
50
- AppState.dom.rootBtn.addEventListener('click', () => this.setActiveMode('root'));
51
- AppState.dom.serenityBtn.addEventListener('click', () => DigitalSerenity.triggerConsciousMoment());
 
 
 
 
 
 
52
  },
53
-
54
- checkUser() {
55
- // La gesti贸n de usuario es 100% local, respetando la privacidad.
56
  const user = localStorage.getItem('synapseUser');
57
- if (user) {
58
- AppState.currentUser = JSON.parse(user);
 
 
 
 
59
  this.startApp();
60
  } else {
61
  AppState.dom.loadingStatus.style.display = 'none';
@@ -65,248 +113,194 @@ const LifecycleManager = {
65
 
66
  createUser() {
67
  const username = AppState.dom.usernameInput.value.trim();
68
- if (username) {
69
- AppState.currentUser = { name: username };
70
- localStorage.setItem('synapseUser', JSON.stringify(AppState.currentUser));
 
 
 
 
 
71
  this.startApp();
72
  }
73
  },
74
 
75
  async startApp() {
76
- AppState.dom.welcomeUser.textContent = `Hola, ${AppState.currentUser.name}`;
77
- AppState.dom.welcomeScreen.style.display = 'none';
78
- AppState.dom.appContainer.style.display = 'flex';
79
 
80
  await AI_Core.initModels();
81
  await CameraManager.start();
82
- DigitalSerenity.startMonitoring(); // El monitor de estr茅s se inicia en segundo plano
83
 
84
- AppState.isInitialized = true;
85
- this.setActiveMode('eco'); // Modo por defecto
 
 
 
 
 
 
 
 
 
86
  },
87
 
88
  setActiveMode(mode) {
89
- if (!AppState.isInitialized || AppState.isProcessing) return;
 
 
 
90
 
91
  AppState.activeMode = mode;
92
- console.log(`Modo activo: ${mode}`);
93
-
94
- // Actualizar UI de botones
95
- document.querySelectorAll('.control-btn').forEach(btn => btn.classList.remove('active'));
96
- AppState.dom[`${mode}Btn`].classList.add('active');
97
-
98
- // Detener an谩lisis continuo si no es un modo de c谩mara
99
- if (this.analysisInterval) clearInterval(this.analysisInterval);
100
 
101
- if (mode === 'eco' || mode === 'root') {
102
- AppState.dom.outputText.textContent = "Apunta la c谩mara para descubrir...";
103
- this.analysisInterval = setInterval(this.runContinuousAnalysis, 3000); // Analiza cada 3 segundos
 
 
 
 
 
 
104
  }
 
105
  },
106
 
107
  async runContinuousAnalysis() {
108
- if (AppState.isProcessing || !['eco', 'root'].includes(AppState.activeMode)) return;
 
 
 
109
  AppState.isProcessing = true;
 
110
 
111
  try {
112
  const detectedObject = await AI_Core.detectObjects();
113
- if (detectedObject) {
 
 
 
 
114
  if (AppState.activeMode === 'eco') {
115
- await EcoResonance.process(detectedObject);
116
  } else if (AppState.activeMode === 'root') {
117
- await RootConnection.process(detectedObject);
118
  }
 
 
 
119
  }
120
  } catch (error) {
121
- console.error("Error en el an谩lisis continuo:", error);
122
  } finally {
123
  AppState.isProcessing = false;
124
  }
 
 
 
 
 
 
 
 
125
  }
126
  };
127
 
128
  // ===================================================================
129
- // M脫DULO 2: N脷CLEO DE INTELIGENCIA ARTIFICIAL (TRANSFORMERS.JS)
130
  // ===================================================================
131
  const AI_Core = {
132
  async initModels() {
133
- AppState.dom.loadingStatus.textContent = "Cargando modelo de visi贸n... (puede tardar)";
134
- // Modelo ligero para detecci贸n de objetos, optimizado para web
135
  AppState.models.detector = await pipeline('object-detection', 'Xenova/detr-resnet-50');
136
-
137
- AppState.dom.loadingStatus.textContent = "Cargando modelo de lenguaje... (casi listo)";
138
- // Phi-3-mini es una excelente opci贸n por su balance tama帽o/rendimiento
139
  AppState.models.textGenerator = await pipeline('text-generation', 'Xenova/Phi-3-mini-4k-instruct-onnx-web');
140
-
141
- AppState.dom.loadingStatus.textContent = "Modelos cargados. Iniciando c谩mara...";
142
  },
143
-
144
- async detectObjects() {
145
- if (!AppState.models.detector || AppState.cameraFeed.paused || AppState.cameraFeed.ended) return null;
146
-
147
  const image = await RawImage.fromElement(AppState.dom.cameraFeed);
148
- const objects = await AppState.models.detector(image, {
149
- threshold: 0.9, // Umbral alto para resultados m谩s precisos
150
- percentage: true,
151
- });
152
-
153
- // Devolvemos el objeto con la puntuaci贸n m谩s alta
154
  const mainObject = objects.filter(obj => obj.score > 0.9).sort((a,b) => b.score - a.score)[0];
155
-
156
- if (mainObject) {
157
- console.log("Objeto detectado:", mainObject.label);
158
- return mainObject.label;
159
- }
160
- return null;
161
  },
162
-
163
- async generateText(prompt) {
164
  if (!AppState.models.textGenerator) return "El modelo de lenguaje no est谩 listo.";
165
-
166
- // Formateamos el prompt para el modelo instructivo
167
- const formattedPrompt = `<|user|>\n${prompt}<|end|>\n<|assistant|>\n`;
168
-
169
- const result = await AppState.models.textGenerator(formattedPrompt, {
170
- max_new_tokens: 80,
171
- temperature: 0.7,
172
- do_sample: true,
173
- //eos_token_id: AppState.models.textGenerator.tokenizer.eos_token_id
174
- });
175
-
176
- // Limpiamos la salida para obtener solo la respuesta del asistente
177
  return result[0].generated_text.split('<|assistant|>').pop().trim();
178
  }
179
  };
180
 
181
  // ===================================================================
182
- // M脫DULO 3: GESTI脫N DE C脕MARA Y SENSORES
183
  // ===================================================================
184
  const CameraManager = {
185
  async start() {
186
  try {
187
- const stream = await navigator.mediaDevices.getUserMedia({
188
- video: { facingMode: 'environment' }, // Prioriza la c谩mara trasera
189
- audio: false
190
- });
191
  AppState.dom.cameraFeed.srcObject = stream;
192
  } catch (error) {
193
  console.error("Error al acceder a la c谩mara:", error);
194
- AppState.dom.outputText.textContent = "No se pudo acceder a la c谩mara. Por favor, otorga los permisos.";
195
  }
196
  }
197
  };
198
 
199
- // ===================================================================
200
- // M脫DULO 4: CARACTER脥STICAS DE SYNAPSE
201
- // ===================================================================
202
-
203
- const EcoResonance = {
204
- async process(objectLabel) {
205
- AppState.dom.outputText.textContent = `Detectado: ${objectLabel}. Analizando impacto...`;
206
- const prompt = `Soy un asistente de IA llamado Synapse. He detectado un objeto: "${objectLabel}". En espa帽ol, en 1-2 frases cortas y directas, describe su impacto ambiental y c贸mo reciclarlo correctamente. S茅 conciso y motivador.`;
207
- const response = await AI_Core.generateText(prompt);
208
- AppState.dom.outputText.innerHTML = `<strong>${objectLabel}:</strong> ${response}`;
209
-
210
- // Nudge visual y h谩ptico
211
- this.triggerNudge();
212
- ConsciousnessMirror.log('Eco-Resonancia', `Sugerencia de reciclaje para "${objectLabel}".`);
213
  },
214
- triggerNudge() {
215
- AppState.dom.body.classList.add('eco-nudge');
216
- if ('vibrate' in navigator) navigator.vibrate([100, 50, 100]); // Vibraci贸n corta y r铆tmica
217
- setTimeout(() => AppState.dom.body.classList.remove('eco-nudge'), 2000);
218
- }
219
- };
220
-
221
- const RootConnection = {
222
- async process(objectLabel) {
223
- AppState.dom.outputText.textContent = `Detectado: ${objectLabel}. Buscando conexi贸n...`;
224
- const prompt = `Soy un asistente de IA llamado Synapse. He detectado: "${objectLabel}". En espa帽ol, proporciona un dato curioso o una reflexi贸n po茅tica sobre este objeto/ser en 1-2 frases para fomentar la conexi贸n con el entorno.`;
225
- const response = await AI_Core.generateText(prompt);
226
- AppState.dom.outputText.innerHTML = `<strong>${objectLabel}:</strong> ${response}`;
227
- ConsciousnessMirror.log('Conexi贸n Ra铆z', `Dato curioso generado para "${objectLabel}".`);
228
  }
229
  };
230
 
231
  const DigitalSerenity = {
232
- stressLevel: 0,
233
- lastActivityTime: Date.now(),
234
-
235
- startMonitoring() {
236
- // Simulaci贸n de monitoreo de estr茅s basado en la actividad del usuario en la app.
237
- // En una app nativa, esto podr铆a acceder a m谩s datos (con permiso).
238
- window.addEventListener('click', () => this.recordActivity());
239
- window.addEventListener('keydown', () => this.recordActivity());
240
- setInterval(() => this.decayStress(), 5000); // El estr茅s disminuye con el tiempo
241
- },
242
-
243
- recordActivity() {
244
- const now = Date.now();
245
- const timeDiff = now - this.lastActivityTime;
246
- if (timeDiff < 500) { // Actividad muy r谩pida incrementa el estr茅s
247
- this.stressLevel = Math.min(10, this.stressLevel + 1);
248
- }
249
- this.lastActivityTime = now;
250
- if (this.stressLevel >= 8) {
251
- this.triggerConsciousMoment();
252
- this.stressLevel = 0; // Resetear despu茅s de la intervenci贸n
253
- }
254
- },
255
-
256
- decayStress() {
257
- this.stressLevel = Math.max(0, this.stressLevel - 0.5);
258
- },
259
-
260
  triggerConsciousMoment() {
261
- console.log("Activando Momento Consciente...");
 
262
  AppState.dom.body.classList.add('serenity-nudge');
263
-
264
- AppState.dom.outputText.textContent = "Respira. Un momento de calma para ti.";
265
-
266
- // Gu铆a de respiraci贸n h谩ptica
267
- if ('vibrate' in navigator) {
268
- // Inhala (4s), Sostiene (1s), Exhala (6s)
269
- navigator.vibrate([4000, 1000, 6000]);
270
- }
271
-
272
- // Paisaje sonoro (requiere un archivo de audio)
273
- // const audio = new Audio('/static/sounds/stream.mp3');
274
- // audio.play();
275
-
276
- ConsciousnessMirror.log('Serenidad Digital', 'Se detect贸 un patr贸n de estr茅s y se activ贸 un momento consciente.');
277
-
278
- setTimeout(() => {
279
- AppState.dom.body.classList.remove('serenity-nudge');
280
- AppState.dom.outputText.textContent = "Sigue explorando tu entorno.";
281
- }, 11000);
282
  }
283
  };
284
 
285
  const ConsciousnessMirror = {
286
- logEntries: [],
287
-
288
  log(source, reason) {
289
- const timestamp = new Date().toLocaleTimeString();
290
- this.logEntries.push({ source, reason, timestamp });
 
 
 
 
 
 
 
291
  this.render();
292
  },
293
-
294
- toggle(show) {
295
- AppState.dom.mirror.classList.toggle('visible', show);
296
- },
297
-
298
  render() {
299
- AppState.dom.mirrorLog.innerHTML = '';
300
- [...this.logEntries].reverse().forEach(entry => {
301
- const logElement = document.createElement('div');
302
- logElement.className = 'log-entry';
303
- logElement.innerHTML = `<strong>${entry.source} (${entry.timestamp})</strong><p>${entry.reason}</p>`;
304
- AppState.dom.mirrorLog.appendChild(logElement);
305
- });
306
  }
307
  };
308
 
309
-
310
  // ===================================================================
311
  // PUNTO DE ENTRADA PRINCIPAL
312
  // ===================================================================
 
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
  isInitialized: false,
7
+ user: { name: 'Che' },
8
+ ai: { name: 'Synapse' },
9
+ activeMode: 'eco',
10
  isProcessing: false,
11
+ isSpeaking: false,
12
+ models: { detector: null, textGenerator: null },
13
+ dom: {}, // Se llena en LifecycleManager.init
14
+ analysisInterval: null
15
+ };
16
+
17
+ // ===================================================================
18
+ // M脫DULO 0: PERSONALIDAD Y VOZ (EL ALMA ARGENTINA)
19
+ // ===================================================================
20
+ const Prompts_AR = {
21
+ greeting: (aiName) => `隆Buenas! Soy ${aiName}, tu compa帽ero digital. 驴En qu茅 te puedo ayudar hoy? Apunt谩 la c谩mara y vemos qu茅 onda.`,
22
+ eco: (object, isNew) => isNew ?
23
+ `Che, eso parece un/a ${object}. 驴Sab茅s la posta para reciclarlo? Te cuento: ` :
24
+ `Otra vez un/a ${object}, 隆qu茅 copado! Nos estamos volviendo expertos en esto, 驴eh? `,
25
+ root: (object, isNew) => isNew ?
26
+ `Mir谩 qu茅 bueno, un/a ${object}. Te tiro un dato de color sobre esto que seguro no sab铆as: ` :
27
+ `隆De nuevo por ac谩, ${object}! Me encanta que sigamos explorando juntos. `,
28
+ 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...`,
29
+ mirrorLog: (source, reason) => `A esta hora, en modo "${source}", te suger铆 algo porque ${reason}.`
30
+ };
31
+
32
+ const VoiceManager = {
33
+ init() {
34
+ this.synth = window.speechSynthesis;
35
+ this.utterance = new SpeechSynthesisUtterance();
36
+ this.utterance.lang = 'es-AR'; // Intentamos conseguir la voz argentina
37
+ this.utterance.rate = 1;
38
+ this.utterance.onstart = () => AppState.isSpeaking = true;
39
+ this.utterance.onend = () => AppState.isSpeaking = false;
40
  },
41
+ speak(text) {
42
+ if (this.synth.speaking) this.synth.cancel();
43
+ this.utterance.text = text;
44
+ this.synth.speak(this.utterance);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  }
46
  };
47
 
48
  // ===================================================================
49
+ // M脫DULO 1: GESTI脫N DE CICLO DE VIDA, USUARIO Y MEMORIA
50
  // ===================================================================
51
  const LifecycleManager = {
52
  init() {
53
+ this.cacheDom();
54
  this.bindEventListeners();
55
+ this.checkUserAndMemory();
56
+ VoiceManager.init();
57
+ },
58
+
59
+ cacheDom() {
60
+ AppState.dom = { // Llenamos el objeto DOM
61
+ welcomeScreen: document.getElementById('welcome-screen'),
62
+ appContainer: document.getElementById('app-container'),
63
+ loadingStatus: document.getElementById('loading-status'),
64
+ userCreation: document.getElementById('user-creation'),
65
+ usernameInput: document.getElementById('username-input'),
66
+ ainameInput: document.getElementById('ainame-input'),
67
+ createUserBtn: document.getElementById('create-user-btn'),
68
+ cameraFeed: document.getElementById('camera-feed'),
69
+ statusText: document.getElementById('status-text'),
70
+ outputText: document.getElementById('output-text'),
71
+ outputContainer: document.getElementById('output-container'),
72
+ body: document.getElementById('synapse-body'),
73
+ settingsScreen: document.getElementById('settings-screen'),
74
+ mirrorScreen: document.getElementById('consciousness-mirror'),
75
+ mirrorLog: document.getElementById('mirror-log'),
76
+ settingsBtn: document.getElementById('settings-btn'),
77
+ saveSettingsBtn: document.getElementById('save-settings-btn'),
78
+ mirrorToggleBtn: document.getElementById('mirror-toggle-btn'),
79
+ closePanelBtns: document.querySelectorAll('.close-panel-btn'),
80
+ controlBtns: document.querySelectorAll('.control-btn')
81
+ };
82
  },
83
 
84
  bindEventListeners() {
85
  AppState.dom.createUserBtn.addEventListener('click', () => this.createUser());
86
+ AppState.dom.settingsBtn.addEventListener('click', () => this.togglePanel('settingsScreen', true));
87
+ AppState.dom.mirrorToggleBtn.addEventListener('click', () => {
88
+ this.togglePanel('settingsScreen', false);
89
+ this.togglePanel('mirrorScreen', true);
90
+ });
91
+ AppState.dom.closePanelBtns.forEach(btn => btn.addEventListener('click', () => {
92
+ this.togglePanel('settingsScreen', false);
93
+ this.togglePanel('mirrorScreen', false);
94
+ }));
95
+ AppState.dom.controlBtns.forEach(btn => btn.addEventListener('click', () => this.setActiveMode(btn.dataset.mode)));
96
+ AppState.dom.saveSettingsBtn.addEventListener('click', () => this.saveSettings());
97
  },
98
+
99
+ checkUserAndMemory() {
 
100
  const user = localStorage.getItem('synapseUser');
101
+ const ai = localStorage.getItem('synapseAI');
102
+ const memory = localStorage.getItem('synapseMemory');
103
+ if (user && ai) {
104
+ AppState.user = JSON.parse(user);
105
+ AppState.ai = JSON.parse(ai);
106
+ AppState.memory = memory ? JSON.parse(memory) : { interactions: {} };
107
  this.startApp();
108
  } else {
109
  AppState.dom.loadingStatus.style.display = 'none';
 
113
 
114
  createUser() {
115
  const username = AppState.dom.usernameInput.value.trim();
116
+ const ainame = AppState.dom.ainameInput.value.trim();
117
+ if (username && ainame) {
118
+ AppState.user.name = username;
119
+ AppState.ai.name = ainame;
120
+ localStorage.setItem('synapseUser', JSON.stringify(AppState.user));
121
+ localStorage.setItem('synapseAI', JSON.stringify(AppState.ai));
122
+ localStorage.setItem('synapseMemory', JSON.stringify({ interactions: {} }));
123
+ AppState.memory = { interactions: {} };
124
  this.startApp();
125
  }
126
  },
127
 
128
  async startApp() {
129
+ AppState.dom.welcomeScreen.classList.remove('visible');
130
+ AppState.dom.appContainer.classList.add('visible');
 
131
 
132
  await AI_Core.initModels();
133
  await CameraManager.start();
 
134
 
135
+ this.setActiveMode(AppState.activeMode); // Activa el modo por defecto
136
+ const greeting = Prompts_AR.greeting(AppState.ai.name);
137
+ this.updateOutput(greeting, true);
138
+
139
+ // Inicia el Ciclo de Conciencia Permanente
140
+ if (AppState.analysisInterval) clearInterval(AppState.analysisInterval);
141
+ AppState.analysisInterval = setInterval(this.runContinuousAnalysis, 4000);
142
+ },
143
+
144
+ togglePanel(panelId, show) {
145
+ document.getElementById(panelId).classList.toggle('visible', show);
146
  },
147
 
148
  setActiveMode(mode) {
149
+ if (!AppState.isInitialized || mode === AppState.activeMode) {
150
+ if (mode === 'serenity') DigitalSerenity.triggerConsciousMoment();
151
+ return;
152
+ }
153
 
154
  AppState.activeMode = mode;
155
+ AppState.dom.controlBtns.forEach(btn => btn.classList.toggle('active', btn.dataset.mode === mode));
 
 
 
 
 
 
 
156
 
157
+ if (mode === 'serenity') DigitalSerenity.triggerConsciousMoment();
158
+ },
159
+
160
+ saveSettings() {
161
+ const newAiName = document.getElementById('ainame-settings-input').value.trim();
162
+ if (newAiName) {
163
+ AppState.ai.name = newAiName;
164
+ localStorage.setItem('synapseAI', JSON.stringify(AppState.ai));
165
+ this.updateOutput(`隆Listo! A partir de ahora me llamo ${newAiName}.`, true);
166
  }
167
+ this.togglePanel('settings-screen', false);
168
  },
169
 
170
  async runContinuousAnalysis() {
171
+ if (AppState.isProcessing || AppState.isSpeaking || ['serenity'].includes(AppState.activeMode)) {
172
+ AppState.dom.statusText.textContent = AppState.isSpeaking ? "Hablando..." : "En modo Zen";
173
+ return;
174
+ }
175
  AppState.isProcessing = true;
176
+ AppState.dom.statusText.textContent = "Viendo...";
177
 
178
  try {
179
  const detectedObject = await AI_Core.detectObjects();
180
+ if (detectedObject && !MemoryManager.isRecent(detectedObject)) {
181
+ const isNew = !MemoryManager.check(detectedObject);
182
+ MemoryManager.add(detectedObject);
183
+
184
+ let promptBase = "";
185
  if (AppState.activeMode === 'eco') {
186
+ promptBase = Prompts_AR.eco(detectedObject, isNew);
187
  } else if (AppState.activeMode === 'root') {
188
+ promptBase = Prompts_AR.root(detectedObject, isNew);
189
  }
190
+ const response = await AI_Core.generateText(promptBase);
191
+ LifecycleManager.updateOutput(response, true);
192
+ ConsciousnessMirror.log(AppState.activeMode, `vi un/a ${detectedObject} y te di una reflexi贸n.`);
193
  }
194
  } catch (error) {
195
+ console.error("Error en el ciclo de an谩lisis:", error);
196
  } finally {
197
  AppState.isProcessing = false;
198
  }
199
+ },
200
+
201
+ updateOutput(text, speak = false) {
202
+ AppState.dom.outputContainer.style.animation = 'none';
203
+ AppState.dom.outputContainer.offsetHeight; // Truco para reiniciar animaci贸n
204
+ AppState.dom.outputContainer.style.animation = 'slideUp 0.5s ease-out';
205
+ AppState.dom.outputText.textContent = text;
206
+ if (speak) VoiceManager.speak(text);
207
  }
208
  };
209
 
210
  // ===================================================================
211
+ // M脫DULO 2: N脷CLEO DE IA (SIN CAMBIOS GRANDES)
212
  // ===================================================================
213
  const AI_Core = {
214
  async initModels() {
215
+ AppState.dom.loadingStatus.textContent = "Cargando modelo de visi贸n...";
 
216
  AppState.models.detector = await pipeline('object-detection', 'Xenova/detr-resnet-50');
217
+ AppState.dom.loadingStatus.textContent = "Cargando modelo de lenguaje...";
 
 
218
  AppState.models.textGenerator = await pipeline('text-generation', 'Xenova/Phi-3-mini-4k-instruct-onnx-web');
219
+ AppState.isInitialized = true;
 
220
  },
221
+ async detectObjects() { /* ... c贸digo sin cambios ... */
222
+ if (!AppState.models.detector || AppState.dom.cameraFeed.paused || AppState.dom.cameraFeed.ended) return null;
 
 
223
  const image = await RawImage.fromElement(AppState.dom.cameraFeed);
224
+ const objects = await AppState.models.detector(image, { threshold: 0.9, percentage: true });
 
 
 
 
 
225
  const mainObject = objects.filter(obj => obj.score > 0.9).sort((a,b) => b.score - a.score)[0];
226
+ return mainObject ? mainObject.label : null;
 
 
 
 
 
227
  },
228
+ async generateText(prompt) { /* ... c贸digo sin cambios ... */
 
229
  if (!AppState.models.textGenerator) return "El modelo de lenguaje no est谩 listo.";
230
+ const formattedPrompt = `<|user|>\nSos un asistente de IA argentino, amable y canchero. Respond茅 de forma concisa. ${prompt}<|end|>\n<|assistant|>\n`;
231
+ const result = await AppState.models.textGenerator(formattedPrompt, { max_new_tokens: 80 });
 
 
 
 
 
 
 
 
 
 
232
  return result[0].generated_text.split('<|assistant|>').pop().trim();
233
  }
234
  };
235
 
236
  // ===================================================================
237
+ // M脫DULO 3: GESTORES DE C脕MARA, MEMORIA Y CARACTER脥STICAS
238
  // ===================================================================
239
  const CameraManager = {
240
  async start() {
241
  try {
242
+ const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' }, audio: false });
 
 
 
243
  AppState.dom.cameraFeed.srcObject = stream;
244
  } catch (error) {
245
  console.error("Error al acceder a la c谩mara:", error);
246
+ LifecycleManager.updateOutput("隆Ups! No pude prender la c谩mara. Fijate si me diste los permisos, che.", true);
247
  }
248
  }
249
  };
250
 
251
+ const MemoryManager = {
252
+ add(objectLabel) {
253
+ AppState.memory.interactions[objectLabel] = {
254
+ timestamp: Date.now(),
255
+ count: (AppState.memory.interactions[objectLabel]?.count || 0) + 1
256
+ };
257
+ localStorage.setItem('synapseMemory', JSON.stringify(AppState.memory));
 
 
 
 
 
 
 
258
  },
259
+ check(objectLabel) {
260
+ return AppState.memory.interactions[objectLabel] !== undefined;
261
+ },
262
+ isRecent(objectLabel) {
263
+ const interaction = AppState.memory.interactions[objectLabel];
264
+ // Evita que hable del mismo objeto si lo vio hace menos de 30 segundos
265
+ return interaction && (Date.now() - interaction.timestamp < 30000);
 
 
 
 
 
 
 
266
  }
267
  };
268
 
269
  const DigitalSerenity = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  triggerConsciousMoment() {
271
+ const text = Prompts_AR.serenity;
272
+ LifecycleManager.updateOutput(text, true);
273
  AppState.dom.body.classList.add('serenity-nudge');
274
+ if ('vibrate' in navigator) navigator.vibrate([4000, 1000, 6000]);
275
+ ConsciousnessMirror.log('Modo Zen', `se activ贸 un momento de calma.`);
276
+ setTimeout(() => AppState.dom.body.classList.remove('serenity-nudge'), 11000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  }
278
  };
279
 
280
  const ConsciousnessMirror = {
 
 
281
  log(source, reason) {
282
+ const logEntry = {
283
+ source,
284
+ reason,
285
+ timestamp: new Date().toLocaleTimeString()
286
+ };
287
+ let logs = JSON.parse(localStorage.getItem('synapseLogs') || '[]');
288
+ logs.unshift(logEntry);
289
+ logs = logs.slice(0, 20); // Guardamos los 煤ltimos 20 logs
290
+ localStorage.setItem('synapseLogs', JSON.stringify(logs));
291
  this.render();
292
  },
 
 
 
 
 
293
  render() {
294
+ const logs = JSON.parse(localStorage.getItem('synapseLogs') || '[]');
295
+ AppState.dom.mirrorLog.innerHTML = logs.map(entry => `
296
+ <div class="log-entry">
297
+ <strong>${entry.source} (${entry.timestamp})</strong>
298
+ <p>${Prompts_AR.mirrorLog(entry.source, entry.reason)}</p>
299
+ </div>
300
+ `).join('');
301
  }
302
  };
303
 
 
304
  // ===================================================================
305
  // PUNTO DE ENTRADA PRINCIPAL
306
  // ===================================================================