157 lines
5.6 KiB
Python
157 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Input-Helper: Entwicklungstool für den Sprachassistenten.
|
||
Nimmt Text-Eingabe im Terminal entgegen und leitet sie als JSON an llm_module.py weiter.
|
||
|
||
Aufruf:
|
||
python input_helper.py # interaktiver Modus (Endlosschleife)
|
||
python input_helper.py "Deine Frage" # einmaliger Aufruf mit Argument
|
||
python input_helper.py --emotion drunk # mit Emotion
|
||
|
||
Optionen:
|
||
--emotion NAME Emotion für TTS (amused|angry|disgusted|drunk|neutral|sleepy|surprised|whisper)
|
||
--no-play Nur JSON erzeugen, kein Audio abspielen
|
||
--json-only Gibt nur das JSON aus, ruft LLM nicht auf
|
||
--history Aktiviert Gesprächsverlauf (Kontext merken)
|
||
--help Diese Hilfe
|
||
"""
|
||
|
||
import sys
|
||
import json
|
||
import os
|
||
import subprocess
|
||
import argparse
|
||
from pathlib import Path
|
||
|
||
SCRIPT_DIR = Path(__file__).parent
|
||
LLM_INPUT_FILE = SCRIPT_DIR / "data" / "llm_input.json"
|
||
|
||
VALID_EMOTIONS = ["amused", "angry", "disgusted", "drunk", "neutral", "sleepy", "surprised", "whisper"]
|
||
|
||
def parse_args():
|
||
parser = argparse.ArgumentParser(description="Voice Assistant Input Helper", add_help=True)
|
||
parser.add_argument("text", nargs="?", help="Eingabetext (optional, sonst interaktiv)")
|
||
parser.add_argument("--emotion", default="neutral", choices=VALID_EMOTIONS,
|
||
help="Emotion für die TTS-Ausgabe (Standard: neutral)")
|
||
parser.add_argument("--no-play", action="store_true",
|
||
help="Kein Audio abspielen, nur Datei erzeugen")
|
||
parser.add_argument("--json-only", action="store_true",
|
||
help="Nur JSON ausgeben, LLM nicht aufrufen")
|
||
parser.add_argument("--history", action="store_true",
|
||
help="Gesprächsverlauf aktivieren")
|
||
return parser.parse_args()
|
||
|
||
def build_payload(text: str, emotion: str, autoplay: bool, history: list) -> dict:
|
||
"""Erstellt das JSON-Payload für das LLM-Modul."""
|
||
return {
|
||
"request": text,
|
||
"type": "query",
|
||
"emotion": emotion,
|
||
"autoplay": autoplay,
|
||
"history": history,
|
||
}
|
||
|
||
def save_and_run(payload: dict, json_only: bool):
|
||
"""Speichert JSON und ruft LLM-Modul auf."""
|
||
LLM_INPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||
with open(LLM_INPUT_FILE, "w", encoding="utf-8") as f:
|
||
json.dump(payload, f, ensure_ascii=False, indent=2)
|
||
|
||
if json_only:
|
||
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||
return None
|
||
|
||
llm_script = SCRIPT_DIR / "llm_module.py"
|
||
venv_python = Path.home() / ".venv" / "bin" / "python"
|
||
python_bin = str(venv_python) if venv_python.exists() else sys.executable
|
||
|
||
result = subprocess.run(
|
||
[python_bin, str(llm_script), str(LLM_INPUT_FILE)],
|
||
cwd=SCRIPT_DIR,
|
||
)
|
||
return result.returncode
|
||
|
||
def get_last_answer() -> str | None:
|
||
"""Liest die letzte LLM-Antwort aus der TTS-Input-Datei (für History)."""
|
||
tts_file = SCRIPT_DIR / "data" / "tts_input.json"
|
||
if tts_file.exists():
|
||
try:
|
||
with open(tts_file) as f:
|
||
data = json.load(f)
|
||
return data.get("text")
|
||
except Exception:
|
||
pass
|
||
return None
|
||
|
||
def interactive_loop(args):
|
||
"""Interaktiver Modus: Eingabe in Schleife."""
|
||
history = []
|
||
print("=" * 60)
|
||
print(" Thorsten Voice Assistant – Entwicklungs-Helper")
|
||
print("=" * 60)
|
||
print(f" Emotion: {args.emotion} | History: {'an' if args.history else 'aus'}")
|
||
print(" Tippe deine Anfrage oder 'exit' zum Beenden.")
|
||
print(" Befehle: /emotion <name> /history /clear /exit")
|
||
print("=" * 60)
|
||
|
||
emotion = args.emotion
|
||
|
||
while True:
|
||
try:
|
||
user_input = input("\n>> ").strip()
|
||
except (EOFError, KeyboardInterrupt):
|
||
print("\n[Helper] Beendet.")
|
||
break
|
||
|
||
if not user_input:
|
||
continue
|
||
|
||
# Meta-Befehle
|
||
if user_input.lower() in ("exit", "quit", "/exit"):
|
||
print("[Helper] Beendet.")
|
||
break
|
||
elif user_input.startswith("/emotion "):
|
||
new_emotion = user_input.split(" ", 1)[1].strip().lower()
|
||
if new_emotion in VALID_EMOTIONS:
|
||
emotion = new_emotion
|
||
print(f"[Helper] Emotion geändert zu: {emotion}")
|
||
else:
|
||
print(f"[Helper] Ungültige Emotion. Verfügbar: {', '.join(VALID_EMOTIONS)}")
|
||
continue
|
||
elif user_input == "/history":
|
||
print(f"[Helper] Verlauf ({len(history)} Einträge):")
|
||
for i, h in enumerate(history):
|
||
print(f" [{i}] User: {h.get('user', '')} → Assistant: {h.get('assistant', '')[:50]}...")
|
||
continue
|
||
elif user_input == "/clear":
|
||
history.clear()
|
||
print("[Helper] Verlauf gelöscht.")
|
||
continue
|
||
|
||
last_request = user_input
|
||
payload = build_payload(user_input, emotion, not args.no_play, history if args.history else [])
|
||
save_and_run(payload, args.json_only)
|
||
|
||
# History aktualisieren
|
||
if args.history:
|
||
answer = get_last_answer()
|
||
if answer:
|
||
history.append({"user": last_request, "assistant": answer})
|
||
# Max. 10 Einträge behalten
|
||
if len(history) > 10:
|
||
history = history[-10:]
|
||
|
||
def main():
|
||
args = parse_args()
|
||
|
||
if args.text:
|
||
# Einmaliger Aufruf
|
||
payload = build_payload(args.text, args.emotion, not args.no_play, [])
|
||
save_and_run(payload, args.json_only)
|
||
else:
|
||
# Interaktiver Modus
|
||
interactive_loop(args)
|
||
|
||
if __name__ == "__main__":
|
||
main()
|