JARVIS/preview_reactor.py
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

87 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""Renderiza el reactor a PNG sin arrancar Newelle.
Extrae el metodo _draw_reactor REAL de call.py (no una copia) y lo ejecuta
con un self falso y niveles de audio simulados. Sirve para iterar el diseño
sin pagar el build de flatpak cada vez.
python3 preview_reactor.py [salida.png]
"""
import ast
import math
import sys
import textwrap
from pathlib import Path
import cairo
CALL_PY = Path(__file__).parent / "newelle" / "src" / "ui" / "widgets" / "call.py"
SIZE = 240
def load_draw_func():
"""Saca _draw_reactor del fuente y lo compila aislado."""
tree = ast.parse(CALL_PY.read_text())
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef) and node.name == "_draw_reactor":
src = textwrap.dedent(ast.get_source_segment(CALL_PY.read_text(), node))
ns = {"math": math}
exec(compile(src, "call.py", "exec"), ns)
return ns["_draw_reactor"]
raise SystemExit("no encuentro _draw_reactor en call.py")
class FakePanel:
"""El minimo que _draw_reactor toca de self."""
def __init__(self, levels, phase, user=False, assistant=False):
self.reactor_levels = levels
self.reactor_phase = phase
self.user_speaking = user
self.assistant_speaking = assistant
def frame(draw, panel, path):
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, SIZE, SIZE)
cr = cairo.Context(surface)
# fondo del modo llamada, para juzgar el contraste real
cr.set_source_rgb(0.04, 0.09, 0.13)
cr.paint()
draw(panel, None, cr, SIZE, SIZE)
surface.write_to_png(str(path))
def main():
draw = load_draw_func()
out = Path(sys.argv[1] if len(sys.argv) > 1 else "reactor.png")
escenas = [
("reposo", FakePanel([0.04] * 12, 0.0)),
("escuchando", FakePanel(
[0.25, 0.55, 0.85, 0.60, 0.35, 0.70, 0.95, 0.45, 0.30, 0.65, 0.50, 0.20],
1.1, user=True)),
("respondiendo", FakePanel(
[0.40, 0.30, 0.55, 0.45, 0.60, 0.35, 0.50, 0.40, 0.55, 0.30, 0.45, 0.35],
2.4, assistant=True)),
]
# tira horizontal con las tres escenas
strip = cairo.ImageSurface(cairo.FORMAT_ARGB32, SIZE * 3, SIZE)
scr = cairo.Context(strip)
scr.set_source_rgb(0.04, 0.09, 0.13)
scr.paint()
for i, (nombre, panel) in enumerate(escenas):
tmp = Path(f"/tmp/_reactor_{nombre}.png")
frame(draw, panel, tmp)
img = cairo.ImageSurface.create_from_png(str(tmp))
scr.set_source_surface(img, i * SIZE, 0)
scr.paint()
tmp.unlink(missing_ok=True)
strip.write_to_png(str(out))
print(f"escrito {out} ({SIZE * 3}x{SIZE}) — reposo | escuchando | respondiendo")
if __name__ == "__main__":
main()