v3
This commit is contained in:
+199
-5
@@ -1,11 +1,27 @@
|
||||
|
||||
import os, threading, hmac
|
||||
from datetime import datetime, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
from flask import Flask, render_template, request, jsonify, Response
|
||||
from flask_socketio import SocketIO
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Charger la configuration AVANT les imports des services
|
||||
load_dotenv("/opt/alfred/.env")
|
||||
load_dotenv(
|
||||
"/opt/alfred/app/config/control_center.env",
|
||||
override=True
|
||||
)
|
||||
|
||||
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
|
||||
@@ -97,6 +113,58 @@ def conversation(score=1.0, injected_text=None): # ALFRED_DUPLEX_CONVERSATION_V
|
||||
content={"type": "text", "text": r.text}
|
||||
)
|
||||
|
||||
# DONA_SUITE_V9: intents locaux explicites, sinon continuer vers Hermes.
|
||||
if _dona_suite_v9.is_request(r.text):
|
||||
_v9_speech=_dona_suite_v9.handle_and_emit(r.text,socketio)
|
||||
if _v9_speech and not audio_settings.speaker_muted():
|
||||
emit_state('speaking')
|
||||
_v9_done=threading.Event()
|
||||
_v9_ok=speak_async(_v9_speech,on_end=_v9_done.set)
|
||||
if _v9_ok:_v9_done.wait(timeout=120)
|
||||
emit_state('idle',partial='')
|
||||
return
|
||||
|
||||
# DONA_NAV_V83: cartes et itinéraires à la demande, sans LLM.
|
||||
if _dona_nav.is_request(r.text):
|
||||
_nav_reply=_dona_nav.handle_and_emit(r.text,socketio,STATE,STATE_LOCK)
|
||||
if _nav_reply and not audio_settings.speaker_muted():
|
||||
emit_state('speaking')
|
||||
_nav_done=threading.Event()
|
||||
_nav_ok=speak_async(_nav_reply,on_end=lambda: (_nav_done.set(),socketio.emit('alfred_tts_finished',{'source':'voice'})))
|
||||
if _nav_ok:_nav_done.wait(timeout=120)
|
||||
emit_state('idle',partial='')
|
||||
return
|
||||
|
||||
# DONA_YOUTUBE_V82 : recherche publique et contrôle du lecteur à la voix.
|
||||
if _dona_youtube.is_search(r.text):
|
||||
_speech=_dona_youtube.search_and_emit(r.text, socketio, STATE, STATE_LOCK)
|
||||
if _speech and not audio_settings.speaker_muted():
|
||||
emit_state('speaking')
|
||||
_evt=threading.Event()
|
||||
_ok=speak_async(_speech,on_end=_evt.set)
|
||||
if _ok: _evt.wait(timeout=120)
|
||||
emit_state('idle',partial='')
|
||||
return
|
||||
if _dona_youtube.is_control(r.text,_dona_youtube.player_active()):
|
||||
_dona_youtube.control_and_emit(r.text,socketio)
|
||||
emit_state('idle',partial='')
|
||||
return
|
||||
|
||||
# DONA_V8_DYNAMIC — creation visuelle explicite, source Hermes/MCP ou API directe.
|
||||
if _dona_dynamic.is_dynamic_request(r.text):
|
||||
_board = _dona_dynamic.build_dashboard(r.text, _dona_dashboard_agent.ask_dashboard)
|
||||
_payload = {'type':'dona_dynamic', 'dashboard':_board}
|
||||
with STATE_LOCK:
|
||||
STATE['content'] = _payload
|
||||
socketio.emit('alfred_render', _payload)
|
||||
if not audio_settings.speaker_muted():
|
||||
emit_state('speaking')
|
||||
_finished = threading.Event()
|
||||
_started = speak_async('Voici ton tableau de bord : ' + _board['title'], on_end=_finished.set)
|
||||
if _started: _finished.wait(timeout=120)
|
||||
emit_state('idle', partial='')
|
||||
return
|
||||
|
||||
# Vue dynamique : commandes déterministes exécutées sans faire
|
||||
# dépendre l'affichage des capacités ou sessions Hermes.
|
||||
visual = None
|
||||
@@ -114,7 +182,7 @@ def conversation(score=1.0, injected_text=None): # ALFRED_DUPLEX_CONVERSATION_V
|
||||
if speech and not audio_settings.speaker_muted():
|
||||
emit_state("speaking")
|
||||
done = threading.Event()
|
||||
speak_async(speech, on_end=done.set)
|
||||
speak_async(speech, on_end=lambda: (done.set(), socketio.emit('alfred_tts_finished', {'source':'voice'})))
|
||||
done.wait(timeout=120)
|
||||
emit_state("idle", partial="")
|
||||
return
|
||||
@@ -135,7 +203,7 @@ def conversation(score=1.0, injected_text=None): # ALFRED_DUPLEX_CONVERSATION_V
|
||||
if _lists_reply and not audio_settings.speaker_muted():
|
||||
emit_state('speaking')
|
||||
_lists_done = threading.Event()
|
||||
speak_async(_lists_reply, on_end=_lists_done.set)
|
||||
speak_async(_lists_reply, on_end=lambda: (_lists_done.set(), socketio.emit('alfred_tts_finished', {'source':'voice'})))
|
||||
_lists_done.wait(timeout=120)
|
||||
emit_state('idle', partial='')
|
||||
return
|
||||
@@ -156,7 +224,8 @@ def conversation(score=1.0, injected_text=None): # ALFRED_DUPLEX_CONVERSATION_V
|
||||
except ValueError as exc:
|
||||
emit_state('error', content={'type': 'text', 'text': str(exc)})
|
||||
return
|
||||
answer = ask_hermes(r.text)
|
||||
# DONA_V7_RELEASE: reconnaissance sur ordre vocal explicite, sinon Hermes normal.
|
||||
answer = _dona_face_recognize(ask_hermes) if _dona_face_request(r.text) else 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 "")
|
||||
@@ -190,7 +259,7 @@ def conversation(score=1.0, injected_text=None): # ALFRED_DUPLEX_CONVERSATION_V
|
||||
done = threading.Event()
|
||||
duplex_monitor.start(speech) # ALFRED_DUPLEX_BARGE_IN_V1
|
||||
try:
|
||||
started = speak_async(speech, on_end=done.set)
|
||||
started = speak_async(speech, on_end=lambda: (done.set(), socketio.emit('alfred_tts_finished', {'source':'voice'})))
|
||||
if started:
|
||||
done.wait(timeout=120)
|
||||
finally:
|
||||
@@ -532,7 +601,7 @@ def _alfred_dashboard_quick_command(prompt):
|
||||
if _speech and not audio_settings.speaker_muted():
|
||||
emit_state('speaking')
|
||||
_done = threading.Event()
|
||||
speak_async(_speech, on_end=_done.set)
|
||||
speak_async(_speech, on_end=lambda: (_done.set(), socketio.emit('alfred_tts_finished', {'source':'voice'})))
|
||||
_done.wait(timeout=120)
|
||||
emit_state('idle', partial='')
|
||||
except Exception as _exc:
|
||||
@@ -584,6 +653,131 @@ _alfred_developer_register(app)
|
||||
from services.alfred_hermes_session_page import register_hermes_session_page as _register_hermes_new_session
|
||||
_register_hermes_new_session(app)
|
||||
|
||||
|
||||
# DONA_FAMILY_UI_V1_START
|
||||
# Text entered in the adult UI uses the existing Hermes session, not microphone/TTS.
|
||||
def _dona_submit_typed(prompt):
|
||||
if not conversation_lock.acquire(blocking=False):
|
||||
return False
|
||||
def _dona_run_typed():
|
||||
try:
|
||||
wake.pause()
|
||||
with STATE_LOCK:
|
||||
_before_image = IMAGE_REVISION
|
||||
emit_state('thinking', partial='', transcript=prompt,
|
||||
content={'type': 'text', 'text': prompt})
|
||||
# DONA_SUITE_V9: commandes quotidiennes écrites, sans perturber Hermes.
|
||||
if _dona_suite_v9.is_request(prompt):
|
||||
_dona_suite_v9.handle_and_emit(prompt,socketio)
|
||||
emit_state('idle',partial='')
|
||||
return
|
||||
|
||||
# DONA_NAV_V83: commande écrite navigation, sans polluer la session Hermes.
|
||||
if _dona_nav.is_request(prompt):
|
||||
_dona_nav.handle_and_emit(prompt,socketio,STATE,STATE_LOCK)
|
||||
emit_state('idle',partial='')
|
||||
return
|
||||
|
||||
# DONA_YOUTUBE_V82 : vidéos et commandes écrites hors conversation Hermes.
|
||||
if _dona_youtube.is_search(prompt):
|
||||
_dona_youtube.search_and_emit(prompt,socketio,STATE,STATE_LOCK)
|
||||
emit_state('idle',partial='')
|
||||
return
|
||||
if _dona_youtube.is_control(prompt,_dona_youtube.player_active()):
|
||||
_dona_youtube.control_and_emit(prompt,socketio)
|
||||
emit_state('idle',partial='')
|
||||
return
|
||||
|
||||
# DONA_V8_DYNAMIC — commandes visuelles saisies via le formulaire habituel.
|
||||
if _dona_dynamic.is_dynamic_request(prompt):
|
||||
_board = _dona_dynamic.build_dashboard(prompt, _dona_dashboard_agent.ask_dashboard)
|
||||
_payload = {'type':'dona_dynamic','dashboard':_board}
|
||||
with STATE_LOCK:
|
||||
STATE['content'] = _payload
|
||||
socketio.emit('alfred_render',_payload)
|
||||
emit_state('idle', partial='')
|
||||
return
|
||||
_answer = ask_hermes(prompt)
|
||||
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}
|
||||
with STATE_LOCK:
|
||||
if IMAGE_REVISION == _before_image:
|
||||
STATE['content'] = _display
|
||||
socketio.emit('alfred_render', _display)
|
||||
# Deliberately no speak_async(): typed interaction is silent.
|
||||
emit_state('idle', partial='')
|
||||
except Exception as exc:
|
||||
print('[DONA TEXT] Echec de la demande:', type(exc).__name__, str(exc), flush=True)
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
emit_state('error', content={'type':'text', 'text':'Hermes n’a pas pu répondre.'})
|
||||
finally:
|
||||
conversation_lock.release()
|
||||
if not audio_settings.mic_muted():
|
||||
wake.resume()
|
||||
threading.Thread(target=_dona_run_typed, daemon=True, name='dona-typed-hermes').start()
|
||||
return True
|
||||
|
||||
from services.dona_family_ui import register_dona_family as _dona_register_family
|
||||
_dona_register_family(app, adult_command=_dona_submit_typed)
|
||||
# DONA_FAMILY_UI_V1_END
|
||||
|
||||
# DONA_V8_DYNAMIC — tableau de bord choisi, généré et rendu en composants contrôlés.
|
||||
from services import dona_dynamic as _dona_dynamic
|
||||
# DONA_V8_1_ISOLATION : tableaux isolés des conversations normales
|
||||
from services import dona_dashboard_agent as _dona_dashboard_agent
|
||||
|
||||
def _dona_dynamic_submit(prompt):
|
||||
if not conversation_lock.acquire(blocking=False):
|
||||
return False
|
||||
def _work():
|
||||
try:
|
||||
wake.pause()
|
||||
emit_state('thinking',transcript=prompt,partial='')
|
||||
_board=_dona_dynamic.build_dashboard(prompt,_dona_dashboard_agent.ask_dashboard)
|
||||
_payload={'type':'dona_dynamic','dashboard':_board}
|
||||
with STATE_LOCK:
|
||||
STATE['content']=_payload
|
||||
socketio.emit('alfred_render',_payload)
|
||||
emit_state('idle',partial='')
|
||||
except Exception as exc:
|
||||
print('[DONA DYNAMIC V8] ',type(exc).__name__,str(exc),flush=True)
|
||||
emit_state('error',content={'type':'text','text':'Impossible de préparer ce tableau de bord.'})
|
||||
finally:
|
||||
conversation_lock.release()
|
||||
if not audio_settings.mic_muted():
|
||||
wake.resume()
|
||||
threading.Thread(target=_work,daemon=True,name='dona-dynamic-v8').start()
|
||||
return True
|
||||
|
||||
_dona_dynamic.register_dynamic(app,_dona_dynamic_submit)
|
||||
|
||||
# DONA_SUITE_V9 : routeur local, cockpit, mémoire, cuisine, documents, routines.
|
||||
from services import dona_suite_v9 as _dona_suite_v9
|
||||
_dona_suite_v9.register_suite(app,socketio)
|
||||
|
||||
# DONA_NAV_V83: géocodage, carte et transfert Waze.
|
||||
from services import dona_navigation as _dona_nav
|
||||
_dona_nav.register_navigation(app,socketio)
|
||||
|
||||
# DONA_YOUTUBE_V82 : lecture publique, sans clé API, moteur Hermes isolé.
|
||||
from services import dona_youtube as _dona_youtube
|
||||
_dona_youtube.register_youtube(app,socketio)
|
||||
|
||||
# DONA_SPOTIFY_V6 — player read/control API, reuses Hermes OAuth (no LLM polling).
|
||||
from services.dona_spotify import register_spotify_panel as _register_dona_spotify
|
||||
_register_dona_spotify(app)
|
||||
|
||||
# DONA_V7_RELEASE : route manuelle caméra + MCP existant; aucune base de visage modifiée.
|
||||
from services.dona_face import (register_face as _dona_register_face,
|
||||
recognize_once as _dona_face_recognize, is_face_request as _dona_face_request)
|
||||
_dona_register_face(app, socketio, ask_hermes, conversation_lock)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user