import os, threading from flask import Flask, render_template, request, jsonify from flask_socketio import SocketIO from dotenv import load_dotenv from services.audio import listen_once, speak_async, tts_backend_detect from services.wakeword import WakeWordListener from services.hermes_client import ask_hermes load_dotenv() ## from services.audio import get_vosk_model print("[ALFRED] Préchargement des modèles vocaux...", flush=True) get_vosk_model(os.environ["WAKE_VOSK_MODEL_PATH"]) get_vosk_model(os.environ["VOSK_MODEL_PATH"]) print("[ALFRED] Modèles vocaux prêts.", flush=True) ## app=Flask(__name__); socketio=SocketIO(app,cors_allowed_origins="*") STATE={"mode":"idle","title":"Alfred","content":{"type":"text","text":"À votre écoute."},"partial":""} conversation_lock=threading.Lock() def emit_state(mode=None, **extra): if mode: STATE["mode"]=mode STATE.update(extra); socketio.emit("alfred_state",STATE) def audio_level(level): socketio.emit("audio_level",{"level":max(0,min(float(level),1))}) def conversation(score=1.0): if not conversation_lock.acquire(blocking=False): return try: emit_state("listening", partial="", wake_score=score) print("[ALFRED] Début écoute STT", flush=True) r = listen_once( on_partial=lambda p: emit_state("listening", partial=p), on_level=audio_level ) print(f"[ALFRED] Résultat STT : {r.text!r}, erreur : {r.error!r}", flush=True) if r.error: emit_state( "error", content={"type": "text", "text": r.error} ) return if not r.text: emit_state("idle", partial="") return emit_state( "thinking", partial="", transcript=r.text, content={"type": "text", "text": r.text} ) print("[ALFRED] Envoi à Hermes", flush=True) answer = ask_hermes(r.text) print(f"[ALFRED] Réponse Hermes : {answer!r}", flush=True) if isinstance(answer, dict): speech = str(answer.get("speech") or answer.get("text") or "") display = answer.get("display") or { "type": "text", "text": speech } else: speech = str(answer or "") display = {"type": "text", "text": speech} STATE["content"] = display socketio.emit("alfred_render", display) if speech: emit_state("speaking") done = threading.Event() speak_async(speech, on_end=done.set) done.wait(timeout=120) emit_state("idle", partial="") except Exception as exc: print(f"[ALFRED] Erreur conversation : {exc}", flush=True) emit_state( "error", content={"type": "text", "text": str(exc)} ) finally: conversation_lock.release() wake.resume() print("[ALFRED] Wake word réactivé", flush=True) def on_wake(score): socketio.emit("wake_detected",{"word":os.getenv("WAKE_WORD","alfred"),"score":score}) threading.Thread(target=conversation,args=(score,),daemon=True).start() wake=WakeWordListener(on_wake=on_wake,on_level=lambda l: audio_level(l if STATE["mode"]!="idle" else l*.18)) @app.get("/") def index(): return render_template("index.html",name=os.getenv("ALFRED_NAME","Alfred")) @app.get("/api/state") def state(): return jsonify({**STATE,"wake":wake.status(),"tts":tts_backend_detect()}) @app.post("/api/state") def set_state(): STATE.update(request.get_json(force=True)); socketio.emit("alfred_state",STATE); return jsonify({"ok":True}) @app.post("/api/render") def render_payload(): data=request.get_json(force=True); STATE["content"]=data; socketio.emit("alfred_render",data); return jsonify({"ok":True}) @app.post("/api/audio-level") def api_audio_level(): audio_level((request.get_json(force=True) or {}).get("level",0)); return jsonify({"ok":True}) @app.get("/api/wake/status") def wake_status(): return jsonify(wake.status()) @app.post("/api/wake/start") def wake_start(): wake.start(); return jsonify({"ok":True,**wake.status()}) @app.post("/api/wake/stop") def wake_stop(): wake.stop(); return jsonify({"ok":True,**wake.status()}) @app.post("/api/tts/say") def tts_say(): text=(request.get_json(silent=True) or {}).get("text",""); return jsonify({"ok":speak_async(text)}) if __name__ == "__main__": if os.getenv("WAKE_ENABLED","true").lower() in ("1","true","yes","on"): wake.start() socketio.run(app,host=os.getenv("FLASK_HOST","0.0.0.0"),port=int(os.getenv("FLASK_PORT","54321")),allow_unsafe_werkzeug=True)