JARVIS/nucleo/cara/hud/puente.js
sito 8e4bc8ad94 JARVIS: asistente de voz local para Linux
Nucleo propio: oye con whisper.cpp, piensa con un modelo de Ollama, habla
con Piper, y hace RAG sobre los apuntes del usuario. 100% local, sin
cuentas ni claves.

Escrito bajo una restriccion dura, 4 GB de VRAM: el cerebro y whisper
comparten tarjeta y solo caben porque estan dimensionados para ello. El
RAG usa embeddings estaticos con busqueda hibrida; la voz clonada se sirve
de una cache de frases.

Incluye instalador (install.sh), requisitos, y documentacion del stack,
del manejo de root y de las acciones. Los apuntes indexados y el diario NO
se incluyen: son privados y el .gitignore los bloquea.
2026-08-16 15:34:37 +02:00

97 lines
3.3 KiB
JavaScript

/* El puente entre el HUD y el nucleo, por WebSocket.
*
* En la version sobre Newelle el HUD vivia dentro de un widget WebKit y hablaba
* por window.webkit.messageHandlers. Fuera del flatpak no hay tal widget, pero
* hud.js usa ese puente en ocho sitios y no merece la pena tocarlos: aqui se
* SUPLANTA window.webkit con un objeto que manda lo mismo por el socket.
*
* Asi hud.js se queda exactamente igual que estaba —cero diff sobre 2.064
* lineas que ya funcionaban— y ademas sigue abriendose suelto en un navegador
* para mirar el diseño, que es como se ha estado trabajando en el.
*
* Este fichero se carga ANTES que hud.js: cuando hud.js pregunte por
* window.webkit, ya esta.
*/
(function () {
"use strict";
// Ya estamos dentro de WebKit (queda de la epoca de Newelle): no tocar nada.
if (window.webkit && window.webkit.messageHandlers) return;
const URL = "ws://" + (location.host || "127.0.0.1:8790") + "/puente";
let socket = null;
let cola = []; // lo que se pulse antes de que abra el socket
let esperaReconexion = 500;
function manda(canal, dato) {
const mensaje = JSON.stringify({ canal: canal, dato: String(dato) });
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(mensaje);
} else {
// No se pierde: un boton pulsado mientras reconecta tiene que llegar
// igual, o el panel parece que se ha colgado. Con tope, que si el nucleo
// esta caido de verdad no queremos acumular mil pulsaciones.
if (cola.length < 50) cola.push(mensaje);
}
}
// El mismo interfaz que ofrecia WebKit, para que hud.js no note el cambio.
window.webkit = {
messageHandlers: {
accion: { postMessage: (d) => manda("accion", d) },
listo: { postMessage: (d) => manda("listo", d) },
},
};
function conecta() {
try {
socket = new WebSocket(URL);
} catch (e) {
reintenta();
return;
}
socket.addEventListener("open", () => {
esperaReconexion = 500;
marcaCaido(false);
const pendientes = cola;
cola = [];
pendientes.forEach((m) => socket.send(m));
});
socket.addEventListener("message", (ev) => {
let mensaje;
try {
mensaje = JSON.parse(ev.data);
} catch (e) {
return;
}
// El formato {tipo, datos} es el mismo que empujaba Python por
// evaluate_javascript, asi que recibe() no cambia.
if (window.jarvis && window.jarvis.recibe) window.jarvis.recibe(mensaje);
});
socket.addEventListener("close", () => { marcaCaido(true); reintenta(); });
socket.addEventListener("error", () => { try { socket.close(); } catch (e) {} });
}
function reintenta() {
// Espera creciente hasta 8 s: reintentar cada 500 ms contra un nucleo
// parado calienta el portatil para nada.
setTimeout(conecta, esperaReconexion);
esperaReconexion = Math.min(esperaReconexion * 2, 8000);
}
// Que se vea que el panel esta hablando solo. Un HUD con datos congelados y
// sin avisar es peor que uno que dice claramente que perdio el hilo.
function marcaCaido(caido) {
document.documentElement.classList.toggle("sin-nucleo", caido);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", conecta);
} else {
conecta();
}
})();