Files
arioch-assistant/assistant/cli.py
2026-04-07 22:06:19 +02:00

197 lines
5.9 KiB
Python

import asyncio
import sys
from . import llm, tts, audio, config
HELP_TEXT = """
Commandes disponibles :
exit / quit Quitter l'assistant
reset Effacer l'historique de conversation
voice <id> Changer la voix Voxtral (voice_id)
voice clear Revenir à la voix par défaut
mode texte Passer en mode saisie texte (défaut)
mode vocal Passer en mode entrée microphone
profiles Lister les profils de personnalité disponibles
profile <slug> Charger un profil (ex: profile traveller_scout)
mcp Lister les serveurs MCP connectés
mcp tools Lister tous les outils MCP disponibles
help Afficher ce message
Mode vocal : appuyez sur Entrée (sans rien écrire) pour commencer à parler,
puis Entrée à nouveau pour envoyer.
"""
def _set_voice(parts: list[str]) -> None:
if len(parts) < 2:
print("Usage : voice <id> ou voice clear")
return
if parts[1] == "clear":
config.VOICE_ID = None
print("Voix réinitialisée (défaut).")
else:
config.VOICE_ID = parts[1]
print(f"Voix définie sur : {config.VOICE_ID}")
def _process_message(user_input: str) -> None:
"""Envoie un message au LLM et lit la réponse à voix haute."""
print(f"Arioch > ", end="", flush=True)
try:
reply = llm.chat(user_input)
except Exception as e:
print(f"\n[Erreur LLM] {e}")
return
print(reply)
try:
audio_bytes = tts.text_to_speech(reply)
audio.play_audio(audio_bytes)
except Exception as e:
print(f"[Erreur TTS/Audio] {e}")
def _handle_command(user_input: str) -> bool:
"""Gère les commandes spéciales. Retourne True si c'était une commande."""
from .profile import list_profiles, apply_profile
lower = user_input.lower()
parts = user_input.split()
if lower in ("exit", "quit"):
print("Au revoir !")
sys.exit(0)
elif lower == "reset":
llm.reset_history()
print("Historique effacé.\n")
return True
elif lower == "help":
print(HELP_TEXT)
return True
elif lower.startswith("voice"):
_set_voice(parts)
return True
elif lower in ("mode texte", "mode text"):
return True # signal au caller
elif lower in ("mode vocal", "mode voix", "mode voice"):
return True # signal au caller
elif lower == "profiles":
_list_profiles(list_profiles())
return True
elif lower.startswith("mcp"):
_handle_mcp(parts)
return True
elif lower.startswith("profile ") and len(parts) >= 2:
_load_profile(parts[1], apply_profile)
return True
return False
def _handle_mcp(parts: list[str]) -> None:
from . import mcp_client
manager = mcp_client.get_manager()
servers = manager.summary()
if len(parts) >= 2 and parts[1] == "tools":
tools = manager.get_mistral_tools()
if not tools:
print("Aucun outil MCP disponible.")
return
print(f"\n{len(tools)} outil(s) MCP disponible(s) :")
for t in tools:
fn = t["function"]
desc = fn.get("description", "")
print(f" {fn['name']:<45} {desc[:60]}")
print()
return
if not servers:
print("Aucun serveur MCP connecté. Configurez 'mcp_servers' dans un profil YAML.\n")
return
print("\nServeurs MCP connectés :")
for name, count in servers:
print(f" {name:<30} {count} outil(s)")
total = sum(c for _, c in servers)
print(f"\nTotal : {total} outil(s). Tapez 'mcp tools' pour les lister.\n")
if not profiles:
print("Aucun profil disponible dans profiles/")
return
print("\nProfils disponibles :")
for slug, name, desc in profiles:
print(f" {slug:<25} {name}" + (f"{desc}" if desc else ""))
print("\nUsage : profile <slug>\n")
def _load_profile(slug: str, apply_fn) -> None:
try:
profile = apply_fn(slug)
print(f"✅ Profil chargé : {profile.name}")
if profile.description:
print(f" {profile.description}")
print()
except FileNotFoundError as e:
print(f"[Profil] {e}")
def run() -> None:
print("🎙️ Arioch — Assistant vocal (Mistral Large + Voxtral)")
print("Commandes : 'profiles' pour voir les personnalités, 'mode vocal' pour parler, 'help' pour l'aide.\n")
vocal_mode = False
while True:
try:
if vocal_mode:
prompt = "🎤 [vocal] Entrée pour parler > "
else:
prompt = "Vous > "
user_input = input(prompt).strip()
except (EOFError, KeyboardInterrupt):
print("\nAu revoir !")
sys.exit(0)
if not user_input:
if vocal_mode:
# Entrée vide en mode vocal → lancer la capture micro
try:
from .stt import transcribe_from_mic
user_input = asyncio.run(transcribe_from_mic())
except Exception as e:
print(f"[Erreur STT] {e}")
continue
if not user_input:
print("(rien capturé)")
continue
print(f"Vous (transcrit) : {user_input}")
_process_message(user_input)
continue
lower = user_input.lower()
# Changement de mode
if lower in ("mode vocal", "mode voix", "mode voice"):
vocal_mode = True
print("Mode vocal activé. Appuyez sur Entrée (sans rien écrire) pour parler.\n")
continue
elif lower in ("mode texte", "mode text"):
vocal_mode = False
print("Mode texte activé.\n")
continue
# Autres commandes
if _handle_command(user_input):
continue
# Message normal (texte)
_process_message(user_input)