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.
100 lines
3.6 KiB
Python
100 lines
3.6 KiB
Python
#!/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())
|