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.
95 lines
3.4 KiB
Python
95 lines
3.4 KiB
Python
"""Elegir de una lista por voz, y buscar dentro de un fichero (Ctrl+F).
|
|
|
|
La capa que le faltaba a las acciones de fichero. Buscar y soltar el primer
|
|
resultado no sirve cuando hay varios: hace falta enseñar la lista y poder decir
|
|
"abre el tercero". Y para buscar DENTRO de un fichero como con Ctrl+F, hace
|
|
falta recordar cual era.
|
|
|
|
Aqui va la logica pura —interpretar "el tercero", detectar las ordenes de
|
|
seleccion y de busqueda interna—; el estado (la ultima lista, el ultimo
|
|
fichero) y la ejecucion viven en el panel.
|
|
"""
|
|
import re
|
|
|
|
# Como se dice cada posicion. Se aceptan el ordinal, el numero en palabra y la
|
|
# cifra, porque el reconocedor devuelve cualquiera de los tres.
|
|
ORDINALES = {
|
|
1: ["primero", "primera", "uno", "1"],
|
|
2: ["segundo", "segunda", "dos", "2"],
|
|
3: ["tercero", "tercera", "tres", "3"],
|
|
4: ["cuarto", "cuarta", "cuatro", "4"],
|
|
5: ["quinto", "quinta", "cinco", "5"],
|
|
6: ["sexto", "sexta", "seis", "6"],
|
|
7: ["septimo", "septima", "siete", "7"],
|
|
8: ["octavo", "octava", "ocho", "8"],
|
|
9: ["noveno", "novena", "nueve", "9"],
|
|
10: ["decimo", "decima", "diez", "10"],
|
|
}
|
|
_PALABRA_A_N = {p: n for n, ps in ORDINALES.items() for p in ps}
|
|
|
|
# "el ultimo" es comodo y no es un numero
|
|
_ULTIMO = {"ultimo", "ultima", "final"}
|
|
|
|
|
|
def posicion(texto: str):
|
|
"""Devuelve el numero (1..N) que se ha dicho, -1 para 'el ultimo', o None.
|
|
|
|
Se queda con la PRIMERA posicion mencionada, para que "abre el tercero"
|
|
de 3 y no se lie con otros numeros que puedan venir detras.
|
|
"""
|
|
for palabra in texto.split():
|
|
if palabra in _ULTIMO:
|
|
return -1
|
|
if palabra in _PALABRA_A_N:
|
|
return _PALABRA_A_N[palabra]
|
|
return None
|
|
|
|
|
|
# Ordenes que actuan sobre la lista ya mostrada. El verbo dice que hacer con lo
|
|
# elegido; la posicion, cual.
|
|
_ABRIR = re.compile(r"\b(abre|abreme|abrir)\b")
|
|
_LEER = re.compile(r"\b(lee|leeme|leer|muestrame|ensename)\b")
|
|
_SELECCION = re.compile(r"\b(el|la|numero|opcion|resultado)\b")
|
|
|
|
|
|
def orden_de_seleccion(texto: str):
|
|
"""(accion, posicion) si el texto elige de la lista, o None.
|
|
|
|
accion: "abrir" o "leer". posicion: 1..N o -1 (ultimo).
|
|
Ejemplos: "abre el tercero", "lee el numero dos", "el ultimo", "abre el 3".
|
|
"""
|
|
pos = posicion(texto)
|
|
if pos is None:
|
|
return None
|
|
# tiene que sonar a seleccion, no a otra cosa que lleve un numero
|
|
if not (_SELECCION.search(texto) or _ABRIR.search(texto) or _LEER.search(texto)):
|
|
return None
|
|
if _LEER.search(texto):
|
|
return ("leer", pos)
|
|
# por defecto, elegir es abrir
|
|
return ("abrir", pos)
|
|
|
|
|
|
# Ctrl+F: buscar un termino dentro del ultimo fichero abierto.
|
|
_DENTRO = re.compile(
|
|
r"\b(busca|buscame|encuentra)\b.*\b(dentro|en el fichero|en el archivo|en el texto)\b")
|
|
|
|
|
|
def orden_buscar_dentro(texto: str):
|
|
"""El termino a buscar dentro del fichero, o None.
|
|
|
|
"busca dentro configuracion", "busca en el texto la palabra error".
|
|
Se queda con lo que va despues de 'dentro'/'en el ...'.
|
|
"""
|
|
if not _DENTRO.search(texto):
|
|
return None
|
|
# cortar por el separador y quedarse con el resto
|
|
for sep in ("dentro de", "dentro", "en el fichero", "en el archivo", "en el texto",
|
|
"la palabra"):
|
|
if sep in texto:
|
|
resto = texto.split(sep, 1)[1].strip()
|
|
resto = re.sub(r"^(la palabra|el termino|de|la|el|los|las)\s+",
|
|
"", resto).strip()
|
|
if resto:
|
|
return resto
|
|
return None
|