Synapse / static /main.js
Lukeetah's picture
Update static/main.js
6dd2a17 verified
// 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 => `
<div class="log-entry">
<strong>${entry.source} (${entry.timestamp})</strong>
<p>${Prompts_AR.mirrorLog(entry.source, entry.reason)}</p>
</div>
`).join('');
}
};
// ===================================================================
// PUNTO DE ENTRADA PRINCIPAL
// ===================================================================
window.onload = () => {
LifecycleManager.init();
};