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.
This commit is contained in:
commit
8e4bc8ad94
125 changed files with 25033 additions and 0 deletions
87
preview_reactor.py
Normal file
87
preview_reactor.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#!/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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue