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.
97 lines
3.3 KiB
JavaScript
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();
|
|
}
|
|
})();
|