JARVIS/config/demo_en_directo.sh
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

94 lines
3.4 KiB
Bash
Executable file

#!/usr/bin/env bash
# Le da ordenes a JARVIS en directo, para verlo trabajar en la pantalla.
#
# Cada orden se dice EN VOZ ALTA con la voz activa (para oir como suena de
# verdad, no en un banco de pruebas) y luego se ejecuta la accion que le
# corresponde, igual que haria el asistente al oirte.
#
# No se ejecuta nada destructivo ni nada con root: eso pasa por un dialogo que
# aprueba una persona, y aqui no hay persona delante del dialogo.
#
# ./demo_en_directo.sh las que salen por pantalla incluidas
# ./demo_en_directo.sh --mudo sin voz, solo resultados
set -uo pipefail
RAIZ=$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)
MUDO=no; [ "${1:-}" = "--mudo" ] && MUDO=si
CATALOGO=$RAIZ/newelle/data/acciones/acciones.json
TMPWAV=$(mktemp --suffix=.wav)
trap 'rm -f "$TMPWAV"' EXIT
# La orden que se le diria, y la accion del catalogo que le toca.
# Se eligen las que se VEN o las que contestan algo concreto.
ORDENES=(
"cuanto espacio queda|disco_libre"
"cuanta memoria|memoria"
"que temperatura hay|temperatura"
"que procesos consumen mas|procesos_top"
"cual es mi ip|red_ip"
"cuanto lleva encendido|uptime"
"busca el fichero README|buscar_fichero|README.md"
"busca dentro JARVIS|grep_recursivo|JARVIS"
"hazme una captura|captura_pantalla"
"abre una terminal|terminal_abrir"
"que peliculas tengo|videos_listar"
"pon la pelicula screencast|pelicula_abrir|Screencast"
"abre la carpeta de jarvis|abrir_carpeta|$RAIZ"
)
dice() {
[ "$MUDO" = si ] && return 0
"$RAIZ/config/jarvis-voz.sh" "$TMPWAV" "$1" >/dev/null 2>&1 \
|| JARVIS_PIPER_VOZ=es_ES-davefx-medium "$RAIZ/config/jarvis-piper.sh" "$TMPWAV" "$1" >/dev/null 2>&1
[ -s "$TMPWAV" ] && { paplay "$TMPWAV" 2>/dev/null || aplay -q "$TMPWAV" 2>/dev/null; }
}
campo() { # campo <id_accion> <clave>
python3 -c "
import json,sys
d=json.load(open('$CATALOGO',encoding='utf-8'))
for a in d.get('acciones', d if isinstance(d,list) else []):
if a['id']==sys.argv[1]: print(a.get(sys.argv[2],'') or ''); break
" "$1" "$2"
}
for entrada in "${ORDENES[@]}"; do
IFS='|' read -r frase accion argumento <<< "$entrada"
comando=$(campo "$accion" comando)
acuse=$(campo "$accion" acuse)
respuesta=$(campo "$accion" respuesta)
if [ -z "$comando" ]; then
printf '\n ── "%s" → accion %s NO EXISTE en el catalogo\n' "$frase" "$accion"
continue
fi
printf '\n ── tu dices: "%s"\n' "$frase"
printf ' jarvis: %s\n' "${acuse:-...}"
dice "${acuse:-Un momento}"
# sustituir el argumento igual que hace el motor
real=${comando//\{argumento\}/$(printf '%q' "${argumento:-}")}
salida=$(timeout 30 bash -lc "$real" 2>&1 | head -6)
if [ -n "$salida" ]; then
printf ' resultado: %s\n' "$(echo "$salida" | head -1 | cut -c1-100)"
echo "$salida" | tail -n +2 | sed 's/^/ /' | cut -c1-100
else
printf ' resultado: (hecho, sin salida que enseñar)\n'
fi
# decir la respuesta con la plantilla, si la tiene y es corta
if [ -n "$respuesta" ] && [ -n "$salida" ]; then
ultima=$(echo "$salida" | tail -1)
hablado=${respuesta//\{ultima\}/$ultima}
hablado=${hablado//\{salida\}/$ultima}
case "$hablado" in
*"{"*) ;; # plantilla con campos que no sabemos
*) [ ${#hablado} -lt 160 ] && { printf ' jarvis: %s\n' "$hablado"; dice "$hablado"; };;
esac
fi
sleep 1
done
printf '\n ── fin de la demostracion\n'