v2.2 - Small improvement Interface change

This commit is contained in:
pporcheret
2026-10-29 11:13:02 +01:00
parent a17c3cb614
commit 7f26f9b015
271 changed files with 6207 additions and 16044 deletions
+55 -9
View File
@@ -6,6 +6,8 @@ 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.alfred_duplex import DuplexMonitor # ALFRED_DUPLEX_BARGE_IN_V1
from services.piper_tts import stop_playback as duplex_stop_piper
from services import admin_console # ALFRED_ADMIN_PAGES_V1
from services.hermes_client import ask_hermes
from services import alfred_timers, alfred_context_images # ALFRED_TIMERS_CONTEXT_V1
@@ -35,6 +37,11 @@ mic_stop_event=threading.Event()
alfred_timer_store = alfred_timers.register(app, socketio, speak_async, audio_settings) # ALFRED_TIMERS_CONTEXT_V1
audio_settings.initialize()
audio_settings.update_settings({"mic_gain": 30})
# ALFRED_DUPLEX_BARGE_IN_V1: interruption audio sans interruption des tâches Hermes
duplex_monitor = DuplexMonitor(muted=audio_settings.mic_muted,
on_interrupt=duplex_stop_piper,
on_status=lambda event: socketio.emit("alfred_duplex_interrupted" if event.get("interrupted") else "alfred_duplex_activity", event))
# Recharge les réglages V4L2 enregistrés, sans interrompre Alfred si la webcam est absente.
camera_settings.restore_saved_settings()
@@ -46,20 +53,25 @@ def emit_state(mode=None, **extra):
def audio_level(level): socketio.emit("audio_level",{"level":max(0,min(float(level),1))})
def conversation(score=1.0):
def conversation(score=1.0, injected_text=None): # ALFRED_DUPLEX_CONVERSATION_V2
if not conversation_lock.acquire(blocking=False):
return
pending_duplex_text = None # ALFRED_DUPLEX_CONVERSATION_V2
try:
emit_state("listening", partial="", transcript="", wake_score=score)
print("[ALFRED] Début écoute STT", flush=True)
r = listen_once(
stop_event=mic_stop_event,
on_partial=lambda p: emit_state("listening", partial=p),
on_level=audio_level
)
if injected_text is not None: # ALFRED_DUPLEX_CONVERSATION_V2
from services.audio import STTResult
r = STTResult(text=injected_text)
else:
r = listen_once(
stop_event=mic_stop_event,
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)
@@ -176,8 +188,23 @@ def conversation(score=1.0):
if speech and not audio_settings.speaker_muted():
emit_state("speaking")
done = threading.Event()
speak_async(speech, on_end=done.set)
done.wait(timeout=120)
duplex_monitor.start(speech) # ALFRED_DUPLEX_BARGE_IN_V1
try:
started = speak_async(speech, on_end=done.set)
if started:
done.wait(timeout=120)
finally:
if not duplex_monitor.interrupted():
duplex_monitor.stop()
# ALFRED_DUPLEX_CONVERSATION_V2: stop-only or keep the rest of the spoken follow-up.
if duplex_monitor.interrupted():
pending_duplex_text = duplex_monitor.wait_for_followup(timeout=30)
duplex_monitor.stop()
else:
# A few wake-free seconds after the assistant finishes speaking.
pending_duplex_text = duplex_monitor.listen_conversation()
if pending_duplex_text:
print('[ALFRED DUPLEX V2] Question enchaînée :', repr(pending_duplex_text), flush=True)
emit_state("idle", partial="")
@@ -190,7 +217,12 @@ def conversation(score=1.0):
finally:
conversation_lock.release()
if not audio_settings.mic_muted():
if pending_duplex_text and not audio_settings.mic_muted(): # ALFRED_DUPLEX_CONVERSATION_V2
# Only the voice is cancelled; the original Hermes job is untouched.
wake.pause()
threading.Thread(target=conversation, kwargs={'injected_text': pending_duplex_text},
daemon=True, name='dona-duplex-followup').start()
elif not audio_settings.mic_muted():
wake.resume()
print("[ALFRED] Wake word réactivé", flush=True)
@@ -205,6 +237,10 @@ def on_wake(score):
wake=WakeWordListener(on_wake=on_wake,on_level=lambda l: audio_level(l if STATE["mode"]!="idle" else l*.18))
admin_console.register_admin_console(app, wake, audio_settings) # ALFRED_ADMIN_PAGES_V1
@app.get('/api/duplex/status')
def duplex_status():
return jsonify(duplex_monitor.status())
@app.get('/api/audio/settings')
def get_audio_settings():
return jsonify(audio_settings.get_settings())
@@ -538,6 +574,16 @@ _alfred_register_logs_v2(app)
from services.alfred_identity_page import register_identity_page as _alfred_identity_register
_alfred_identity_register(app)
# ALFRED_DEVELOPER_V1 : interface /maintenance (administrateur).
from services.alfred_developer_page import register_developer_page as _alfred_developer_register
_alfred_developer_register(app)
# ALFRED_HERMES_NEW_SESSION_V1 : session Hermes neuve via /config admin.
from services.alfred_hermes_session_page import register_hermes_session_page as _register_hermes_new_session
_register_hermes_new_session(app)
if __name__ == "__main__":
if os.getenv("WAKE_ENABLED","true").lower() in ("1","true","yes","on") and not audio_settings.mic_muted(): 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)