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
100
config/set_castellano.py
Normal file
100
config/set_castellano.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Pasa JARVIS a castellano: voz, idioma y personalidad.
|
||||
|
||||
flatpak run --command=python3 io.github.qwersyk.Newelle//master \
|
||||
~/COFRE/CODERS/JARVIS/config/set_castellano.py
|
||||
|
||||
La voz es em_alex, la mas grave de las dos masculinas en español que trae
|
||||
Kokoro. Medido con las muestras de comparar_voces_es.py:
|
||||
|
||||
em_alex 129 Hz
|
||||
em_alex grave 117 Hz <- elegida
|
||||
em_santa 140 Hz
|
||||
em_santa grave 132 Hz
|
||||
|
||||
El descenso de tono no va aqui sino en la cadena de audio (tts.py), porque se
|
||||
aplica a la reproduccion y no a la seleccion de voz. Se ajusta con
|
||||
JARVIS_TTS_GRAVEDAD.
|
||||
|
||||
Sustituye la personalidad inglesa en vez de añadir otra: dos personalidades
|
||||
activas se pisan y el modelo acaba mezclando idiomas.
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
|
||||
from gi.repository import Gio
|
||||
|
||||
SCHEMA = "io.github.qwersyk.Newelle"
|
||||
VOZ = "em_alex"
|
||||
CLAVE = "jarvis_persona"
|
||||
|
||||
TEXTO = """Eres JARVIS, un asistente de voz que funciona entero en esta maquina.
|
||||
|
||||
Como hablas:
|
||||
- En castellano, siempre. Registro sobrio y contenido, sin efusividad.
|
||||
- Trata al usuario de usted y llamale SIEMPRE "señor". En la primera
|
||||
frase de cada respuesta, sin excepcion.
|
||||
- Se breve: una o dos frases salvo que te pidan detalle. Lo que dices se
|
||||
escucha en voz alta, y los parrafos largos son insufribles de oir.
|
||||
- No anuncies lo que vas a hacer. Hazlo y di como ha quedado.
|
||||
- Ironia seca de vez en cuando. Nunca servil, nunca efusivo.
|
||||
|
||||
Que dices:
|
||||
- Tienes acceso real a esta maquina. Antes que suponer, comprueba: si te
|
||||
preguntan por el disco, la memoria, los procesos o la grafica, ejecuta el
|
||||
comando y responde con la cifra de verdad.
|
||||
- Di los numeros redondeados y en palabras: "quedan 846 gigabytes", no
|
||||
"846.3 GiB disponibles en /dev/nvme0n1p2".
|
||||
- Si algo va a borrar datos, cambiar la configuracion del sistema o necesita
|
||||
sudo, di lo que va a hacer y espera confirmacion.
|
||||
- Si no lo sabes, dilo en una frase. No especules.
|
||||
- Ejemplos del tono: "Enseguida, señor." / "Quedan 774 gigabytes, señor." /
|
||||
"No he podido, señor: el disco esta desmontado."
|
||||
- Si ves algo raro en la maquina, mencionalo aunque no te pregunten."""
|
||||
|
||||
ENTRADA = {
|
||||
"key": CLAVE,
|
||||
"text": TEXTO,
|
||||
"title": "JARVIS",
|
||||
"description": ("Castellano, trata de señor, registro sobrio, respuestas "
|
||||
"breves para voz, comprueba en vez de suponer"),
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
settings = Gio.Settings.new(SCHEMA)
|
||||
|
||||
# voz
|
||||
try:
|
||||
voces = json.loads(settings.get_string("tts-voice") or "{}")
|
||||
except json.JSONDecodeError:
|
||||
voces = {}
|
||||
voces.setdefault("kokoro", {})["voice"] = VOZ
|
||||
settings.set_string("tts-voice", json.dumps(voces))
|
||||
|
||||
# personalidad, sustituyendo la que hubiera con la misma clave
|
||||
try:
|
||||
prompts = json.loads(settings.get_string("user-custom-prompts") or "[]")
|
||||
except json.JSONDecodeError:
|
||||
prompts = []
|
||||
prompts = [p for p in prompts if p.get("key") != CLAVE]
|
||||
prompts.append(ENTRADA)
|
||||
settings.set_string("user-custom-prompts", json.dumps(prompts))
|
||||
|
||||
Gio.Settings.sync()
|
||||
|
||||
# releer de gsettings, no fiarse de lo que acabamos de escribir
|
||||
voz = json.loads(settings.get_string("tts-voice")).get("kokoro", {}).get("voice")
|
||||
guardados = json.loads(settings.get_string("user-custom-prompts"))
|
||||
entrada = next((p for p in guardados if p.get("key") == CLAVE), None)
|
||||
|
||||
print(f"voz: {voz}")
|
||||
print(f"personalidad: {'castellano' if entrada and 'castellano' in entrada['text'] else '?'}")
|
||||
|
||||
ok = voz == VOZ and entrada is not None and entrada["text"] == TEXTO
|
||||
print("verificado" if ok else "FALLO: no quedo como se pidio")
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue