Lukeetah commited on
Commit
6dd2a17
·
verified ·
1 Parent(s): 2742f0d

Update static/main.js

Browse files
Files changed (1) hide show
  1. 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, // Nuevo estado para controlar el proceso de despertar
7
- isAwake: false, // La IA está 100% operativa
8
- // ... otros estados como antes ...
9
- voice: null, // Acá guardaremos el objeto de voz seleccionado
 
 
 
 
 
 
 
10
  };
11
 
12
  // ===================================================================
13
- // MÓDULO 0: PERSONALIDAD Y VOZ (EL ALMA ARGENTINA MEJORADA)
14
  // ===================================================================
15
- // ... Prompts sin cambios ...
 
 
 
 
 
 
 
 
 
 
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; // ¡Usamos el objeto de voz, no solo el lang!
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 (CON NUEVO FLUJO DE ARRANQUE)
75
  // ===================================================================
76
  const LifecycleManager = {
77
  init() {
78
- this.cacheDom(); // Cachea elementos del DOM
79
- this.bindEventListeners(); // Vincula los botones
80
- this.checkUserAndMemory(); // Chequea si ya existe el usuario
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  },
82
 
83
  async checkUserAndMemory() {
84
- await VoiceManager.loadVoices(); // ¡Cargamos las voces al principio!
85
  const user = localStorage.getItem('synapseUser');
86
- // ... Lógica de usuario igual que antes ...
87
- if (user) {
88
- // ...
89
- // En vez de arrancar todo, solo mostramos el botón de despertar
90
- AppState.dom.loadingStatus.style.display = 'none';
91
- AppState.dom.wakeAiBtn.style.display = 'block';
92
- AppState.dom.appContainer.classList.add('visible'); // Mostramos la app principal, pero deshabilitada
93
  AppState.dom.welcomeScreen.classList.remove('visible');
 
 
 
94
  } else {
95
- // ... Muestra la creación de usuario ...
 
 
 
96
  }
97
  },
98
 
99
- bindEventListeners() {
100
- // ... otros listeners ...
101
- AppState.dom.wakeAiBtn.addEventListener('click', () => this.wakeUpAI());
 
 
 
 
 
 
 
 
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
- AppState.dom.wakeAiBtn.style.display = 'none'; // Ocultamos el botón de despertar
110
- LifecycleManager.updateOutput("¡Buenas! Dame un toque que me despierto...", true);
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
- LifecycleManager.updateOutput("No pude prender la cámara, che. Fijate los permisos y probá de nuevo.", true);
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 (!AppState.isAwake) { // No hacer nada si la IA no está despierta
143
- if (mode === 'serenity') DigitalSerenity.triggerConsciousMoment();
144
  return;
145
  }
146
- // ... resto de la lógica ...
 
 
 
147
  },
148
 
 
 
 
 
 
 
 
 
 
 
149
  runContinuousAnalysis() {
150
  if (!AppState.isAwake || AppState.isProcessing || AppState.isSpeaking) return;
 
 
151
  AppState.dom.statusText.textContent = "Viendo...";
152
- // ... resto del código del ciclo ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  }
154
  };
155
 
156
  // ===================================================================
157
- // MÓDULO 3: GESTOR DE CÁMARA (MODIFICADO PARA DEVOLVER ESTADO)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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; // Devuelve éxito
165
  } catch (error) {
166
  console.error("Error al acceder a la cámara:", error);
167
- return false; // Devuelve fracaso
168
  }
169
  }
170
  };
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
- // (Los otros módulos como AI_Core, MemoryManager, etc., permanecen igual)
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
  };