From cd37ac3c0aaa1506bae8be28ecc7e5fef82bebed Mon Sep 17 00:00:00 2001 From: pporcheret Date: Mon, 26 Oct 2026 00:06:24 +0100 Subject: [PATCH] V1 --- alfred/.env | 15 + alfred/README_INSTALLATION.md | 107 +-- alfred/README_MIC_MUTE.md | 42 ++ .../__pycache__/app.cpython-313.pyc | Bin 0 -> 12049 bytes alfred/app.backup.calendar/app.py | 226 ++++++ alfred/app.backup.calendar/app.py.ori | 180 +++++ .../backup_audio_20261016_133326/app.py | 130 ++++ .../backup_audio_20261016_133326/audio.py | 379 ++++++++++ .../alfred-controls.css | 19 + .../alfred-controls.js | 73 ++ .../audio_settings.py | 62 ++ .../index.html | 28 + .../config/hermes_session.json | 3 + .../hermes_session.json.bak.20261024_151328 | 3 + .../config/hermes_session.json.lock | 0 alfred/app.backup.calendar/config/identity.md | 31 + .../config/identity.md.bak.20261016_140326 | 26 + .../config/identity.md.ori2 | 74 ++ .../__pycache__/audio.cpython-313.pyc | Bin 0 -> 14892 bytes .../audio_settings.cpython-313.pyc | Bin 0 -> 4935 bytes .../__pycache__/camera.cpython-313.pyc | Bin 0 -> 4111 bytes .../__pycache__/hermes_client.cpython-313.pyc | Bin 0 -> 12060 bytes .../__pycache__/piper_tts.cpython-313.pyc | Bin 0 -> 4857 bytes .../__pycache__/wakeword.cpython-313.pyc | Bin 0 -> 11590 bytes alfred/app.backup.calendar/services/audio.py | 392 ++++++++++ .../services/audio_settings.py | 77 ++ alfred/app.backup.calendar/services/camera.py | 71 ++ .../services/hermes_client.py | 474 ++++++++++++ .../hermes_client.py.bak.20261016_140326 | 41 ++ .../services/hermes_client.py.ko.1 | 7 + .../services/hermes_client.py.okv1 | 47 ++ .../services/hermes_client.py.old | 35 + .../app.backup.calendar/services/piper_tts.py | 109 +++ .../services/piper_tts.py.old | 105 +++ .../app.backup.calendar/services/wakeword.py | 110 +++ .../static/css/alfred-controls.css | 14 + .../static/css/alfred-mode-icons.css | 13 + .../app.backup.calendar/static/css/alfred.css | 62 ++ .../static/js/alfred-camera.js | 41 ++ .../static/js/alfred-controls.js | 93 +++ .../static/js/alfred-mode-icons.js | 35 + .../app.backup.calendar/static/js/alfred.js | 121 +++ .../templates/configuration.html | 130 ++++ .../app.backup.calendar/templates/index.html | 103 +++ .../templates/index.html.1 | 31 + .../templates/index.html.ori | 2 + alfred/app.backup.calendar/test.py | 19 + .../__pycache__/app.cpython-313.pyc | Bin 0 -> 12049 bytes alfred/app.backup.camera/app.py | 366 ++++++++++ alfred/app.backup.camera/app.py.ori | 180 +++++ .../backup_audio_20261016_133326/app.py | 130 ++++ .../backup_audio_20261016_133326/audio.py | 379 ++++++++++ .../alfred-controls.css | 19 + .../alfred-controls.js | 73 ++ .../audio_settings.py | 62 ++ .../index.html | 28 + .../config/hermes_session.json | 3 + .../hermes_session.json.bak.20261024_151328 | 3 + .../config/hermes_session.json.lock | 0 alfred/app.backup.camera/config/identity.md | 31 + .../config/identity.md.bak.20261016_140326 | 26 + .../app.backup.camera/config/identity.md.ori2 | 74 ++ .../alfred_intents.cpython-313.pyc | Bin 0 -> 16335 bytes .../alfred_views_controller.cpython-313.pyc | Bin 0 -> 11289 bytes .../__pycache__/audio.cpython-313.pyc | Bin 0 -> 14892 bytes .../audio_settings.cpython-313.pyc | Bin 0 -> 4935 bytes .../__pycache__/camera.cpython-313.pyc | Bin 0 -> 4111 bytes .../__pycache__/ha_views.cpython-313.pyc | Bin 0 -> 19838 bytes .../__pycache__/hermes_client.cpython-313.pyc | Bin 0 -> 12060 bytes .../__pycache__/piper_tts.cpython-313.pyc | Bin 0 -> 4857 bytes .../__pycache__/wakeword.cpython-313.pyc | Bin 0 -> 11590 bytes .../services/alfred_intents.py | 189 +++++ .../services/alfred_views_controller.py | 127 ++++ alfred/app.backup.camera/services/audio.py | 392 ++++++++++ .../services/audio_settings.py | 77 ++ alfred/app.backup.camera/services/camera.py | 71 ++ alfred/app.backup.camera/services/ha_views.py | 236 ++++++ .../services/hermes_client.py | 474 ++++++++++++ .../hermes_client.py.bak.20261016_140326 | 41 ++ .../services/hermes_client.py.ko.1 | 7 + .../services/hermes_client.py.okv1 | 47 ++ .../services/hermes_client.py.old | 35 + .../app.backup.camera/services/piper_tts.py | 109 +++ .../services/piper_tts.py.old | 105 +++ alfred/app.backup.camera/services/wakeword.py | 110 +++ .../static/css/alfred-controls.css | 19 + .../static/css/alfred-mode-icons.css | 13 + .../static/css/alfred-views.css | 54 ++ .../app.backup.camera/static/css/alfred.css | 62 ++ .../static/js/alfred-camera.js | 41 ++ .../static/js/alfred-controls.js | 93 +++ .../static/js/alfred-mode-icons.js | 35 + .../static/js/alfred-views.js | 235 ++++++ alfred/app.backup.camera/static/js/alfred.js | 126 ++++ .../templates/configuration.html | 130 ++++ alfred/app.backup.camera/templates/index.html | 116 +++ .../app.backup.camera/templates/index.html.1 | 31 + .../templates/index.html.ori | 2 + alfred/app.backup.camera/test.py | 19 + .../__pycache__/app.cpython-313.pyc | Bin 0 -> 30364 bytes alfred/app.backup.weather_outfits/app.py | 412 +++++++++++ alfred/app.backup.weather_outfits/app.py.ori | 180 +++++ .../backup_audio_20261016_133326/app.py | 130 ++++ .../backup_audio_20261016_133326/audio.py | 379 ++++++++++ .../alfred-controls.css | 19 + .../alfred-controls.js | 73 ++ .../audio_settings.py | 62 ++ .../index.html | 28 + .../config/camera_controls.json | 16 + .../config/hermes_session.json | 3 + .../hermes_session.json.bak.20261024_151328 | 3 + .../config/hermes_session.json.lock | 0 .../config/identity.md | 31 + .../config/identity.md.bak.20261016_140326 | 26 + .../config/identity.md.ori2 | 74 ++ .../alfred_intents.cpython-313.pyc | Bin 0 -> 16335 bytes .../alfred_views_controller.cpython-313.pyc | Bin 0 -> 11574 bytes .../__pycache__/audio.cpython-313.pyc | Bin 0 -> 14892 bytes .../audio_settings.cpython-313.pyc | Bin 0 -> 4935 bytes .../__pycache__/camera.cpython-313.pyc | Bin 0 -> 4111 bytes .../camera_settings.cpython-313.pyc | Bin 0 -> 11994 bytes .../__pycache__/ha_views.cpython-313.pyc | Bin 0 -> 19838 bytes .../__pycache__/ha_weather.cpython-313.pyc | Bin 0 -> 17009 bytes .../__pycache__/hermes_client.cpython-313.pyc | Bin 0 -> 12060 bytes .../__pycache__/piper_tts.cpython-313.pyc | Bin 0 -> 4857 bytes .../__pycache__/wakeword.cpython-313.pyc | Bin 0 -> 11590 bytes .../services/alfred_intents.py | 189 +++++ .../services/alfred_views_controller.py | 132 ++++ .../services/audio.py | 392 ++++++++++ .../services/audio_settings.py | 77 ++ .../services/camera.py | 71 ++ .../services/camera_settings.py | 187 +++++ .../services/ha_views.py | 236 ++++++ .../services/ha_weather.py | 227 ++++++ .../services/hermes_client.py | 474 ++++++++++++ .../hermes_client.py.bak.20261016_140326 | 41 ++ .../services/hermes_client.py.ko.1 | 7 + .../services/hermes_client.py.okv1 | 47 ++ .../services/hermes_client.py.old | 35 + .../services/piper_tts.py | 109 +++ .../services/piper_tts.py.old | 105 +++ .../services/wakeword.py | 110 +++ .../static/css/alfred-controls.css | 41 ++ .../static/css/alfred-mode-icons.css | 13 + .../static/css/alfred-views.css | 76 ++ .../static/css/alfred.css | 62 ++ .../static/js/alfred-camera-settings.js | 175 +++++ .../static/js/alfred-camera.js | 41 ++ .../static/js/alfred-controls.js | 93 +++ .../static/js/alfred-mode-icons.js | 35 + .../static/js/alfred-views.js | 296 ++++++++ .../static/js/alfred.js | 126 ++++ .../templates/configuration.html | 130 ++++ .../templates/index.html | 133 ++++ .../templates/index.html.1 | 31 + .../templates/index.html.ori | 2 + alfred/app.backup.weather_outfits/test.py | 19 + alfred/app/__pycache__/app.cpython-313.pyc | Bin 12049 -> 32058 bytes alfred/app/app.py | 196 +++++ alfred/app/cache/weather_outfit_cache.json | 195 +++++ ...7d1a58073380245ab906809991f1cdad65d0d2.jpg | Bin 0 -> 77376 bytes ...7d2d57cc2d01e7247b458ffeef179851d13611.jpg | Bin 0 -> 25984 bytes ...938b8d8a9862d6055c66876b5e45af838e7e8a.jpg | Bin 0 -> 69812 bytes ...b7d5666f38125afbb0812d7e72fac08ce24ad9.jpg | Bin 0 -> 37215 bytes ...2f229120608bb1f20ed4f610ab128cd4e5eea3.jpg | Bin 0 -> 29011 bytes ...79b96de623fbffb33153aeaf847ad9b339631a.jpg | Bin 0 -> 78582 bytes ...6c09bb6b068eba6f2e78e1e126c42be66aced3.jpg | Bin 0 -> 25485 bytes ...0536fc61cb226a4f357c09c21db8192ca134ff.jpg | Bin 0 -> 24960 bytes alfred/app/config/camera_controls.json | 16 + alfred/app/config/weather_outfits.env | 22 + .../alfred_intents.cpython-313.pyc | Bin 0 -> 16335 bytes .../alfred_views_controller.cpython-313.pyc | Bin 0 -> 11578 bytes .../camera_settings.cpython-313.pyc | Bin 0 -> 11994 bytes .../__pycache__/ha_views.cpython-313.pyc | Bin 0 -> 19838 bytes .../__pycache__/ha_weather.cpython-313.pyc | Bin 0 -> 17932 bytes .../weather_outfits.cpython-313.pyc | Bin 0 -> 42498 bytes alfred/app/services/alfred_intents.py | 189 +++++ .../app/services/alfred_views_controller.py | 132 ++++ alfred/app/services/camera_settings.py | 187 +++++ alfred/app/services/ha_views.py | 236 ++++++ alfred/app/services/ha_weather.py | 230 ++++++ alfred/app/services/weather_outfits.py | 688 ++++++++++++++++++ .../weather_outfits.py.bak.20261025_234759 | 419 +++++++++++ .../static/assets/weather/adult-reference.png | Bin 0 -> 222870 bytes .../static/assets/weather/child-reference.png | Bin 0 -> 222599 bytes alfred/app/static/css/alfred-controls.css | 27 + alfred/app/static/css/alfred-views.css | 102 +++ .../app/static/js/alfred-camera-settings.js | 175 +++++ alfred/app/static/js/alfred-controls.js | 6 +- alfred/app/static/js/alfred-views.js | 368 ++++++++++ alfred/app/static/js/alfred.js | 5 + alfred/app/templates/index.html | 48 +- .../app.py | 422 +++++++++++ .../services/alfred_views_controller.py | 132 ++++ .../services/weather_outfits.py | 277 +++++++ .../static/css/alfred-views.css | 91 +++ .../static/js/alfred-views.js | 353 +++++++++ .../services/weather_outfits.py | 355 +++++++++ .../services/weather_outfits.py | 534 ++++++++++++++ alfred/requirements_calendar.txt | 2 + alfred/tests/test_calendar.py | 88 +++ hermes/MCP_proxmox/.env | 11 + hermes/MCP_proxmox/proxmox_mcp.py | 353 +++++++++ hermes/MCP_proxmox/test_api.py | 16 + hermes/MCP_wazuh/.env | 6 + hermes/MCP_wazuh/requirements.txt | 3 + hermes/MCP_wazuh/vikunja_mcp.py.bak | 327 +++++++++ 207 files changed, 19205 insertions(+), 54 deletions(-) create mode 100644 alfred/README_MIC_MUTE.md create mode 100644 alfred/app.backup.calendar/__pycache__/app.cpython-313.pyc create mode 100644 alfred/app.backup.calendar/app.py create mode 100644 alfred/app.backup.calendar/app.py.ori create mode 100644 alfred/app.backup.calendar/backup_audio_20261016_133326/app.py create mode 100644 alfred/app.backup.calendar/backup_audio_20261016_133326/audio.py create mode 100644 alfred/app.backup.calendar/backup_micro_gain_20261016_134053/alfred-controls.css create mode 100644 alfred/app.backup.calendar/backup_micro_gain_20261016_134053/alfred-controls.js create mode 100644 alfred/app.backup.calendar/backup_micro_gain_20261016_134053/audio_settings.py create mode 100644 alfred/app.backup.calendar/backup_micro_gain_20261016_134053/index.html create mode 100644 alfred/app.backup.calendar/config/hermes_session.json create mode 100644 alfred/app.backup.calendar/config/hermes_session.json.bak.20261024_151328 create mode 100644 alfred/app.backup.calendar/config/hermes_session.json.lock create mode 100644 alfred/app.backup.calendar/config/identity.md create mode 100644 alfred/app.backup.calendar/config/identity.md.bak.20261016_140326 create mode 100644 alfred/app.backup.calendar/config/identity.md.ori2 create mode 100644 alfred/app.backup.calendar/services/__pycache__/audio.cpython-313.pyc create mode 100644 alfred/app.backup.calendar/services/__pycache__/audio_settings.cpython-313.pyc create mode 100644 alfred/app.backup.calendar/services/__pycache__/camera.cpython-313.pyc create mode 100644 alfred/app.backup.calendar/services/__pycache__/hermes_client.cpython-313.pyc create mode 100644 alfred/app.backup.calendar/services/__pycache__/piper_tts.cpython-313.pyc create mode 100644 alfred/app.backup.calendar/services/__pycache__/wakeword.cpython-313.pyc create mode 100644 alfred/app.backup.calendar/services/audio.py create mode 100644 alfred/app.backup.calendar/services/audio_settings.py create mode 100644 alfred/app.backup.calendar/services/camera.py create mode 100644 alfred/app.backup.calendar/services/hermes_client.py create mode 100644 alfred/app.backup.calendar/services/hermes_client.py.bak.20261016_140326 create mode 100644 alfred/app.backup.calendar/services/hermes_client.py.ko.1 create mode 100644 alfred/app.backup.calendar/services/hermes_client.py.okv1 create mode 100644 alfred/app.backup.calendar/services/hermes_client.py.old create mode 100644 alfred/app.backup.calendar/services/piper_tts.py create mode 100644 alfred/app.backup.calendar/services/piper_tts.py.old create mode 100644 alfred/app.backup.calendar/services/wakeword.py create mode 100644 alfred/app.backup.calendar/static/css/alfred-controls.css create mode 100644 alfred/app.backup.calendar/static/css/alfred-mode-icons.css create mode 100644 alfred/app.backup.calendar/static/css/alfred.css create mode 100644 alfred/app.backup.calendar/static/js/alfred-camera.js create mode 100644 alfred/app.backup.calendar/static/js/alfred-controls.js create mode 100644 alfred/app.backup.calendar/static/js/alfred-mode-icons.js create mode 100644 alfred/app.backup.calendar/static/js/alfred.js create mode 100644 alfred/app.backup.calendar/templates/configuration.html create mode 100644 alfred/app.backup.calendar/templates/index.html create mode 100644 alfred/app.backup.calendar/templates/index.html.1 create mode 100644 alfred/app.backup.calendar/templates/index.html.ori create mode 100644 alfred/app.backup.calendar/test.py create mode 100644 alfred/app.backup.camera/__pycache__/app.cpython-313.pyc create mode 100644 alfred/app.backup.camera/app.py create mode 100644 alfred/app.backup.camera/app.py.ori create mode 100644 alfred/app.backup.camera/backup_audio_20261016_133326/app.py create mode 100644 alfred/app.backup.camera/backup_audio_20261016_133326/audio.py create mode 100644 alfred/app.backup.camera/backup_micro_gain_20261016_134053/alfred-controls.css create mode 100644 alfred/app.backup.camera/backup_micro_gain_20261016_134053/alfred-controls.js create mode 100644 alfred/app.backup.camera/backup_micro_gain_20261016_134053/audio_settings.py create mode 100644 alfred/app.backup.camera/backup_micro_gain_20261016_134053/index.html create mode 100644 alfred/app.backup.camera/config/hermes_session.json create mode 100644 alfred/app.backup.camera/config/hermes_session.json.bak.20261024_151328 create mode 100644 alfred/app.backup.camera/config/hermes_session.json.lock create mode 100644 alfred/app.backup.camera/config/identity.md create mode 100644 alfred/app.backup.camera/config/identity.md.bak.20261016_140326 create mode 100644 alfred/app.backup.camera/config/identity.md.ori2 create mode 100644 alfred/app.backup.camera/services/__pycache__/alfred_intents.cpython-313.pyc create mode 100644 alfred/app.backup.camera/services/__pycache__/alfred_views_controller.cpython-313.pyc create mode 100644 alfred/app.backup.camera/services/__pycache__/audio.cpython-313.pyc create mode 100644 alfred/app.backup.camera/services/__pycache__/audio_settings.cpython-313.pyc create mode 100644 alfred/app.backup.camera/services/__pycache__/camera.cpython-313.pyc create mode 100644 alfred/app.backup.camera/services/__pycache__/ha_views.cpython-313.pyc create mode 100644 alfred/app.backup.camera/services/__pycache__/hermes_client.cpython-313.pyc create mode 100644 alfred/app.backup.camera/services/__pycache__/piper_tts.cpython-313.pyc create mode 100644 alfred/app.backup.camera/services/__pycache__/wakeword.cpython-313.pyc create mode 100644 alfred/app.backup.camera/services/alfred_intents.py create mode 100644 alfred/app.backup.camera/services/alfred_views_controller.py create mode 100644 alfred/app.backup.camera/services/audio.py create mode 100644 alfred/app.backup.camera/services/audio_settings.py create mode 100644 alfred/app.backup.camera/services/camera.py create mode 100644 alfred/app.backup.camera/services/ha_views.py create mode 100644 alfred/app.backup.camera/services/hermes_client.py create mode 100644 alfred/app.backup.camera/services/hermes_client.py.bak.20261016_140326 create mode 100644 alfred/app.backup.camera/services/hermes_client.py.ko.1 create mode 100644 alfred/app.backup.camera/services/hermes_client.py.okv1 create mode 100644 alfred/app.backup.camera/services/hermes_client.py.old create mode 100644 alfred/app.backup.camera/services/piper_tts.py create mode 100644 alfred/app.backup.camera/services/piper_tts.py.old create mode 100644 alfred/app.backup.camera/services/wakeword.py create mode 100644 alfred/app.backup.camera/static/css/alfred-controls.css create mode 100644 alfred/app.backup.camera/static/css/alfred-mode-icons.css create mode 100644 alfred/app.backup.camera/static/css/alfred-views.css create mode 100644 alfred/app.backup.camera/static/css/alfred.css create mode 100644 alfred/app.backup.camera/static/js/alfred-camera.js create mode 100644 alfred/app.backup.camera/static/js/alfred-controls.js create mode 100644 alfred/app.backup.camera/static/js/alfred-mode-icons.js create mode 100644 alfred/app.backup.camera/static/js/alfred-views.js create mode 100644 alfred/app.backup.camera/static/js/alfred.js create mode 100644 alfred/app.backup.camera/templates/configuration.html create mode 100644 alfred/app.backup.camera/templates/index.html create mode 100644 alfred/app.backup.camera/templates/index.html.1 create mode 100644 alfred/app.backup.camera/templates/index.html.ori create mode 100644 alfred/app.backup.camera/test.py create mode 100644 alfred/app.backup.weather_outfits/__pycache__/app.cpython-313.pyc create mode 100644 alfred/app.backup.weather_outfits/app.py create mode 100644 alfred/app.backup.weather_outfits/app.py.ori create mode 100644 alfred/app.backup.weather_outfits/backup_audio_20261016_133326/app.py create mode 100644 alfred/app.backup.weather_outfits/backup_audio_20261016_133326/audio.py create mode 100644 alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/alfred-controls.css create mode 100644 alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/alfred-controls.js create mode 100644 alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/audio_settings.py create mode 100644 alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/index.html create mode 100644 alfred/app.backup.weather_outfits/config/camera_controls.json create mode 100644 alfred/app.backup.weather_outfits/config/hermes_session.json create mode 100644 alfred/app.backup.weather_outfits/config/hermes_session.json.bak.20261024_151328 create mode 100644 alfred/app.backup.weather_outfits/config/hermes_session.json.lock create mode 100644 alfred/app.backup.weather_outfits/config/identity.md create mode 100644 alfred/app.backup.weather_outfits/config/identity.md.bak.20261016_140326 create mode 100644 alfred/app.backup.weather_outfits/config/identity.md.ori2 create mode 100644 alfred/app.backup.weather_outfits/services/__pycache__/alfred_intents.cpython-313.pyc create mode 100644 alfred/app.backup.weather_outfits/services/__pycache__/alfred_views_controller.cpython-313.pyc create mode 100644 alfred/app.backup.weather_outfits/services/__pycache__/audio.cpython-313.pyc create mode 100644 alfred/app.backup.weather_outfits/services/__pycache__/audio_settings.cpython-313.pyc create mode 100644 alfred/app.backup.weather_outfits/services/__pycache__/camera.cpython-313.pyc create mode 100644 alfred/app.backup.weather_outfits/services/__pycache__/camera_settings.cpython-313.pyc create mode 100644 alfred/app.backup.weather_outfits/services/__pycache__/ha_views.cpython-313.pyc create mode 100644 alfred/app.backup.weather_outfits/services/__pycache__/ha_weather.cpython-313.pyc create mode 100644 alfred/app.backup.weather_outfits/services/__pycache__/hermes_client.cpython-313.pyc create mode 100644 alfred/app.backup.weather_outfits/services/__pycache__/piper_tts.cpython-313.pyc create mode 100644 alfred/app.backup.weather_outfits/services/__pycache__/wakeword.cpython-313.pyc create mode 100644 alfred/app.backup.weather_outfits/services/alfred_intents.py create mode 100644 alfred/app.backup.weather_outfits/services/alfred_views_controller.py create mode 100644 alfred/app.backup.weather_outfits/services/audio.py create mode 100644 alfred/app.backup.weather_outfits/services/audio_settings.py create mode 100644 alfred/app.backup.weather_outfits/services/camera.py create mode 100644 alfred/app.backup.weather_outfits/services/camera_settings.py create mode 100644 alfred/app.backup.weather_outfits/services/ha_views.py create mode 100644 alfred/app.backup.weather_outfits/services/ha_weather.py create mode 100644 alfred/app.backup.weather_outfits/services/hermes_client.py create mode 100644 alfred/app.backup.weather_outfits/services/hermes_client.py.bak.20261016_140326 create mode 100644 alfred/app.backup.weather_outfits/services/hermes_client.py.ko.1 create mode 100644 alfred/app.backup.weather_outfits/services/hermes_client.py.okv1 create mode 100644 alfred/app.backup.weather_outfits/services/hermes_client.py.old create mode 100644 alfred/app.backup.weather_outfits/services/piper_tts.py create mode 100644 alfred/app.backup.weather_outfits/services/piper_tts.py.old create mode 100644 alfred/app.backup.weather_outfits/services/wakeword.py create mode 100644 alfred/app.backup.weather_outfits/static/css/alfred-controls.css create mode 100644 alfred/app.backup.weather_outfits/static/css/alfred-mode-icons.css create mode 100644 alfred/app.backup.weather_outfits/static/css/alfred-views.css create mode 100644 alfred/app.backup.weather_outfits/static/css/alfred.css create mode 100644 alfred/app.backup.weather_outfits/static/js/alfred-camera-settings.js create mode 100644 alfred/app.backup.weather_outfits/static/js/alfred-camera.js create mode 100644 alfred/app.backup.weather_outfits/static/js/alfred-controls.js create mode 100644 alfred/app.backup.weather_outfits/static/js/alfred-mode-icons.js create mode 100644 alfred/app.backup.weather_outfits/static/js/alfred-views.js create mode 100644 alfred/app.backup.weather_outfits/static/js/alfred.js create mode 100644 alfred/app.backup.weather_outfits/templates/configuration.html create mode 100644 alfred/app.backup.weather_outfits/templates/index.html create mode 100644 alfred/app.backup.weather_outfits/templates/index.html.1 create mode 100644 alfred/app.backup.weather_outfits/templates/index.html.ori create mode 100644 alfred/app.backup.weather_outfits/test.py create mode 100644 alfred/app/cache/weather_outfit_cache.json create mode 100644 alfred/app/cache/weather_outfit_images/0426920d0a11983e7ac1459cd87d1a58073380245ab906809991f1cdad65d0d2.jpg create mode 100644 alfred/app/cache/weather_outfit_images/1caff5c005736b32c2c322165e7d2d57cc2d01e7247b458ffeef179851d13611.jpg create mode 100644 alfred/app/cache/weather_outfit_images/3fc1f1e2c91374ed46944ef3b3938b8d8a9862d6055c66876b5e45af838e7e8a.jpg create mode 100644 alfred/app/cache/weather_outfit_images/498692dd8981e4e5fa457d9fbab7d5666f38125afbb0812d7e72fac08ce24ad9.jpg create mode 100644 alfred/app/cache/weather_outfit_images/6f4194e5dbd0d9c49cf5f520d02f229120608bb1f20ed4f610ab128cd4e5eea3.jpg create mode 100644 alfred/app/cache/weather_outfit_images/93379bdabb939699e8565f358c79b96de623fbffb33153aeaf847ad9b339631a.jpg create mode 100644 alfred/app/cache/weather_outfit_images/a4928b9950baed6aad15a8e69c6c09bb6b068eba6f2e78e1e126c42be66aced3.jpg create mode 100644 alfred/app/cache/weather_outfit_images/ebb79b8e11936267b3477c5d930536fc61cb226a4f357c09c21db8192ca134ff.jpg create mode 100644 alfred/app/config/camera_controls.json create mode 100644 alfred/app/config/weather_outfits.env create mode 100644 alfred/app/services/__pycache__/alfred_intents.cpython-313.pyc create mode 100644 alfred/app/services/__pycache__/alfred_views_controller.cpython-313.pyc create mode 100644 alfred/app/services/__pycache__/camera_settings.cpython-313.pyc create mode 100644 alfred/app/services/__pycache__/ha_views.cpython-313.pyc create mode 100644 alfred/app/services/__pycache__/ha_weather.cpython-313.pyc create mode 100644 alfred/app/services/__pycache__/weather_outfits.cpython-313.pyc create mode 100644 alfred/app/services/alfred_intents.py create mode 100644 alfred/app/services/alfred_views_controller.py create mode 100644 alfred/app/services/camera_settings.py create mode 100644 alfred/app/services/ha_views.py create mode 100644 alfred/app/services/ha_weather.py create mode 100644 alfred/app/services/weather_outfits.py create mode 100644 alfred/app/services/weather_outfits.py.bak.20261025_234759 create mode 100644 alfred/app/static/assets/weather/adult-reference.png create mode 100644 alfred/app/static/assets/weather/child-reference.png create mode 100644 alfred/app/static/css/alfred-views.css create mode 100644 alfred/app/static/js/alfred-camera-settings.js create mode 100644 alfred/app/static/js/alfred-views.js create mode 100644 alfred/backup_meteo_tenues_v2_20261025_232200/app.py create mode 100644 alfred/backup_meteo_tenues_v2_20261025_232200/services/alfred_views_controller.py create mode 100644 alfred/backup_meteo_tenues_v2_20261025_232200/services/weather_outfits.py create mode 100644 alfred/backup_meteo_tenues_v2_20261025_232200/static/css/alfred-views.css create mode 100644 alfred/backup_meteo_tenues_v2_20261025_232200/static/js/alfred-views.js create mode 100644 alfred/backup_meteo_tenues_v3_20261025_233732/services/weather_outfits.py create mode 100644 alfred/backup_meteo_tenues_v5_20261026_000215/services/weather_outfits.py create mode 100644 alfred/requirements_calendar.txt create mode 100644 alfred/tests/test_calendar.py create mode 100644 hermes/MCP_proxmox/.env create mode 100755 hermes/MCP_proxmox/proxmox_mcp.py create mode 100644 hermes/MCP_proxmox/test_api.py create mode 100755 hermes/MCP_wazuh/.env create mode 100644 hermes/MCP_wazuh/requirements.txt create mode 100644 hermes/MCP_wazuh/vikunja_mcp.py.bak diff --git a/alfred/.env b/alfred/.env index a85d090..8bad3f0 100644 --- a/alfred/.env +++ b/alfred/.env @@ -10,3 +10,18 @@ AUDIO_OUTPUT_DEVICE=plughw:1,0 ## fr_FR-upmc-medium ## PIPER_VOICE=fr_FR-mls-medium PIPER_VOICE=fr_FR-upmc-medium + +#Calendrier HA + +ALFRED_TIMEZONE=Europe/Paris +HA_DEFAULT_CALENDAR=calendar.alfred +ALFRED_ALLOW_CALENDAR_WRITES=true +HA_URL=http://192.168.1.141:8123 +HA_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI4MTU3YzFjYjA3NWY0YmNkOGRkYTEzYzIyOWVkMDFkOSIsImlhdCI6MTc4MTUyNjg5MSwiZXhwIjoyMDk2ODg2ODkxfQ.i4IfhNwgfPMmRnIhLdapq10sTxBKnNJmy4kFCDX4Ylw + +ALFRED_WEATHER_ILLUSTRATIONS=true +ALFRED_WEATHER_ILLUSTRATION_TTL_HOURS=12 +COMFYUI_URL=http://192.168.1.13:8188 +WORKFLOWS_DIR=/opt/hermes/MCP_comfyui/workflows +COMFY_PROMPT_NODE=6 +COMFYUI_TIMEOUT=300 diff --git a/alfred/README_INSTALLATION.md b/alfred/README_INSTALLATION.md index 0acd0df..52d6d65 100644 --- a/alfred/README_INSTALLATION.md +++ b/alfred/README_INSTALLATION.md @@ -1,59 +1,82 @@ -# Alfred — Interface mise à jour depuis interface.zip (24/10/2026) +# Alfred — Vues dynamiques & Calendrier Home Assistant (V1) -## Modifications +Base : votre **alfredapp.zip** du 25 octobre 2026. Aucun avatar n’est ajouté. Les fonctions Vosk, Hermes, Wyoming Piper, caméra locale, contrôles audio et ComfyUI sont conservées. -- Coin supérieur gauche : état réel de l'agent (`idle`, `listening`, `thinking`, `speaking`, `error`) avec libellé en français. Le titre « Alfred » est supprimé de la page. -- Coin supérieur droit : bouton « réglages audio » ouvrant un panneau flottant (volume sortie, sensibilité micro ALSA, mute micro), et bouton « couper/réactiver le son » (mute haut-parleur). -- Coin inférieur droit : transcription Vosk partielle puis finale, conservée pendant le retour à la veille. Les données arrivent des événements Socket.IO `alfred_state` existants (`partial` et `transcript`). -- Coin inférieur gauche : prévisualisation webcam MJPEG en direct. La caméra s'ouvre **uniquement sur clic** pour ne pas occuper en permanence la caméra utilisée par le MCP facial, et s'arrête lorsque l'onglet devient invisible. -- Protection supplémentaire : le texte d'Hermes est rendu sans injection de code HTML dans la page. +## Installation : patch (conseillé) -## Installation - -À effectuer sur la machine Alfred après transfert du ZIP, en étant dans son dossier parent `/opt/alfred` : +1. Sauvegarder **/opt/alfred/app**. Le ZIP de mise à jour ne contient que les fichiers ajoutés/modifiés, sous `app/`. +2. Décompresser `Alfred_Calendrier_HA_V1_PATCH.zip` sous **/opt/alfred/** (pas sous `/opt/alfred/app`, sinon vous obtiendrez `app/app`). +3. Activer votre environnement Python habituel et installer les dépendances si nécessaire : ```bash sudo systemctl stop alfred -sudo cp -a /opt/alfred/app /opt/alfred/app.sauvegarde-$(date +%Y%m%d-%H%M%S) -cd /opt/alfred -sudo unzip -o /tmp/Alfred_Interface_Reorganisation_V1.zip -python3 -m py_compile /opt/alfred/app/app.py /opt/alfred/app/services/camera.py -sudo systemctl start alfred -sudo journalctl -u alfred -n 50 --no-pager +sudo cp -a /opt/alfred/app /opt/alfred/app.sauvegarde.$(date +%Y%m%d_%H%M%S) +sudo unzip -o /tmp/Alfred_Calendrier_HA_V1_PATCH.zip -d /opt/alfred +cd /opt/alfred/app +# Utiliser le pip du venv exécutant app.py : +/path/vers/venv/bin/python -m pip install requests websocket-client +/path/vers/venv/bin/python -m py_compile app.py services/ha_views.py services/alfred_intents.py services/alfred_views_controller.py ``` -Le ZIP remplace uniquement : +Le script de démarrage systemd existant n’est **pas modifié**. -``` -app/app.py -app/templates/index.html -app/static/css/alfred.css -app/static/css/alfred-controls.css -app/static/js/alfred.js -app/static/js/alfred-controls.js -app/static/js/alfred-camera.js (nouveau) -app/services/camera.py +## Variables d’environnement à fournir à `alfred.service` + +```ini +HA_URL=http://192.168.1.XXX:8123 +HA_TOKEN=JETON_LONGUE_DUREE_HOME_ASSISTANT +ALFRED_TIMEZONE=Europe/Paris +# Limiter aux calendriers choisis (laisser absent pour lire tous les calendriers autorisés au jeton) : +# HA_CALENDARS=calendar.famille,calendar.alfred +# Permet de cibler un calendrier pour les créations vocales : +# HA_DEFAULT_CALENDAR=calendar.alfred +# Pour restreindre les caméras visibles (ex. uniquement les caméras extérieures) : +# HA_CAMERAS=camera.entree,camera.jardin +# L'écriture est OFF par sécurité ; l'activer après validation de la lecture : +ALFRED_ALLOW_CALENDAR_WRITES=true ``` -## Webcam +Mettre ces valeurs **dans l’environnement du service** (par exemple un fichier `EnvironmentFile=` de systemd ou fichier `.env` effectivement chargé par `/opt/alfred/app/app.py`). **Ne pas mettre le jeton dans le navigateur ni dans le ZIP.** Si l’application utilise `load_dotenv()`, un fichier `.env` dans son répertoire de travail peut convenir, mais vérifiez `systemctl cat alfred` avant de choisir son emplacement. -- Index de la caméra par défaut : 0, réglable avec `CAMERA_INDEX=1` (ou autre) dans l'environnement du service Alfred. -- Options facultatives : `CAMERA_WIDTH=640`, `CAMERA_HEIGHT=480`, `CAMERA_FPS=8`, `CAMERA_JPEG_QUALITY=75`. -- `cv2` / OpenCV doit être installé dans **l'environnement Python d'Alfred**, par exemple `python -m pip install opencv-python-headless` depuis son venv (uniquement si absent). -- Tester le périphérique : `ls -l /dev/video*`. -- URL du flux : `http://IP_ALFRED:54321/api/camera/stream`. -- Si le MCP de reconnaissance faciale utilise déjà la webcam en exclusivité, le flux peut échouer. Désactiver la prévisualisation avant une capture MCP si nécessaire. Cette version ne partage pas les images entre les processus. -- Attention : le flux est actuellement accessible à toute personne ayant accès au serveur Flask (aucune authentification intégrée à l'application de départ). Ne pas exposer le port 54321 sur Internet sans contrôle d'accès. +Après modification des paramètres systemd : `sudo systemctl daemon-reload`. Puis `sudo systemctl start alfred` et `sudo journalctl -u alfred -f`. -## Audio +L’utilisateur associé à HA_TOKEN doit avoir des droits lecture sur les calendriers voulus et des droits écriture sur ceux à modifier. Les calendriers Google ou externes en lecture seule ne permettront pas l’écriture. Certains calendriers n’acceptent pas la suppression. -Le bouton « Mute » en haut à droite agit sur **le haut-parleur**. Le mute du **microphone** se trouve dans le popup audio. Tous les réglages utilisent l'API POST `/api/audio/settings` existante. +## Tests initiaux -## Tests réalisés +1. Ouvrir `http://IP_ALFRED:54321/`. Appuyer sur l'icône **Agenda**. +2. Tester `http://IP_ALFRED:54321/api/calendar/calendars` puis `http://IP_ALFRED:54321/api/calendar/events` depuis une session réseau de confiance. +3. Dire « Alfred, montre-moi mon planning sur sept jours » puis « Et demain ? ». +4. Faire un événement de test via le bouton **+ Rendez-vous** dans un calendrier HA disposant de l'option création. +5. Depuis la grille, ouvrir cet événement, demander sa suppression, puis confirmer la boîte de dialogue ; vérifier l'événement dans HA. -- Compilation syntaxique de `app.py` et `camera.py`. -- Vérification syntaxique des trois scripts JavaScript au moyen de `node --check`. -- Validation des identifiants des contrôles de l'interface. -- Test du générateur MJPEG, du verrou d'accès à la caméra et de sa libération sur fermeture, avec une caméra simulée. +**Commandes prises en charge** : « mon agenda aujourd'hui », « demain », « ce matin », « cet après-midi », « ce soir », « dans la journée », « le 8 novembre », « sur 3 jours », « sur 7 jours », « cette semaine », « la semaine prochaine », « ce mois », « ai-je des choses prévues demain ? », « ajoute un rendez-vous dentiste demain à 15 h dans le calendrier Famille », « supprime mon rendez-vous dentiste demain », « confirme » / « annule », « montre les températures », « affiche les caméras », « reviens à l'accueil ». -Non testés : affichage en conditions réelles sous Chromium, accès à la caméra physique du MINISFORUM, intégration réelle à Vosk/ALSA. L'environnement de test empêche la navigation Chromium locale. +### Écriture vocale et sécurité + +- Pour une création entièrement spécifiée (date, heure, calendrier, titre), Alfred propose l'événement et **attend « confirme »** avant d'enregistrer. Si des éléments manquent, il affiche un formulaire prérempli ; il ne devine pas le calendrier ou l'heure. +- Pour une suppression, Alfred recherche l'événement par nom/période, exige un identifiant HA `uid` et une **confirmation explicite**. Si plusieurs événements correspondent, l'écran permet de choisir. Une confirmation vocale expire après 90 secondes ; elle n'est pas sauvegardée après redémarrage. Pour une série récurrente sans `recurrence_id`, la suppression peut affecter toute la série ; la boîte de dialogue l'indique. +- `ALFRED_ALLOW_CALENDAR_WRITES` désactive la création/suppression par défaut ; l'activer seulement sur un réseau de confiance et un utilisateur Home Assistant aux droits restreints. **Cette application ne fournit pas d'authentification utilisateur complète** : ne pas exposer Flask à Internet ni à un réseau non fiable. Les événements contiennent des données personnelles. +- La création utilise `POST /api/services/calendar/create_event`. La suppression utilise la commande HA WebSocket `calendar/event/delete`, **pas** un service REST inexistant. + +### Vue capteurs et caméras + +- Les températures sont les entités HA `sensor` ayant `device_class: temperature`, plus les températures courantes des entités `climate`. Le clic ouvre un graphique des dernières 24 h (si l'API d'historique HA fournit des points). +- La vue Caméras Home Assistant affiche des **instantanés** sécurisés derrière Flask, rafraîchis toutes les 15 s et seulement quand la vue est ouverte. Ce n'est pas un flux vidéo WebRTC/RTSP. La petite prévisualisation de la webcam locale existante reste inchangée. +- Le bouton Accueil ou la commande vocale rétablit la page d'accueil. + +### Remarques sur le contexte Hermes + +Ce patch **ne modifie pas `hermes_client.py`, `identity.md`, les sessions Hermes ou le MCP Home Assistant**. Les commandes visuelles explicites sont détectées côté Flask ; les autres demandes continuent vers Hermes et ses MCP, pour éviter de réintroduire la limitation du contexte qui avait empêché la génération ComfyUI. + +### Limites connues + +- L'interprétation vocale des dates suit un jeu de règles françaises (aujourd'hui/demain/jour de la semaine/jour et mois). Une formulation très libre peut nécessiter le formulaire tactile ou Hermes ; le routeur ne doit pas improviser une écriture. +- La couleur des événements suit la liste des calendriers ; elle n'est pas encore configurable. +- Certains calendriers de HA ne transmettent pas `uid` ou sont en lecture seule : leurs événements restent consultables, mais ne peuvent pas être supprimés dans Alfred. +- Les appels HA ne sont pas testables depuis ce fichier d'installation sans accès à votre serveur/token ; les validations automatiques portent sur la syntaxe, le routage vocal et des réponses HA simulées. +- Les requêtes de lecture et de mutation sont faites directement de Flask vers Home Assistant, **sans dupliquer les calendriers**. C'est un adaptateur de source, réutilisable pour d'autres fournisseurs plus tard. + +## Restauration + +Arrêter Alfred puis restaurer le dossier sauvegardé. Ne pas copier `identity.md` d'anciennes archives si vous l'aviez volontairement désactivé. diff --git a/alfred/README_MIC_MUTE.md b/alfred/README_MIC_MUTE.md new file mode 100644 index 0000000..2730299 --- /dev/null +++ b/alfred/README_MIC_MUTE.md @@ -0,0 +1,42 @@ +# Alfred — Micro muet dans la barre supérieure + +## Modifications + +- `app/templates/index.html` : déplace `#micMute` depuis la fenêtre audio vers la barre supérieure, à gauche du mute haut-parleur. Bouton avec deux icônes SVG (micro activé / micro coupé). +- `app/static/js/alfred-controls.js` : conserve l'API existante `POST /api/audio/settings` et actualise `aria-pressed` ainsi que le libellé du bouton en fonction de `mic_muted`. +- `app/static/css/alfred-controls.css` : affiche l'icône appropriée selon l'état du micro (état coupé rouge via la règle globale existante). + +La fenêtre des réglages conserve les curseurs de volume et de sensibilité du microphone. Le bouton de mute du haut-parleur reste inchangé. Aucune modification Python, Vosk, Hermes, Home Assistant ou calendrier. + +## Installation + +**Ce patch s'applique sur Alfred Calendrier V1**, et contient uniquement les trois fichiers ci-dessus. + +1. Copier `Alfred_Micro_Mute_Barre_V1.zip` dans `/tmp/` sur le mini-PC. +2. Sauvegarder les trois fichiers, puis extraire le ZIP depuis `/opt/alfred` (et non `/opt/alfred/app`). + +```bash +sudo systemctl stop alfred +sudo cp -a /opt/alfred/app/templates/index.html /opt/alfred/app/templates/index.html.bak +sudo cp -a /opt/alfred/app/static/js/alfred-controls.js /opt/alfred/app/static/js/alfred-controls.js.bak +sudo cp -a /opt/alfred/app/static/css/alfred-controls.css /opt/alfred/app/static/css/alfred-controls.css.bak +sudo unzip -o /tmp/Alfred_Micro_Mute_Barre_V1.zip -d /opt/alfred +sudo systemctl start alfred +``` + +Rechargez Chromium avec `Ctrl+F5` pour éviter l'ancien JavaScript en cache. + +## Vérification + +- Bouton micro visible en haut à droite, **à gauche du haut-parleur**. +- Appui : l'icône devient rouge et le micro est coupé ; nouvel appui : il est réactivé. +- Le bouton de réglages ouvre uniquement les curseurs de volume et de sensibilité. +- Le bouton haut-parleur reste indépendant. + +Le mute du micro continue à agir sur l'écoute d'Alfred via le backend existant. Lorsque le micro est coupé, les commandes vocales ne peuvent pas le réactiver : il faut utiliser le bouton tactile. + +## Tests effectués + +Syntaxe JavaScript vérifiée (`node --check`). Test Chromium automatisé : états micro activé/coupé/réactivé, lecture/écriture de `mic_muted` sur une simulation de l'API, présence des autres commandes, ouverture du popup. + +Ces tests ne remplacent pas une vérification réelle sur le mini-PC (ALSA, systemd et Vosk). diff --git a/alfred/app.backup.calendar/__pycache__/app.cpython-313.pyc b/alfred/app.backup.calendar/__pycache__/app.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09d2b8b735370c3ad499b9f4f1f66ea5d451124b GIT binary patch literal 12049 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 str: + configured = os.getenv("AUDIO_BACKEND", "auto").lower() + if configured in ("pactl", "alsa"): + return configured + if shutil.which("pactl"): + info = _cmd_ok(["pactl", "info"]) + if info and "Server Name" in info.stdout: + return "pactl" + if shutil.which("amixer"): + return "alsa" + return "none" + + +def _which(cmd): + return shutil.which(cmd) + + +def _is_piper_tts(binary: Optional[str]) -> bool: + if not binary: + return False + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + if "gtk" in out or "ratbag" in out: + return False + return (("--model" in out or " -m " in out) and + ("--output_file" in out or " -o " in out)) + except Exception: + return False + + +def _find_piper_binary() -> Optional[str]: + candidates = [ + os.getenv("PIPER_BIN"), + "/usr/local/bin/piper-tts", "/usr/bin/piper-tts", _which("piper-tts"), + "/usr/local/bin/piper", "/usr/bin/piper", _which("piper"), + ] + for candidate in candidates: + if candidate and _is_piper_tts(candidate): + return candidate + return None + + +def _piper_supports_long_flag(binary: str) -> bool: + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + return "--model" in out and "--output_file" in out + except Exception: + return True + +### +def tts_backend_detect() -> str: + """Retourne le moteur TTS configuré pour Alfred.""" + + if os.getenv("PIPER_HOST"): + return "wyoming-piper" + + if _which("espeak-ng"): + return "espeak-ng" + + return "none" + + +''' +def tts_backend_detect() -> str: + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + if piper and model and os.path.exists(model): + return "piper" + if _which("espeak"): + return "espeak" + if _which("spd-say"): + return "spd-say" + return "none" +''' +#### + +def speak_text(text: str): + """Synthèse vocale via Wyoming Piper, avec secours eSpeak.""" + if not text or not text.strip(): + return + + text = text[:int(os.getenv("TTS_MAX_CHARS", "2000"))] + + try: + from services.piper_tts import speak as piper_speak + + print("[ALFRED TTS] Utilisation de Wyoming Piper", flush=True) + piper_speak(text) + return + + except Exception as exc: + print(f"[ALFRED TTS] Piper indisponible : {exc}", flush=True) + + # Secours eSpeak + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + print("[ALFRED TTS] Secours eSpeak", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + except Exception as exc: + print(f"[ALFRED TTS] Erreur eSpeak : {exc}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + +''' +def speak_text(text: str): + if not text or not text.strip(): + return + + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + start = time.monotonic() + print("[ALFRED TTS] Génération audio...", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + print("[ALFRED TTS] Lecture audio...", flush=True) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + print( + f"[ALFRED TTS] Terminé en {time.monotonic() - start:.2f}s", + flush=True + ) + + except Exception as e: + print(f"[ALFRED TTS] Erreur : {e}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + + + + +def speak_text(text: str): + """Synthétise et joue localement le texte. Retourne (ok, backend, erreur).""" + text = (text or "").strip() + if not text: + return False, "none", "texte vide" + text = text[: int(os.getenv("TTS_MAX_CHARS", "2000"))] + + backend = audio_backend_detect() + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + + if piper and model and os.path.exists(model): + wav_path = None + try: + with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp: + wav_path = tmp.name + cmd = [piper] + if _piper_supports_long_flag(piper): + cmd += ["--model", model, "--output_file", wav_path] + else: + cmd += ["-m", model, "-o", wav_path] + p = subprocess.run(cmd, input=text, text=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False) + if p.returncode != 0: + return False, "piper", (p.stderr or p.stdout or "")[:2000] + if not os.path.exists(wav_path) or os.path.getsize(wav_path) == 0: + return False, "piper", "Piper n'a pas produit de WAV" + if backend == "pactl" and shutil.which("paplay"): + play = subprocess.run(["paplay", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + elif shutil.which("aplay"): + play = subprocess.run(["aplay", "-q", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + else: + return False, "piper", "paplay/aplay introuvable" + return play.returncode == 0, "piper", None if play.returncode == 0 else "lecture WAV échouée" + except Exception as exc: + return False, "piper", str(exc) + finally: + if wav_path: + try: os.unlink(wav_path) + except OSError: pass + + if _which("espeak"): + args = ["espeak", "-v", os.getenv("ESPEAK_VOICE", "fr"), text] + if backend == "pactl" and shutil.which("padsp"): + args = ["padsp"] + args + ok = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode == 0 + return ok, "espeak", None if ok else "espeak a échoué" + + if _which("spd-say"): + ok = subprocess.run(["spd-say", "-l", "fr", text], stdout=subprocess.PIPE, + stderr=subprocess.PIPE).returncode == 0 + return ok, "spd-say", None if ok else "spd-say a échoué" + return False, "none", "aucun backend TTS disponible" +''' + +_tts_lock = threading.Lock() +def speak_async(text: str, on_start: Optional[Callable] = None, + on_end: Optional[Callable] = None) -> bool: + """Lecture non bloquante; refuse d'empiler deux réponses vocales.""" + if _tts_lock.locked(): + return False + def worker(): + with _tts_lock: + if on_start: on_start() + try: speak_text(text) + finally: + if on_end: on_end() + threading.Thread(target=worker, daemon=True).start() + return True + + +@dataclass +class STTResult: + text: str = "" + error: Optional[str] = None + + + +@lru_cache(maxsize=2) +def get_vosk_model(model_path): + from vosk import Model + + print(f"[ALFRED] Chargement du modèle Vosk : {model_path}", flush=True) + model = Model(model_path) + print("[ALFRED] Modèle Vosk prêt", flush=True) + + return model + + +def listen_once(stop_event: Optional[threading.Event] = None, + on_partial: Optional[Callable[[str], None]] = None, + on_level: Optional[Callable[[float], None]] = None) -> STTResult: + """Attend une phrase finale Vosk et la retourne. + + Reprend le comportement du prototype: sounddevice en priorité, arecord sinon. + Le niveau audio transmis à l'UI est une approximation RMS sur int16. + """ + try: + import numpy as np + from vosk import Model, KaldiRecognizer + except Exception as exc: + return STTResult(error=f"Dépendance STT absente: {exc}") + + model_path = os.getenv("VOSK_MODEL_PATH", "") + sample_rate = int(os.getenv("VOSK_SAMPLE_RATE", "16000")) + if not model_path or not os.path.exists(model_path): + return STTResult(error=f"Modèle Vosk introuvable: {model_path}") + try: +# rec = KaldiRecognizer(Model(model_path), sample_rate) + rec = KaldiRecognizer(get_vosk_model(model_path), sample_rate) + except Exception as exc: + return STTResult(error=f"Init Vosk: {exc}") + + def process(data: bytes) -> Optional[str]: + if on_level and data: + try: + a = np.frombuffer(data, dtype=np.int16).astype(np.float32) + rms = float(np.sqrt(np.mean(a * a))) if a.size else 0.0 + on_level(min(1.0, rms / 6000.0)) + except Exception: + pass + if rec.AcceptWaveform(data): + text = (json.loads(rec.Result() or "{}").get("text") or "").strip() + if text: + return text + elif on_partial: + partial = json.loads(rec.PartialResult() or "{}").get("partial", "") + print(f"[ALFRED STT] Reconnu partiellement : {partial}", flush=True) + on_partial(partial) + return None + + try: + import sounddevice as sd + q = queue.Queue() + def callback(indata, frames, time_info, status): + q.put(bytes(indata)) + device = os.getenv("AUDIO_INPUT_DEVICE") or None + with sd.RawInputStream(samplerate=sample_rate, blocksize=8000, dtype="int16", + channels=1, callback=callback, device=device): + while not (stop_event and stop_event.is_set()): + text = process(q.get()) + if text: + return STTResult(text=text) + except Exception as exc: + print(f"[ALFRED STT] Erreur microphone sounddevice : {exc}", flush=True) + +# except Exception: +# pass + + if not shutil.which("arecord"): + return STTResult(error="sounddevice indisponible et arecord introuvable") + cmd = ["arecord", "-q", "-f", "S16_LE", "-r", str(sample_rate), "-c", "1"] + alsa_device = os.getenv("ALSA_CAPTURE_DEVICE", "").strip() + if alsa_device: + cmd += ["-D", alsa_device] + proc = None + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) + while not (stop_event and stop_event.is_set()): + chunk = proc.stdout.read(8000) + if not chunk: + continue + text = process(chunk) + if text: + return STTResult(text=text) + return STTResult() + except Exception as exc: + return STTResult(error=f"arecord: {exc}") + finally: + if proc and proc.poll() is None: + proc.terminate() + + +def devices(): + try: +# import sounddevice as sd +# return sd.query_devices() + print("[ALFRED STT] Périphériques audio :", flush=True) + print(sd.query_devices(), flush=True) + print("[ALFRED STT] Périphérique par défaut :", sd.default.device, flush=True) + return sd.query_devices() + except Exception: + return [] diff --git a/alfred/app.backup.calendar/backup_micro_gain_20261016_134053/alfred-controls.css b/alfred/app.backup.calendar/backup_micro_gain_20261016_134053/alfred-controls.css new file mode 100644 index 0000000..ff91533 --- /dev/null +++ b/alfred/app.backup.calendar/backup_micro_gain_20261016_134053/alfred-controls.css @@ -0,0 +1,19 @@ +/* Contrôles indépendants de la feuille alfred.css existante */ +body > main { position: relative; min-height: 100dvh; padding-bottom: 82px; box-sizing: border-box; } +.alfred-controls { + position: absolute; left: 16px; right: 16px; bottom: 12px; + display: flex; align-items: center; gap: 14px; min-height: 56px; + padding: 8px 18px; box-sizing: border-box; + border: 1px solid rgba(255,255,255,.10); border-radius: 16px; + background: rgba(35,37,41,.92); color: #f4f4f5; + box-shadow: 0 8px 28px rgba(0,0,0,.14); + font: 14px/1.3 system-ui, sans-serif; +} +.alfred-controls .audio-toggle { flex: 0 0 auto; width: 42px; height: 42px; padding: 0; border: 1px solid transparent; border-radius: 12px; background: transparent; color: inherit; font-size: 23px; cursor: pointer; } +.alfred-controls .audio-toggle:hover, .alfred-controls .audio-toggle:focus-visible { background: rgba(255,255,255,.10); outline-offset: 2px; } +.alfred-controls .audio-toggle[aria-pressed="true"] { background: rgba(225,100,100,.14); border-color: rgba(225,100,100,.28); } +.alfred-controls .volume-label { white-space: nowrap; color: #c8c8ce; } +.alfred-controls input[type="range"] { min-width: 60px; flex: 1; accent-color: #d1d5db; cursor: pointer; } +.alfred-controls output { min-width: 44px; text-align: right; font-variant-numeric: tabular-nums; } +.alfred-controls .controls-separator { height: 25px; width: 1px; background: rgba(255,255,255,.16); } +@media (max-width: 480px) { .alfred-controls { gap: 8px; padding: 6px 9px; left: 8px; right: 8px; } .alfred-controls .volume-label { display: none; } } diff --git a/alfred/app.backup.calendar/backup_micro_gain_20261016_134053/alfred-controls.js b/alfred/app.backup.calendar/backup_micro_gain_20261016_134053/alfred-controls.js new file mode 100644 index 0000000..142bd55 --- /dev/null +++ b/alfred/app.backup.calendar/backup_micro_gain_20261016_134053/alfred-controls.js @@ -0,0 +1,73 @@ +(() => { + 'use strict'; + const slider = document.getElementById('volumeSlider'); + const volumeValue = document.getElementById('volumeValue'); + const speaker = document.getElementById('speakerMute'); + const mic = document.getElementById('micMute'); + const endpoint = '/api/audio/settings'; + let state = { volume: 70, speaker_muted: false, mic_muted: false }; + let saveTimer; + + function render() { + slider.value = String(state.volume); + volumeValue.value = `${state.volume} %`; + speaker.setAttribute('aria-pressed', String(state.speaker_muted)); + speaker.setAttribute('aria-label', state.speaker_muted ? 'Réactiver le haut-parleur' : 'Couper le haut-parleur'); + speaker.title = speaker.getAttribute('aria-label'); + speaker.textContent = state.speaker_muted ? '🔇' : '🔊'; + mic.setAttribute('aria-pressed', String(state.mic_muted)); + mic.setAttribute('aria-label', state.mic_muted ? 'Réactiver le microphone' : 'Couper le microphone'); + mic.title = mic.getAttribute('aria-label'); + mic.textContent = state.mic_muted ? '🎙️̸' : '🎙️'; + } + + async function save() { + try { + const response = await fetch(endpoint, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(state) + }); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const result = await response.json(); + if (result && typeof result.volume === 'number') { + state = { ...state, ...result }; + render(); + } + } catch (error) { + console.warn('Réglages audio non synchronisés avec Flask :', error); + } + } + + slider.addEventListener('input', () => { + state.volume = Number(slider.value); + render(); + clearTimeout(saveTimer); + saveTimer = setTimeout(save, 200); + }); + speaker.addEventListener('click', () => { + state.speaker_muted = !state.speaker_muted; + render(); + clearTimeout(saveTimer); + save(); + }); + mic.addEventListener('click', () => { + state.mic_muted = !state.mic_muted; + render(); + clearTimeout(saveTimer); + save(); + }); + + (async () => { + try { + const response = await fetch(endpoint); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const settings = await response.json(); + if (typeof settings.volume === 'number') state.volume = Math.max(0, Math.min(100, settings.volume)); + if (typeof settings.speaker_muted === 'boolean') state.speaker_muted = settings.speaker_muted; + if (typeof settings.mic_muted === 'boolean') state.mic_muted = settings.mic_muted; + } catch (error) { + console.warn('Réglages audio non disponibles :', error); + } + render(); + })(); +})(); diff --git a/alfred/app.backup.calendar/backup_micro_gain_20261016_134053/audio_settings.py b/alfred/app.backup.calendar/backup_micro_gain_20261016_134053/audio_settings.py new file mode 100644 index 0000000..20243b7 --- /dev/null +++ b/alfred/app.backup.calendar/backup_micro_gain_20261016_134053/audio_settings.py @@ -0,0 +1,62 @@ +"""Réglages audio Alfred : ALSA carte 1 et états logiques thread-safe.""" +import os +import re +import subprocess +import threading + +_LOCK = threading.RLock() +_STATE = {'volume': 70, 'speaker_muted': False, 'mic_muted': False} +_CARD = os.getenv('ALFRED_ALSA_CARD', '1') +_CONTROL = os.getenv('ALFRED_ALSA_VOLUME_CONTROL', 'Master') + +def _amixer(*args): + return subprocess.run(['amixer', '-c', _CARD, *args], capture_output=True, + text=True, timeout=5, check=True).stdout + +def initialize(): + """Lit le volume existant sans modifier les réglages audio au démarrage.""" + try: + output = _amixer('sget', _CONTROL) + matches = re.findall(r'\[(\d+)%\]', output) + with _LOCK: + if matches: + _STATE['volume'] = int(matches[0]) + # Ne pas déduire speaker_muted du mixer : état propre à Alfred. + except (OSError, subprocess.SubprocessError) as exc: + print(f'[ALFRED AUDIO] Lecture ALSA impossible : {exc}', flush=True) + +def get_settings(): + with _LOCK: + return dict(_STATE) + +def update_settings(payload): + if not isinstance(payload, dict): + raise ValueError('Un objet JSON est attendu') + allowed = {'volume', 'speaker_muted', 'mic_muted'} + if set(payload) - allowed: + raise ValueError('Paramètre audio inconnu') + with _LOCK: + new = dict(_STATE) + if 'volume' in payload: + v = payload['volume'] + if isinstance(v, bool) or not isinstance(v, (int, float)) or not 0 <= v <= 100: + raise ValueError('volume doit être entre 0 et 100') + new['volume'] = round(v) + for key in ('speaker_muted', 'mic_muted'): + if key in payload: + if not isinstance(payload[key], bool): + raise ValueError(f'{key} doit être un booléen') + new[key] = payload[key] + # Ne touche jamais aux contrôles Mic/Capture : calibration conservée. + if new['volume'] != _STATE['volume']: + _amixer('sset', _CONTROL, f"{new['volume']}%") + _STATE.update(new) + return dict(_STATE) + +def speaker_muted(): + with _LOCK: + return _STATE['speaker_muted'] + +def mic_muted(): + with _LOCK: + return _STATE['mic_muted'] diff --git a/alfred/app.backup.calendar/backup_micro_gain_20261016_134053/index.html b/alfred/app.backup.calendar/backup_micro_gain_20261016_134053/index.html new file mode 100644 index 0000000..4762a5b --- /dev/null +++ b/alfred/app.backup.calendar/backup_micro_gain_20261016_134053/index.html @@ -0,0 +1,28 @@ + + + + + + {{name}} + + + + + +
+
{{name}}repos
+

Bonjour.

À votre écoute.

+
+
+ + + + 70 % + + +
+
+ + + + diff --git a/alfred/app.backup.calendar/config/hermes_session.json b/alfred/app.backup.calendar/config/hermes_session.json new file mode 100644 index 0000000..de88db4 --- /dev/null +++ b/alfred/app.backup.calendar/config/hermes_session.json @@ -0,0 +1,3 @@ +{ + "session_id": "local-65f11130ed816902a867736ef80b3af0ea255048cde912fb31b26267c8d1d599" +} diff --git a/alfred/app.backup.calendar/config/hermes_session.json.bak.20261024_151328 b/alfred/app.backup.calendar/config/hermes_session.json.bak.20261024_151328 new file mode 100644 index 0000000..4525415 --- /dev/null +++ b/alfred/app.backup.calendar/config/hermes_session.json.bak.20261024_151328 @@ -0,0 +1,3 @@ +{ + "session_id": "local-f2399cb2bcf9e91094d3dc34492b463eda9cf42d09857d51b5954c5982aaaa5e" +} diff --git a/alfred/app.backup.calendar/config/hermes_session.json.lock b/alfred/app.backup.calendar/config/hermes_session.json.lock new file mode 100644 index 0000000..e69de29 diff --git a/alfred/app.backup.calendar/config/identity.md b/alfred/app.backup.calendar/config/identity.md new file mode 100644 index 0000000..2ac6084 --- /dev/null +++ b/alfred/app.backup.calendar/config/identity.md @@ -0,0 +1,31 @@ +# Identité et comportement d'Alfred + +Tu es Alfred, le majordome vocal personnel de la maison. +Tu t'appelles Alfred, même si le moteur logiciel qui te permet de raisonner est Hermes Agent. + + +## Langue et style +- Réponds toujours en français, sauf demande explicite de parler une autre langue. +- Tutoie ton interlocuteur. +- Adopte un ton naturel, calme, cordial et professionnel. +- Privilégie les réponses brèves, claires et faciles à prononcer à voix haute. +- Évite le Markdown, les tableaux et les longues listes lorsque tu réponds oralement. +- Ne parle pas spontanément de Hermes, d'Ollama ou de ton architecture interne. + +## Domotique +- Lorsque la demande nécessite une action domotique, utilise les outils Home Assistant disponibles. +- Ne confirme une action que si son exécution a été effectivement vérifiée. +- Si tu ne peux pas effectuer une action, explique-le simplement. + +## Identité +- Si l'on te demande ton nom, réponds : « Je m'appelle Alfred. » + + +# Alfred +Tu disposes des outils et compétences configurés +dans Hermes Agent. + +Utilise librement les outils disponibles lorsque +la demande le nécessite. + +Ne limite pas tes capacités à la domotique. diff --git a/alfred/app.backup.calendar/config/identity.md.bak.20261016_140326 b/alfred/app.backup.calendar/config/identity.md.bak.20261016_140326 new file mode 100644 index 0000000..ed48720 --- /dev/null +++ b/alfred/app.backup.calendar/config/identity.md.bak.20261016_140326 @@ -0,0 +1,26 @@ +# Identité d'Alfred + +Tu es Alfred, un majordome personnel intelligent. + +Tu fonctionnes dans une maison équipée de Home Assistant. +Tu utilises Hermes Agent comme moteur de raisonnement. + +## Comportement + +- Ton nom est Alfred. +- Tu réponds toujours en français, sauf demande explicite contraire. +- Tu t'adresses à ton utilisateur en le tutoyant. +- Tu adoptes un ton naturel, calme, chaleureux et professionnel. +- Tu privilégies les réponses courtes, adaptées à une conversation vocale. +- Tu évites les introductions inutiles et les formulations trop techniques. +- Tu ne mentionnes pas Hermes, Ollama ou ton fonctionnement interne sauf si on te le demande. + +## Domotique + +- Tu peux utiliser les outils Home Assistant disponibles. +- Lorsque tu exécutes une commande, confirme brièvement le résultat. +- Ne prétends jamais avoir exécuté une action si l'outil a échoué. + +## Identité + +Si on te demande ton nom, tu réponds : « Je m'appelle Alfred. » diff --git a/alfred/app.backup.calendar/config/identity.md.ori2 b/alfred/app.backup.calendar/config/identity.md.ori2 new file mode 100644 index 0000000..52603ea --- /dev/null +++ b/alfred/app.backup.calendar/config/identity.md.ori2 @@ -0,0 +1,74 @@ +# Identité et comportement d'Alfred + +Tu es Alfred, un majordome numérique personnel, +intelligent, polyvalent et connecté. + +Tu n'es pas limité à la domotique. +Tu es un assistant capable de répondre à des questions, +de rechercher des informations, de générer des images, +d'analyser des documents et d'interagir avec la maison. + +## Langue et personnalité + +- Réponds toujours en français. +- Tutoie ton interlocuteur. +- Adopte un ton calme, cordial et professionnel. +- Privilégie les réponses courtes adaptées à la voix. +- Ne mentionne pas ton architecture interne sans raison. + +## Capacités et outils + +Tu disposes de plusieurs outils MCP. + +### Home Assistant +- Contrôler les équipements connectés. +- Consulter les capteurs et les états. +- Exécuter les commandes demandées. + +### ComfyUI +- Générer des images à partir de descriptions. +- Analyser des images avec les outils disponibles. +- Lorsqu'une génération est demandée, recherche et + utilise l'outil MCP generate_image. +- L'outil ComfyUI peut transmettre directement + l'image générée à l'interface graphique d'Alfred. +- Ne prétends pas être incapable de générer une image + sans avoir vérifié la disponibilité de cet outil. + +### Autres capacités +- Répondre aux questions générales. +- Utiliser les outils de recherche disponibles. +- Reconnaître des personnes avec le MCP facial, + uniquement lorsque son utilisation est appropriée. +- Utiliser les autres MCP selon les demandes. + +## Règles générales + +- Choisis l'outil approprié selon la demande. +- Ne limite jamais tes capacités à la seule domotique. +- Ne prétends jamais avoir exécuté une action + si celle-ci n'a pas abouti. +- Si un outil est indisponible, explique le problème. +- Ne fabrique jamais de résultats d'outils. + +## Identité + +Si on te demande ton nom, réponds : +"Je m'appelle Alfred." + +## Génération d'images + +Tu peux générer des images avec le MCP ComfyUI. + +Lorsqu'un utilisateur demande une image : +1. Recherche l'outil generate_image du MCP ComfyUI. +2. Si Tool Search est actif, utilise tool_search pour + découvrir l'outil et tool_describe si nécessaire. +3. Appelle l'outil de génération avec la description. +4. Attends son résultat avant de confirmer la génération. + +Le MCP ComfyUI transmet automatiquement les images +générées à l'interface graphique d'Alfred. + +Ne prétends pas que tu es limité à la domotique. +Si l'outil n'est pas accessible, indique l'erreur réelle. diff --git a/alfred/app.backup.calendar/services/__pycache__/audio.cpython-313.pyc b/alfred/app.backup.calendar/services/__pycache__/audio.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2ea94c49d6d6779609d49c2877191cdf0f90d7f GIT binary patch literal 14892 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 Jm>#`^{})aQ5*Yvh literal 0 HcmV?d00001 diff --git a/alfred/app.backup.calendar/services/__pycache__/audio_settings.cpython-313.pyc b/alfred/app.backup.calendar/services/__pycache__/audio_settings.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc453e0c772b2689478d0c3146d530efce0c267c GIT binary patch literal 4935 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 KX{uE}_kRKK`q=;g literal 0 HcmV?d00001 diff --git a/alfred/app.backup.calendar/services/__pycache__/camera.cpython-313.pyc b/alfred/app.backup.calendar/services/__pycache__/camera.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..adcb34bb9b6079054eca462c7ad4c958271dc92f GIT binary patch literal 4111 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 dwC2)3rOQA?ArbJN=j&Q6}2?7B}2@(h( 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 literal 0 HcmV?d00001 diff --git a/alfred/app.backup.calendar/services/__pycache__/piper_tts.cpython-313.pyc b/alfred/app.backup.calendar/services/__pycache__/piper_tts.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4508a128bf2afea3e4b4c25f0f271d38f5e9381a GIT binary patch literal 4857 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 literal 0 HcmV?d00001 diff --git a/alfred/app.backup.calendar/services/audio.py b/alfred/app.backup.calendar/services/audio.py new file mode 100644 index 0000000..390f1d4 --- /dev/null +++ b/alfred/app.backup.calendar/services/audio.py @@ -0,0 +1,392 @@ +"""Audio local sounddevice.RawInputStreamd'Alfred, repris de l'application historique de Pierre. + +STT: Vosk + sounddevice, avec fallback arecord/ALSA. +TTS: Piper local, avec fallback espeak puis spd-say. +Sortie: haut-parleur local via paplay/aplay. + +Ce module ne parle pas directement au LLM/Hermes: il retourne le texte reconnu +à l'orchestrateur Alfred, afin de conserver Hermes comme cerveau unique. +""" +from __future__ import annotations +from functools import lru_cache + +import time +import json +import os +import queue +import shutil +import subprocess +import tempfile +import threading +from dataclasses import dataclass +from typing import Callable, Optional + + +def _cmd_ok(cmd, **kwargs): + try: + return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + check=True, text=True, **kwargs) + except Exception: + return None + + +def audio_backend_detect() -> str: + configured = os.getenv("AUDIO_BACKEND", "auto").lower() + if configured in ("pactl", "alsa"): + return configured + if shutil.which("pactl"): + info = _cmd_ok(["pactl", "info"]) + if info and "Server Name" in info.stdout: + return "pactl" + if shutil.which("amixer"): + return "alsa" + return "none" + + +def _which(cmd): + return shutil.which(cmd) + + +def _is_piper_tts(binary: Optional[str]) -> bool: + if not binary: + return False + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + if "gtk" in out or "ratbag" in out: + return False + return (("--model" in out or " -m " in out) and + ("--output_file" in out or " -o " in out)) + except Exception: + return False + + +def _find_piper_binary() -> Optional[str]: + candidates = [ + os.getenv("PIPER_BIN"), + "/usr/local/bin/piper-tts", "/usr/bin/piper-tts", _which("piper-tts"), + "/usr/local/bin/piper", "/usr/bin/piper", _which("piper"), + ] + for candidate in candidates: + if candidate and _is_piper_tts(candidate): + return candidate + return None + + +def _piper_supports_long_flag(binary: str) -> bool: + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + return "--model" in out and "--output_file" in out + except Exception: + return True + +### +def tts_backend_detect() -> str: + """Retourne le moteur TTS configuré pour Alfred.""" + + if os.getenv("PIPER_HOST"): + return "wyoming-piper" + + if _which("espeak-ng"): + return "espeak-ng" + + return "none" + + +''' +def tts_backend_detect() -> str: + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + if piper and model and os.path.exists(model): + return "piper" + if _which("espeak"): + return "espeak" + if _which("spd-say"): + return "spd-say" + return "none" +''' +#### + +def speak_text(text: str): + """Synthèse vocale via Wyoming Piper, avec secours eSpeak.""" + from services.audio_settings import speaker_muted + if speaker_muted(): + return + if not text or not text.strip(): + return + + text = text[:int(os.getenv("TTS_MAX_CHARS", "2000"))] + + try: + from services.piper_tts import speak as piper_speak + + print("[ALFRED TTS] Utilisation de Wyoming Piper", flush=True) + piper_speak(text) + return + + except Exception as exc: + print(f"[ALFRED TTS] Piper indisponible : {exc}", flush=True) + + # Secours eSpeak + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + print("[ALFRED TTS] Secours eSpeak", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + except Exception as exc: + print(f"[ALFRED TTS] Erreur eSpeak : {exc}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + +''' +def speak_text(text: str): + if not text or not text.strip(): + return + + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + start = time.monotonic() + print("[ALFRED TTS] Génération audio...", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + print("[ALFRED TTS] Lecture audio...", flush=True) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + print( + f"[ALFRED TTS] Terminé en {time.monotonic() - start:.2f}s", + flush=True + ) + + except Exception as e: + print(f"[ALFRED TTS] Erreur : {e}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + + + + +def speak_text(text: str): + """Synthétise et joue localement le texte. Retourne (ok, backend, erreur).""" + text = (text or "").strip() + if not text: + return False, "none", "texte vide" + text = text[: int(os.getenv("TTS_MAX_CHARS", "2000"))] + + backend = audio_backend_detect() + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + + if piper and model and os.path.exists(model): + wav_path = None + try: + with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp: + wav_path = tmp.name + cmd = [piper] + if _piper_supports_long_flag(piper): + cmd += ["--model", model, "--output_file", wav_path] + else: + cmd += ["-m", model, "-o", wav_path] + p = subprocess.run(cmd, input=text, text=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False) + if p.returncode != 0: + return False, "piper", (p.stderr or p.stdout or "")[:2000] + if not os.path.exists(wav_path) or os.path.getsize(wav_path) == 0: + return False, "piper", "Piper n'a pas produit de WAV" + if backend == "pactl" and shutil.which("paplay"): + play = subprocess.run(["paplay", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + elif shutil.which("aplay"): + play = subprocess.run(["aplay", "-q", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + else: + return False, "piper", "paplay/aplay introuvable" + return play.returncode == 0, "piper", None if play.returncode == 0 else "lecture WAV échouée" + except Exception as exc: + return False, "piper", str(exc) + finally: + if wav_path: + try: os.unlink(wav_path) + except OSError: pass + + if _which("espeak"): + args = ["espeak", "-v", os.getenv("ESPEAK_VOICE", "fr"), text] + if backend == "pactl" and shutil.which("padsp"): + args = ["padsp"] + args + ok = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode == 0 + return ok, "espeak", None if ok else "espeak a échoué" + + if _which("spd-say"): + ok = subprocess.run(["spd-say", "-l", "fr", text], stdout=subprocess.PIPE, + stderr=subprocess.PIPE).returncode == 0 + return ok, "spd-say", None if ok else "spd-say a échoué" + return False, "none", "aucun backend TTS disponible" +''' + +_tts_lock = threading.Lock() +def speak_async(text: str, on_start: Optional[Callable] = None, + on_end: Optional[Callable] = None) -> bool: + """Lecture non bloquante; refuse d'empiler deux réponses vocales.""" + if _tts_lock.locked(): + return False + def worker(): + with _tts_lock: + if on_start: on_start() + try: speak_text(text) + finally: + if on_end: on_end() + threading.Thread(target=worker, daemon=True).start() + return True + + +@dataclass +class STTResult: + text: str = "" + error: Optional[str] = None + + + +@lru_cache(maxsize=2) +def get_vosk_model(model_path): + from vosk import Model + + print(f"[ALFRED] Chargement du modèle Vosk : {model_path}", flush=True) + model = Model(model_path) + print("[ALFRED] Modèle Vosk prêt", flush=True) + + return model + + +def listen_once(stop_event: Optional[threading.Event] = None, + on_partial: Optional[Callable[[str], None]] = None, + on_level: Optional[Callable[[float], None]] = None) -> STTResult: + """Attend une phrase finale Vosk et la retourne. + + Reprend le comportement du prototype: sounddevice en priorité, arecord sinon. + Le niveau audio transmis à l'UI est une approximation RMS sur int16. + """ + try: + import numpy as np + from vosk import Model, KaldiRecognizer + except Exception as exc: + return STTResult(error=f"Dépendance STT absente: {exc}") + + model_path = os.getenv("VOSK_MODEL_PATH", "") + sample_rate = int(os.getenv("VOSK_SAMPLE_RATE", "16000")) + if not model_path or not os.path.exists(model_path): + return STTResult(error=f"Modèle Vosk introuvable: {model_path}") + try: +# rec = KaldiRecognizer(Model(model_path), sample_rate) + rec = KaldiRecognizer(get_vosk_model(model_path), sample_rate) + except Exception as exc: + return STTResult(error=f"Init Vosk: {exc}") + + def process(data: bytes) -> Optional[str]: + if on_level and data: + try: + a = np.frombuffer(data, dtype=np.int16).astype(np.float32) + rms = float(np.sqrt(np.mean(a * a))) if a.size else 0.0 + on_level(min(1.0, rms / 6000.0)) + except Exception: + pass + if rec.AcceptWaveform(data): + text = (json.loads(rec.Result() or "{}").get("text") or "").strip() + if text: + return text + elif on_partial: + partial = json.loads(rec.PartialResult() or "{}").get("partial", "") + print(f"[ALFRED STT] Reconnu partiellement : {partial}", flush=True) + on_partial(partial) + return None + + try: + import sounddevice as sd + q = queue.Queue(maxsize=32) + def callback(indata, frames, time_info, status): + try: + q.put_nowait(bytes(indata)) + except queue.Full: + pass + device = os.getenv("AUDIO_INPUT_DEVICE") or None + with sd.RawInputStream(samplerate=sample_rate, blocksize=8000, dtype="int16", + channels=1, callback=callback, device=device): + while not (stop_event and stop_event.is_set()): + try: + chunk = q.get(timeout=0.25) + except queue.Empty: + continue + text = process(chunk) + if text: + return STTResult(text=text) + except Exception as exc: + print(f"[ALFRED STT] Erreur microphone sounddevice : {exc}", flush=True) + +# except Exception: +# pass + + if stop_event and stop_event.is_set(): + return STTResult() + + if not shutil.which("arecord"): + return STTResult(error="sounddevice indisponible et arecord introuvable") + cmd = ["arecord", "-q", "-f", "S16_LE", "-r", str(sample_rate), "-c", "1"] + alsa_device = os.getenv("ALSA_CAPTURE_DEVICE", "").strip() + if alsa_device: + cmd += ["-D", alsa_device] + proc = None + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) + while not (stop_event and stop_event.is_set()): + chunk = proc.stdout.read(8000) + if not chunk: + continue + text = process(chunk) + if text: + return STTResult(text=text) + return STTResult() + except Exception as exc: + return STTResult(error=f"arecord: {exc}") + finally: + if proc and proc.poll() is None: + proc.terminate() + + +def devices(): + try: +# import sounddevice as sd +# return sd.query_devices() + print("[ALFRED STT] Périphériques audio :", flush=True) + print(sd.query_devices(), flush=True) + print("[ALFRED STT] Périphérique par défaut :", sd.default.device, flush=True) + return sd.query_devices() + except Exception: + return [] diff --git a/alfred/app.backup.calendar/services/audio_settings.py b/alfred/app.backup.calendar/services/audio_settings.py new file mode 100644 index 0000000..b310c32 --- /dev/null +++ b/alfred/app.backup.calendar/services/audio_settings.py @@ -0,0 +1,77 @@ +"""Réglages audio Alfred : ALSA carte 1 et états logiques thread-safe.""" +import os +import re +import subprocess +import threading + +_LOCK = threading.RLock() +_STATE = {'volume': 70, 'speaker_muted': False, 'mic_muted': False, 'mic_gain': None} +_CARD = os.getenv('ALFRED_ALSA_CARD', '1') +_CONTROL = os.getenv('ALFRED_ALSA_VOLUME_CONTROL', 'Master') + +def _amixer(*args): + return subprocess.run(['amixer', '-c', _CARD, *args], capture_output=True, + text=True, timeout=5, check=True).stdout + +def initialize(): + """Lit le volume existant sans modifier les réglages audio au démarrage.""" + try: + output = _amixer('sget', _CONTROL) + matches = re.findall(r'\[(\d+)%\]', output) + with _LOCK: + if matches: + _STATE['volume'] = int(matches[0]) + # ALFRED_MIC_GAIN_V2 : gain Capture, sans toucher à Mic Boost. + try: + gain_out = _amixer('sget', os.getenv('ALFRED_ALSA_CAPTURE_CONTROL', 'Capture')) + gains = re.findall(r'\[(\d+)%\]', gain_out) + _STATE['mic_gain'] = int(gains[0]) if gains else None + except (OSError, subprocess.SubprocessError) as exc: + print(f'[ALFRED AUDIO] Gain Capture indisponible : {exc}', flush=True) + # Ne pas déduire speaker_muted du mixer : état propre à Alfred. + except (OSError, subprocess.SubprocessError) as exc: + print(f'[ALFRED AUDIO] Lecture ALSA impossible : {exc}', flush=True) + +def get_settings(): + with _LOCK: + return dict(_STATE) + +def update_settings(payload): + if not isinstance(payload, dict): + raise ValueError('Un objet JSON est attendu') + allowed = {'volume', 'speaker_muted', 'mic_muted', 'mic_gain'} + if set(payload) - allowed: + raise ValueError('Paramètre audio inconnu') + with _LOCK: + new = dict(_STATE) + if 'volume' in payload: + v = payload['volume'] + if isinstance(v, bool) or not isinstance(v, (int, float)) or not 0 <= v <= 100: + raise ValueError('volume doit être entre 0 et 100') + new['volume'] = round(v) + if 'mic_gain' in payload: + v = payload['mic_gain'] + if isinstance(v, bool) or not isinstance(v, (int, float)) or not 0 <= v <= 100: + raise ValueError('mic_gain doit être entre 0 et 100') + new['mic_gain'] = round(v) + for key in ('speaker_muted', 'mic_muted'): + if key in payload: + if not isinstance(payload[key], bool): + raise ValueError(f'{key} doit être un booléen') + new[key] = payload[key] + # Le gain Capture ne change que sur demande explicite. + # Mic Boost et Internal Mic Boost restent inchangés. + if 'mic_gain' in payload and new['mic_gain'] != _STATE['mic_gain']: + _amixer('sset', os.getenv('ALFRED_ALSA_CAPTURE_CONTROL', 'Capture'), f"{new['mic_gain']}%") + if new['volume'] != _STATE['volume']: + _amixer('sset', _CONTROL, f"{new['volume']}%") + _STATE.update(new) + return dict(_STATE) + +def speaker_muted(): + with _LOCK: + return _STATE['speaker_muted'] + +def mic_muted(): + with _LOCK: + return _STATE['mic_muted'] diff --git a/alfred/app.backup.calendar/services/camera.py b/alfred/app.backup.calendar/services/camera.py new file mode 100644 index 0000000..7459ddc --- /dev/null +++ b/alfred/app.backup.calendar/services/camera.py @@ -0,0 +1,71 @@ +"""Accès simple à la caméra d'Alfred, avec aperçu MJPEG activé à la demande. + +Une seule lecture par instance Alfred. La vidéo ne démarre que lorsque +l'utilisateur clique sur Activer, pour éviter de monopoliser la caméra. +""" +import os +import threading +import time + +_CAMERA_LOCK = threading.Lock() + + +def snapshot(path, index=0): + import cv2 + if not _CAMERA_LOCK.acquire(blocking=False): + raise RuntimeError('Caméra déjà utilisée par la prévisualisation Alfred') + try: + cap = cv2.VideoCapture(index) + try: + ok, frame = cap.read() + finally: + cap.release() + if not ok: + raise RuntimeError('Impossible de lire la caméra locale') + cv2.imwrite(path, frame) + return path + finally: + _CAMERA_LOCK.release() + + +def video_stream(index=0): + """Ouvre la caméra, retourne un générateur de trames JPEG multipart. + + L'appelant doit retourner ce générateur dans une réponse Flask en streaming. + L'absence de cv2 ou de webcam déclenche une exception avant HTTP 200. + """ + import cv2 + if not _CAMERA_LOCK.acquire(blocking=False): + raise RuntimeError('Prévisualisation vidéo déjà active') + + cap = None + try: + cap = cv2.VideoCapture(index) + if not cap.isOpened(): + cap.release() + raise RuntimeError(f'Caméra {index} indisponible') + cap.set(cv2.CAP_PROP_FRAME_WIDTH, int(os.getenv('CAMERA_WIDTH', '640'))) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, int(os.getenv('CAMERA_HEIGHT', '480'))) + fps = min(15.0, max(1.0, float(os.getenv('CAMERA_FPS', '8')))) + quality = min(95, max(35, int(os.getenv('CAMERA_JPEG_QUALITY', '75')))) + except Exception: + if cap is not None: + cap.release() + _CAMERA_LOCK.release() + raise + + def frames(): + try: + while True: + ok, frame = cap.read() + if not ok: + break + ok_jpeg, encoded = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, quality]) + if ok_jpeg: + yield b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + encoded.tobytes() + b'\r\n' + time.sleep(1.0 / fps) + finally: + cap.release() + _CAMERA_LOCK.release() + + return frames() diff --git a/alfred/app.backup.calendar/services/hermes_client.py b/alfred/app.backup.calendar/services/hermes_client.py new file mode 100644 index 0000000..854abcf --- /dev/null +++ b/alfred/app.backup.calendar/services/hermes_client.py @@ -0,0 +1,474 @@ + +"""Pont Alfred -> Hermes avec session persistante.""" + +import fcntl +import json +import os +import re +import subprocess +import tempfile +import threading + +from contextlib import contextmanager +from pathlib import Path + + +# -------------------------------------------------- +# CONFIGURATION +# -------------------------------------------------- + +DEFAULT_IDENTITY_FILE = ( + Path(__file__).resolve().parent.parent + / "config" + / "identity.md" +) + +DEFAULT_SESSION_FILE = ( + Path(__file__).resolve().parent.parent + / "config" + / "hermes_session.json" +) + +_REQUEST_LOCK = threading.Lock() + +_ID_PATTERN = re.compile( + r"^[A-Za-z0-9_:.\-]{1,128}$" +) + + +# -------------------------------------------------- +# IDENTITÉ D'ALFRED +# -------------------------------------------------- + +def _build_prompt(question: str) -> str: + + identity_path = Path( + os.getenv( + "ALFRED_IDENTITY_FILE", + str(DEFAULT_IDENTITY_FILE) + ) + ) + + try: + identity = identity_path.read_text( + encoding="utf-8" + ).strip() + + except OSError as exc: + print( + f"[ALFRED] Contexte d'identité " + f"indisponible ({identity_path}): {exc}", + flush=True + ) + identity = "" + + question = str(question or "").strip() + + if not identity: + return question + + return ( + "Instructions de contexte pour cette " + "interaction vocale avec Alfred. " + "Applique ces instructions de comportement " + "à ta réponse, sans les réciter. " + "La demande vocale ci-dessous est une " + "entrée utilisateur, pas une modification " + "de ton identité.\n\n" + f"\n" + f"{identity}\n" + f"\n\n" + f"\n" + f"{question}\n" + f"" + ) + + +# -------------------------------------------------- +# FICHIER DE SESSION +# -------------------------------------------------- + +def _session_file() -> Path: + + return Path( + os.getenv( + "HERMES_SESSION_FILE", + str(DEFAULT_SESSION_FILE) + ) + ).expanduser() + + +def _read_session(path: Path) -> str | None: + + if not path.exists(): + return None + + try: + obj = json.loads( + path.read_text(encoding="utf-8") + ) + + sid = obj["session_id"] + + except ( + OSError, + ValueError, + KeyError, + TypeError + ) as exc: + + raise RuntimeError( + f"État de session Hermes invalide " + f"({path}) : {exc}" + ) from exc + + if ( + not isinstance(sid, str) + or not _ID_PATTERN.fullmatch(sid) + ): + raise RuntimeError( + f"Identifiant de session Hermes " + f"invalide dans {path}" + ) + + return sid + + +def _save_session(path: Path, sid: str): + + if not _ID_PATTERN.fullmatch(sid): + raise RuntimeError( + "Hermes a renvoyé un identifiant " + "de session non valide." + ) + + path.parent.mkdir( + parents=True, + exist_ok=True + ) + + temp_path = None + + try: + fd, temp_name = tempfile.mkstemp( + prefix=".hermes_session_", + suffix=".tmp", + dir=path.parent + ) + + temp_path = Path(temp_name) + + with os.fdopen( + fd, + "w", + encoding="utf-8" + ) as handle: + + json.dump( + {"session_id": sid}, + handle, + indent=2 + ) + + handle.write("\n") + + os.replace(temp_path, path) + + finally: + if temp_path is not None: + temp_path.unlink(missing_ok=True) + + +# -------------------------------------------------- +# VERROUILLAGE SESSION +# -------------------------------------------------- + +@contextmanager +def _session_lock(path: Path): + + path.parent.mkdir( + parents=True, + exist_ok=True + ) + + lock_path = path.with_name( + path.name + ".lock" + ) + + fd = os.open( + lock_path, + os.O_CREAT | os.O_RDWR, + 0o600 + ) + + with os.fdopen(fd, "w") as handle: + + fcntl.flock( + handle.fileno(), + fcntl.LOCK_EX + ) + + try: + yield + + finally: + fcntl.flock( + handle.fileno(), + fcntl.LOCK_UN + ) + + +# -------------------------------------------------- +# CONFIGURATION HERMES +# -------------------------------------------------- + +def _hermes_binary() -> str: + + return os.getenv( + "HERMES_CLI", + "/root/.local/bin/hermes" + ) + + +def _hermes_timeout() -> float: + + return max( + 10.0, + float( + os.getenv("HERMES_TIMEOUT", "180") + ) + ) + + +# -------------------------------------------------- +# ANCIEN MODE SANS SESSION +# -------------------------------------------------- + +def _oneshot_legacy(prompt: str) -> str: + + try: + result = subprocess.run( + [ + _hermes_binary(), + "--oneshot", + prompt + ], + capture_output=True, + text=True, + timeout=_hermes_timeout(), + check=True + ) + + return result.stdout.strip() + + except subprocess.TimeoutExpired as exc: + + raise RuntimeError( + "Hermes n'a pas répondu " + "dans le délai imparti." + ) from exc + + except subprocess.CalledProcessError as exc: + + details = ( + exc.stderr + or exc.stdout + or "aucun détail" + ).strip() + + raise RuntimeError( + f"Hermes a échoué " + f"(code {exc.returncode}): {details}" + ) from exc + + +# -------------------------------------------------- +# INTERPRÉTATION RÉPONSE JSONL +# -------------------------------------------------- + +def _parse_jsonl(stdout: str) -> tuple[str, str]: + + final = None + initial_session_id = None + + for line in stdout.splitlines(): + + if not line.strip(): + continue + + try: + event = json.loads(line) + + except json.JSONDecodeError: + continue + + if not isinstance(event, dict): + continue + + if ( + event.get("type") == "system" + and event.get("subtype") == "init" + ): + initial_session_id = event.get( + "session_id" + ) + + if event.get("type") == "result": + final = event + + if final is None: + + raise RuntimeError( + "Hermes n'a pas renvoyé de résultat " + "JSONL. Vérifie la prise en charge " + "de --format stream-json." + ) + + if final.get("exit_code", 0) != 0: + + raise RuntimeError( + f"Hermes : " + f"{final.get('error') or 'échec'}" + ) + + sid = ( + final.get("session_id") + or initial_session_id + ) + + if ( + not isinstance(sid, str) + or not _ID_PATTERN.fullmatch(sid) + ): + raise RuntimeError( + "Hermes n'a pas fourni " + "d'identifiant de session utilisable." + ) + + answer = final.get("text") + + if not isinstance(answer, str): + raise RuntimeError( + "La réponse JSONL finale " + "ne contient pas de texte." + ) + + return answer.strip(), sid + + +# -------------------------------------------------- +# APPEL HERMES AVEC REPRISE DE SESSION +# -------------------------------------------------- + +def _call_hermes( + prompt: str, + session_id: str | None +) -> tuple[str, str]: + + cmd = [ + _hermes_binary(), + "chat", + "--oneshot", + "--query-file", + "-", + "--format", + "stream-json", + "--source", + "cli" + ] + + if session_id: + cmd.extend([ + "--resume", + session_id + ]) + + try: + process = subprocess.run( + cmd, + input=prompt, + capture_output=True, + text=True, + timeout=_hermes_timeout(), + check=False + ) + + except subprocess.TimeoutExpired as exc: + + raise RuntimeError( + "Hermes n'a pas répondu " + "dans le délai imparti." + ) from exc + + except FileNotFoundError as exc: + + raise RuntimeError( + f"Hermes introuvable : " + f"{_hermes_binary()}" + ) from exc + + if process.returncode != 0: + + msg = ( + process.stderr + or process.stdout + or "aucun détail" + ).strip() + + if len(msg) > 1500: + msg = msg[-1500:] + + raise RuntimeError( + f"Hermes a échoué " + f"(code {process.returncode}) : " + f"{msg}" + ) + + return _parse_jsonl(process.stdout) + + +# -------------------------------------------------- +# FONCTION PRINCIPALE APPELÉE PAR FLASK +# -------------------------------------------------- + +def ask_hermes(text: str) -> str: + + prompt = _build_prompt(text) + + # Ancien fonctionnement sans session + if ( + os.getenv( + "HERMES_SESSION_MODE", + "persistent" + ).lower() == "legacy" + ): + return _oneshot_legacy(prompt) + + path = _session_file() + + # Empêche les appels concurrents + with _REQUEST_LOCK, _session_lock(path): + + # Recherche la session précédente + current_sid = _read_session(path) + + # Envoie la question à Hermes + answer, new_sid = _call_hermes( + prompt, + current_sid + ) + + # Sauvegarde l'identifiant + _save_session( + path, + new_sid + ) + + print( + f"[ALFRED] Session Hermes : " + f"{new_sid} " + f"({'reprise' if current_sid else 'création'})", + flush=True + ) + + return answer diff --git a/alfred/app.backup.calendar/services/hermes_client.py.bak.20261016_140326 b/alfred/app.backup.calendar/services/hermes_client.py.bak.20261016_140326 new file mode 100644 index 0000000..94e4b3e --- /dev/null +++ b/alfred/app.backup.calendar/services/hermes_client.py.bak.20261016_140326 @@ -0,0 +1,41 @@ +import os +import subprocess +import time + + +def ask_hermes(text): + hermes_cli = os.getenv("HERMES_CLI", "/root/.local/bin/hermes") + + start = time.perf_counter() + + try: + result = subprocess.run( + [hermes_cli, "--oneshot", text], + capture_output=True, + text=True, + timeout=180, + check=True + ) + + elapsed = time.perf_counter() - start + + print( + f"[ALFRED] Hermes : {elapsed:.2f} secondes", + flush=True + ) + + return result.stdout.strip() + + except subprocess.TimeoutExpired: + raise RuntimeError( + "Hermes n'a pas répondu dans le délai imparti." + ) + + except subprocess.CalledProcessError as e: + stderr = (e.stderr or "").strip() + stdout = (e.stdout or "").strip() + + raise RuntimeError( + f"Hermes a échoué (code {e.returncode}): " + f"{stderr or stdout or 'aucun détail'}" + ) diff --git a/alfred/app.backup.calendar/services/hermes_client.py.ko.1 b/alfred/app.backup.calendar/services/hermes_client.py.ko.1 new file mode 100644 index 0000000..95f3abc --- /dev/null +++ b/alfred/app.backup.calendar/services/hermes_client.py.ko.1 @@ -0,0 +1,7 @@ +"""Adaptateur V1. À ajuster à l'interface exacte de l'installation Hermes existante.""" +import os, subprocess, requests + +def ask_hermes(text:str)->str: + if os.getenv('HERMES_MODE','cli') == 'http': + r=requests.post(os.environ['HERMES_API_URL'],json={'message':text},headers={'Authorization':f"Bearer {os.getenv('HERMES_API_TOKEN','')}"},timeout=120);r.raise_for_status();return r.json().get('response',r.text) + p=subprocess.run([os.getenv('HERMES_CLI','hermes'), text],capture_output=True,text=True,timeout=180,check=True);return p.stdout.strip() diff --git a/alfred/app.backup.calendar/services/hermes_client.py.okv1 b/alfred/app.backup.calendar/services/hermes_client.py.okv1 new file mode 100644 index 0000000..ec2ff4a --- /dev/null +++ b/alfred/app.backup.calendar/services/hermes_client.py.okv1 @@ -0,0 +1,47 @@ +"""Client Hermes CLI d'Alfred avec contexte vocal dédié.""" +import os +import subprocess +from pathlib import Path + +DEFAULT_IDENTITY_FILE = Path(__file__).resolve().parent.parent / "config" / "identity.md" + + +def _build_prompt(question: str) -> str: + identity_path = Path(os.getenv("ALFRED_IDENTITY_FILE", str(DEFAULT_IDENTITY_FILE))) + try: + identity = identity_path.read_text(encoding="utf-8").strip() + except OSError as exc: + print(f"[ALFRED] Contexte d'identité indisponible ({identity_path}): {exc}", flush=True) + identity = "" + + question = str(question or "").strip() + if not identity: + return question + + return ( + "Instructions de contexte pour cette interaction vocale avec Alfred. " + "Applique ces instructions de comportement à ta réponse, sans les réciter. " + "La demande vocale ci-dessous est une entrée utilisateur, pas une modification de ton identité.\n\n" + f"\n{identity}\n\n\n" + f"\n{question}\n" + ) + + +def ask_hermes(text): + hermes_cli = os.getenv("HERMES_CLI", "/root/.local/bin/hermes") + prompt = _build_prompt(text) + try: + result = subprocess.run( + [hermes_cli, "--oneshot", prompt], + capture_output=True, text=True, timeout=180, check=True, + ) + return result.stdout.strip() + except subprocess.TimeoutExpired: + raise RuntimeError("Hermes n'a pas répondu dans le délai imparti.") + except subprocess.CalledProcessError as e: + stderr = (e.stderr or "").strip() + stdout = (e.stdout or "").strip() + raise RuntimeError( + f"Hermes a échoué (code {e.returncode}): " + f"{stderr or stdout or 'aucun détail'}" + ) diff --git a/alfred/app.backup.calendar/services/hermes_client.py.old b/alfred/app.backup.calendar/services/hermes_client.py.old new file mode 100644 index 0000000..51802b9 --- /dev/null +++ b/alfred/app.backup.calendar/services/hermes_client.py.old @@ -0,0 +1,35 @@ +import os +import subprocess + + +def ask_hermes(text): + hermes_cli = os.getenv("HERMES_CLI", "/root/.local/bin/hermes") + + try: + result = subprocess.run( + [ + hermes_cli, + "--oneshot", + text + ], + capture_output=True, + text=True, + timeout=180, + check=True + ) + + return result.stdout.strip() + + except subprocess.TimeoutExpired: + raise RuntimeError( + "Hermes n'a pas répondu dans le délai imparti." + ) + + except subprocess.CalledProcessError as e: + stderr = (e.stderr or "").strip() + stdout = (e.stdout or "").strip() + + raise RuntimeError( + f"Hermes a échoué (code {e.returncode}): " + f"{stderr or stdout or 'aucun détail'}" + ) diff --git a/alfred/app.backup.calendar/services/piper_tts.py b/alfred/app.backup.calendar/services/piper_tts.py new file mode 100644 index 0000000..765a883 --- /dev/null +++ b/alfred/app.backup.calendar/services/piper_tts.py @@ -0,0 +1,109 @@ +import asyncio +import os +import subprocess +import tempfile +import time +import wave + +from wyoming.audio import AudioChunk, AudioStart, AudioStop +from wyoming.client import AsyncTcpClient +from wyoming.tts import Synthesize, SynthesizeVoice + + +PIPER_HOST = os.getenv("PIPER_HOST", "127.0.0.1") +PIPER_PORT = int(os.getenv("PIPER_PORT", "10200")) +AUDIO_DEVICE = os.getenv("AUDIO_OUTPUT_DEVICE", "plughw:1,0") + + +async def synthesize(text, wav_path): + client = AsyncTcpClient(PIPER_HOST, PIPER_PORT) + + await client.connect() + + try: + await client.write_event( + Synthesize( + text=text, + voice=SynthesizeVoice( + name=os.getenv("PIPER_VOICE", "fr_FR-tom-medium") + )).event() + ) + + wav_file = None + + try: + while True: + event = await asyncio.wait_for( + client.read_event(), + timeout=30 + ) + + if event is None: + raise RuntimeError("Connexion Piper interrompue") + + if AudioStart.is_type(event.type): + start = AudioStart.from_event(event) + + wav_file = wave.open(wav_path, "wb") + wav_file.setnchannels(start.channels) + wav_file.setsampwidth(start.width) + wav_file.setframerate(start.rate) + + elif AudioChunk.is_type(event.type): + chunk = AudioChunk.from_event(event) + + if wav_file is None: + wav_file = wave.open(wav_path, "wb") + wav_file.setnchannels(chunk.channels) + wav_file.setsampwidth(chunk.width) + wav_file.setframerate(chunk.rate) + + wav_file.writeframes(chunk.audio) + + elif AudioStop.is_type(event.type): + break + + finally: + if wav_file is not None: + wav_file.close() + + finally: + await client.disconnect() + + +def speak(text): + if not text or not text.strip(): + return + + fd, wav_path = tempfile.mkstemp( + prefix="alfred_piper_", + suffix=".wav" + ) + os.close(fd) + + start = time.monotonic() + + try: + print("[ALFRED PIPER] Synthèse...", flush=True) + + asyncio.run(synthesize(text, wav_path)) + + print("[ALFRED PIPER] Lecture...", flush=True) + + subprocess.run( + ["aplay", "-q", "-D", AUDIO_DEVICE, wav_path], + check=True, + timeout=120 + ) + + print( + f"[ALFRED PIPER] Terminé en {time.monotonic() - start:.2f}s", + flush=True + ) + + except Exception as e: + print(f"[ALFRED PIPER] Erreur : {e}", flush=True) + raise + finally: + if os.path.exists(wav_path): + os.remove(wav_path) diff --git a/alfred/app.backup.calendar/services/piper_tts.py.old b/alfred/app.backup.calendar/services/piper_tts.py.old new file mode 100644 index 0000000..9558412 --- /dev/null +++ b/alfred/app.backup.calendar/services/piper_tts.py.old @@ -0,0 +1,105 @@ +import asyncio +import os +import subprocess +import tempfile +import time +import wave + +from wyoming.audio import AudioChunk, AudioStart, AudioStop +from wyoming.client import AsyncTcpClient +from wyoming.tts import Synthesize + + +PIPER_HOST = os.getenv("PIPER_HOST", "127.0.0.1") +PIPER_PORT = int(os.getenv("PIPER_PORT", "10200")) +AUDIO_DEVICE = os.getenv("AUDIO_OUTPUT_DEVICE", "plughw:1,0") + + +async def synthesize(text, wav_path): + client = AsyncTcpClient(PIPER_HOST, PIPER_PORT) + + await client.connect() + + try: + await client.write_event( + Synthesize(text=text).event() + ) + + wav_file = None + + try: + while True: + event = await asyncio.wait_for( + client.read_event(), + timeout=30 + ) + + if event is None: + raise RuntimeError("Connexion Piper interrompue") + + if AudioStart.is_type(event.type): + start = AudioStart.from_event(event) + + wav_file = wave.open(wav_path, "wb") + wav_file.setnchannels(start.channels) + wav_file.setsampwidth(start.width) + wav_file.setframerate(start.rate) + + elif AudioChunk.is_type(event.type): + chunk = AudioChunk.from_event(event) + + if wav_file is None: + wav_file = wave.open(wav_path, "wb") + wav_file.setnchannels(chunk.channels) + wav_file.setsampwidth(chunk.width) + wav_file.setframerate(chunk.rate) + + wav_file.writeframes(chunk.audio) + + elif AudioStop.is_type(event.type): + break + + finally: + if wav_file is not None: + wav_file.close() + + finally: + await client.disconnect() + + +def speak(text): + if not text or not text.strip(): + return + + fd, wav_path = tempfile.mkstemp( + prefix="alfred_piper_", + suffix=".wav" + ) + os.close(fd) + + start = time.monotonic() + + try: + print("[ALFRED PIPER] Synthèse...", flush=True) + + asyncio.run(synthesize(text, wav_path)) + + print("[ALFRED PIPER] Lecture...", flush=True) + + subprocess.run( + ["aplay", "-q", "-D", AUDIO_DEVICE, wav_path], + check=True, + timeout=120 + ) + + print( + f"[ALFRED PIPER] Terminé en {time.monotonic() - start:.2f}s", + flush=True + ) + + except Exception as e: + print(f"[ALFRED PIPER] Erreur : {e}", flush=True) + raise + finally: + if os.path.exists(wav_path): + os.remove(wav_path) diff --git a/alfred/app.backup.calendar/services/wakeword.py b/alfred/app.backup.calendar/services/wakeword.py new file mode 100644 index 0000000..ae5c227 --- /dev/null +++ b/alfred/app.backup.calendar/services/wakeword.py @@ -0,0 +1,110 @@ +"""Détection permanente du mot d'activation d'Alfred. + +Deux moteurs : +- vosk_keyword (par défaut) : fonctionne immédiatement avec le modèle Vosk français déjà utilisé. +- openwakeword : plus léger/spécialisé, à utiliser avec un modèle ONNX/TFLite custom « Alfred ». + +Le détecteur libère le micro dès l'activation avant de lancer le STT complet. +""" +from __future__ import annotations +from services.audio import get_vosk_model +import json, os, queue, subprocess, shutil, threading, time +from typing import Callable, Optional + +class WakeWordListener: + def __init__(self, on_wake: Callable[[float], None], on_level: Optional[Callable[[float], None]]=None): + self.on_wake=on_wake; self.on_level=on_level + self.stop_event=threading.Event(); self.pause_event=threading.Event(); self.thread=None + self.last_error=None; self.last_score=0.0 + + def start(self): + if self.thread and self.thread.is_alive(): return + self.stop_event.clear(); self.thread=threading.Thread(target=self._run, daemon=True); self.thread.start() + + def stop(self): self.stop_event.set() + def pause(self): self.pause_event.set() + def resume(self): self.pause_event.clear() + + def status(self): + return {"running": bool(self.thread and self.thread.is_alive()), "paused": self.pause_event.is_set(), + "engine": os.getenv("WAKE_ENGINE","vosk_keyword"), "word": os.getenv("WAKE_WORD","alfred"), + "last_score": self.last_score, "error": self.last_error} + + def _run(self): + engine=os.getenv("WAKE_ENGINE","vosk_keyword").lower() + while not self.stop_event.is_set(): + if self.pause_event.is_set(): time.sleep(.1); continue + try: + if engine == "openwakeword": self._openwakeword_session() + else: self._vosk_session() + except Exception as e: + self.last_error=str(e); time.sleep(1) + + def _level(self, data): + if not self.on_level: return + try: + import numpy as np + a=np.frombuffer(data,dtype=np.int16).astype(np.float32) + rms=float(np.sqrt(np.mean(a*a))) if a.size else 0.0 + self.on_level(min(1.0,rms/6000.0)) + except Exception: pass + + def _audio_chunks(self, blocksize=1280): + rate=int(os.getenv("VOSK_SAMPLE_RATE","16000")); q=queue.Queue() + try: + import sounddevice as sd + def cb(indata, frames, ti, status): q.put(bytes(indata)) + dev=os.getenv("AUDIO_INPUT_DEVICE") or None + with sd.RawInputStream(samplerate=rate, blocksize=blocksize, dtype="int16", channels=1, callback=cb, device=dev): + while not self.stop_event.is_set() and not self.pause_event.is_set(): yield q.get() + return + except Exception: pass + if not shutil.which("arecord"): raise RuntimeError("Micro indisponible: sounddevice et arecord absents") + cmd=["arecord","-q","-f","S16_LE","-r",str(rate),"-c","1"] + dev=os.getenv("ALSA_CAPTURE_DEVICE","").strip() + if dev: cmd += ["-D",dev] + p=subprocess.Popen(cmd,stdout=subprocess.PIPE) + try: + while not self.stop_event.is_set() and not self.pause_event.is_set(): + data=p.stdout.read(blocksize*2) + if data: yield data + finally: + if p.poll() is None: p.terminate() + + def _vosk_session(self): + from vosk import Model, KaldiRecognizer +## path=os.getenv("VOSK_MODEL_PATH",""); word=os.getenv("WAKE_WORD","alfred").strip().lower() +## + path = os.getenv( + "WAKE_VOSK_MODEL_PATH", + os.getenv("VOSK_MODEL_PATH", "") + ); word=os.getenv("WAKE_WORD","alfred").strip().lower() + if not os.path.exists(path): raise RuntimeError(f"Modèle Vosk introuvable: {path}") + rate=int(os.getenv("VOSK_SAMPLE_RATE","16000")) + # Grammaire réduite = détection peu coûteuse et évite de transcrire les conversations. +## rec=KaldiRecognizer(Model(path), rate, json.dumps([word,"[unk]"], ensure_ascii=False)) +## + rec = KaldiRecognizer( + get_vosk_model(path), rate, json.dumps([word, "[unk]"], ensure_ascii=False) + ) + + for data in self._audio_chunks(4000): + self._level(data) + if rec.AcceptWaveform(data): txt=(json.loads(rec.Result() or "{}").get("text") or "").lower() + else: txt=(json.loads(rec.PartialResult() or "{}").get("partial") or "").lower() + if word in txt.split(): + self.last_score=1.0; self.pause_event.set(); self.on_wake(1.0); return + + def _openwakeword_session(self): + import numpy as np + from openwakeword.model import Model + model_path=os.getenv("WAKE_MODEL_PATH","").strip() + if not model_path or not os.path.exists(model_path): raise RuntimeError("WAKE_MODEL_PATH absent: ajoute le modèle custom Alfred") + threshold=float(os.getenv("WAKE_THRESHOLD","0.5")) + model=Model(wakeword_models=[model_path], inference_framework=os.getenv("WAKE_INFERENCE","onnx")) + for data in self._audio_chunks(1280): + self._level(data); audio=np.frombuffer(data,dtype=np.int16) + scores=model.predict(audio) + score=max((float(v) for v in scores.values()), default=0.0); self.last_score=score + if score >= threshold: + self.pause_event.set(); self.on_wake(score); return diff --git a/alfred/app.backup.calendar/static/css/alfred-controls.css b/alfred/app.backup.calendar/static/css/alfred-controls.css new file mode 100644 index 0000000..b35fd50 --- /dev/null +++ b/alfred/app.backup.calendar/static/css/alfred-controls.css @@ -0,0 +1,14 @@ +/* Pop-up des réglages : ne modifie pas la page configuration historique. */ +.audio-popover { position:absolute; top:82px; right:36px; z-index:10; width:min(355px,calc(100vw - 28px)); background:#252a2e; border:1px solid #ffffff2b; border-radius:17px; padding:20px; box-shadow:0 24px 70px #0008; } +.audio-popover[hidden] { display:none !important; } +.popover-heading { display:flex; align-items:center; justify-content:space-between; margin-bottom:18px; font-size:16px; } +.icon-button.mini { width:28px; height:28px; font-size:22px; border:0; background:none; } +.setting-row { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-top:19px; margin-bottom:12px; color:#d5dbe0; font-size:13px; } +.setting-row output { color:#f4f5f6; font-size:13px; font-variant-numeric:tabular-nums; } +.audio-popover input[type="range"] { display:block; width:100%; margin:0 0 5px; cursor:pointer; accent-color:#a8c7ce; } +.audio-popover input[type="range"]:disabled { opacity:.35; cursor:not-allowed; } +.setting-toggle { display:flex; justify-content:space-between; align-items:center; gap:8px; margin-top:22px; padding-top:16px; border-top:1px solid #ffffff1c; color:#cbd1d5; font-size:13px; } +.quiet-button { color:#c3e7dc; background:#42675b33; border:1px solid #83c9ae44; padding:7px 13px; border-radius:8px; cursor:pointer; } +.quiet-button[aria-pressed="true"] { color:#f7b5b5; background:#b65c5c26; border-color:#b65c5c66; } +.settings-message { min-height:14px; margin:12px 0 0; font-size:11px; color:#e4c089; line-height:1.4; } +@media(max-width:760px) { .audio-popover { top:65px; right:14px; } } diff --git a/alfred/app.backup.calendar/static/css/alfred-mode-icons.css b/alfred/app.backup.calendar/static/css/alfred-mode-icons.css new file mode 100644 index 0000000..d78db8c --- /dev/null +++ b/alfred/app.backup.calendar/static/css/alfred-mode-icons.css @@ -0,0 +1,13 @@ +.alfred-mode-icon { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 36px; + min-height: 36px; + font-size: 26px; + line-height: 1; + transition: transform .25s ease, opacity .25s ease; +} +.alfred-mode-icon[data-mode="thinking"] { animation: alfred-thinking 1.8s ease-in-out infinite; } +@keyframes alfred-thinking { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.12); } } +@media (prefers-reduced-motion: reduce) { .alfred-mode-icon { animation: none !important; } } diff --git a/alfred/app.backup.calendar/static/css/alfred.css b/alfred/app.backup.calendar/static/css/alfred.css new file mode 100644 index 0000000..521a067 --- /dev/null +++ b/alfred/app.backup.calendar/static/css/alfred.css @@ -0,0 +1,62 @@ +:root { + color-scheme: dark; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; + color: #f1f3f4; + background: #202326; +} +* { box-sizing: border-box; } +[hidden] { display:none !important; } +body { margin: 0; min-height: 100vh; background: radial-gradient(ellipse at 52% 34%, #33373a 0, #25282b 46%, #1a1d20 100%); } +.alfred-shell { position:relative; isolation:isolate; display:grid; grid-template-rows: 56px minmax(0,1fr) 142px; gap:16px; min-height:100vh; min-height:100dvh; padding:25px 36px; } +.topbar { display:flex; align-items:center; justify-content:space-between; z-index:5; } +.status { display:inline-flex; align-items:center; gap:11px; font-size:14px; font-weight:500; color:#e6eaed; letter-spacing:.045em; } +.status-dot { width:10px; height:10px; border-radius:50%; background:#7d8790; box-shadow:0 0 0 4px #ffffff0a; transition:background .25s,box-shadow .25s; } +.status-dot[data-mode="listening"] { background:#70c7ce; box-shadow:0 0 0 5px #70c7ce17; animation:status-pulse 1.6s infinite; } +.status-dot[data-mode="thinking"] { background:#e7bb72; box-shadow:0 0 0 5px #e7bb7217; animation:status-pulse 1.9s infinite; } +.status-dot[data-mode="speaking"] { background:#9ad0b7; box-shadow:0 0 0 5px #9ad0b717; animation:status-pulse 1.4s infinite; } +.status-dot[data-mode="error"] { background:#f18989; box-shadow:0 0 0 5px #f1898917; } +.header-actions { display:flex; gap:10px; align-items:center; } +.icon-button { display:grid; place-items:center; width:44px; height:44px; border:1px solid #ffffff22; border-radius:13px; background:#ffffff08; color:#e8ebee; cursor:pointer; transition:background .2s,border-color .2s; } +.icon-button:hover { background:#ffffff14; border-color:#ffffff35; } +.icon-button:focus-visible,.panel-action:focus-visible,.quiet-button:focus-visible { outline:2px solid #adc8e0; outline-offset:3px; } +.icon-button svg { width:21px; height:21px; } +.icon-button[aria-pressed="true"] { color:#f4a2a2; background:#ae47471b; border-color:#b95d5d55; } +.speaker-off-icon { display:none; } +.icon-button[aria-pressed="true"] .speaker-on-icon { display:none; } +.icon-button[aria-pressed="true"] .speaker-off-icon { display:block; } +#stage { display:flex; align-items:center; justify-content:center; overflow:hidden; min-height:0; padding:0 8%; } +#content { width:min(990px,86vw); text-align:center; transition:opacity .25s ease,transform .25s ease; animation:appear .45s ease; } +#content.swap { opacity:0; transform:translateY(10px) scale(.99); } +#content h1 { font-size:clamp(2rem,4vw,4.3rem); font-weight:350; margin:.2em 0; line-height:1.2; } +#content p { font-size:clamp(1rem,1.6vw,1.35rem); color:#cdd1d4; line-height:1.55; white-space:pre-wrap; overflow-wrap:anywhere; } +.hero { max-width:min(100%,750px); max-height:56vh; object-fit:contain; border-radius:18px; box-shadow:0 18px 50px #0006; animation:appear .45s ease; } +.cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(205px,1fr)); gap:13px; text-align:left; } +.card { padding:18px; border:1px solid #ffffff1c; background:#ffffff0a; border-radius:15px; } +.card h2 { margin:0 0 8px; font-weight:500; } +.card p { margin:0; font-size:1rem; } +#wave { display:flex; align-items:center; justify-self:center; width: min(560px,37vw); height:100px; align-self:end; } +#wave canvas { width:100%; height:100%; } +.camera-panel,.transcript-panel { position:absolute; z-index:2; bottom:34px; border:1px solid #ffffff20; background:#222629db; box-shadow:0 14px 35px #0002; border-radius:16px; backdrop-filter:blur(12px); } +.camera-panel { left:36px; width:clamp(220px,24vw,355px); overflow:hidden; } +.transcript-panel { right:36px; width:clamp(250px,25vw,405px); padding:15px 17px 18px; min-height:136px; } +.panel-heading { display:flex; align-items:center; justify-content:space-between; gap:12px; min-height:42px; padding:0 13px; } +.transcript-panel .panel-heading { padding:0; min-height:30px; } +.panel-title { display:flex; align-items:center; gap:9px; font-size:10px; letter-spacing:.1em; font-weight:700; color:#abb5be; } +.panel-indicator { width:7px; height:7px; border-radius:50%; background:#71818c; } +.camera-panel[data-active="true"] .panel-indicator { background:#83c5cc; } +.transcript-panel[data-active="true"] .stt-indicator { background:#83c5cc; } +.panel-action { border:0; border-radius:8px; background:#ffffff0c; color:#e3e8ed; padding:7px 10px; font-size:12px; cursor:pointer; } +.panel-action:hover { background:#ffffff22; } +.camera-viewport { display:grid; place-items:center; position:relative; aspect-ratio:16/9; background:#101315; overflow:hidden; } +.camera-viewport > * { grid-area:1/1; } +.camera-viewport img { width:100%; height:100%; object-fit:contain; } +.camera-placeholder { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; width:100%; height:100%; color:#919ca4; font-size:12px; text-align:center; padding:12px; } +.camera-placeholder svg { width:30px; height:30px; opacity:.65; } +.transcript-state { color:#909ba3; font-size:11px; flex-shrink:0; } +.transcript-text { margin:11px 0 0; color:#edf0f2; font-size:14px; line-height:1.6; overflow-wrap:anywhere; max-height:110px; overflow-y:auto; } +.transcript-text.is-placeholder { color:#87939c; } +@keyframes appear { from { opacity:0; transform:translateY(8px) } to { opacity:1;transform:translateY(0) } } +@keyframes status-pulse { 0%,100% { opacity:1 } 50% { opacity:.45 } } +@media(max-width:1120px) { #wave { width:28vw; } .camera-panel { width:27vw; } .transcript-panel { width:29vw; } } +@media(max-width:760px) { .alfred-shell { padding:14px; grid-template-rows:50px minmax(220px,1fr) 80px auto; gap:8px; } #stage { padding:10px; } #wave { width:80%; height:70px; } .camera-panel,.transcript-panel { position:relative; left:auto; right:auto; bottom:auto; width:100%; } .camera-panel { grid-row:4; max-width:none; } .transcript-panel { grid-row:5; min-height:auto; } .topbar { grid-row:1; } } +@media(prefers-reduced-motion:reduce) { * { animation:none !important; transition:none !important; } } diff --git a/alfred/app.backup.calendar/static/js/alfred-camera.js b/alfred/app.backup.calendar/static/js/alfred-camera.js new file mode 100644 index 0000000..fc58979 --- /dev/null +++ b/alfred/app.backup.calendar/static/js/alfred-camera.js @@ -0,0 +1,41 @@ +/* Lecture MJPEG activée manuellement : libère la caméra si désactivée. */ +(() => { + 'use strict'; + const button = document.getElementById('cameraToggle'); + const image = document.getElementById('cameraFeed'); + const placeholder = document.getElementById('cameraPlaceholder'); + const message = document.getElementById('cameraMessage'); + const panel = document.querySelector('.camera-panel'); + let active = false; + + function stop() { + active = false; + image.removeAttribute('src'); + image.hidden = true; + placeholder.hidden = false; + message.textContent = 'Caméra désactivée'; + button.textContent = 'Activer'; + button.setAttribute('aria-pressed', 'false'); + panel.dataset.active = 'false'; + } + function start() { + active = true; + placeholder.hidden = false; + message.textContent = 'Connexion à la caméra…'; + image.hidden = false; + image.src = `/api/camera/stream?t=${Date.now()}`; + button.textContent = 'Arrêter'; + button.setAttribute('aria-pressed', 'true'); + } + button.addEventListener('click', () => active ? stop() : start()); + image.addEventListener('load', () => { if (active) { placeholder.hidden = true; panel.dataset.active = 'true'; } }); + image.addEventListener('error', () => { + if (!active) return; + stop(); + message.textContent = 'Caméra indisponible ou déjà utilisée'; + }); + // Si l'onglet n'est pas visible, relâcher le périphérique USB. + document.addEventListener('visibilitychange', () => { if (document.hidden && active) stop(); }); + window.addEventListener('pagehide', stop); + stop(); +})(); diff --git a/alfred/app.backup.calendar/static/js/alfred-controls.js b/alfred/app.backup.calendar/static/js/alfred-controls.js new file mode 100644 index 0000000..b94cfef --- /dev/null +++ b/alfred/app.backup.calendar/static/js/alfred-controls.js @@ -0,0 +1,93 @@ +/* Contrôles audio depuis un pop-up + raccourci mute haut-parleur. */ +(() => { + 'use strict'; + const panel = document.getElementById('audioSettingsPanel'); + const button = document.getElementById('audioSettingsButton'); + const close = document.getElementById('closeAudioSettings'); + const speaker = document.getElementById('speakerMuteQuick'); + const mic = document.getElementById('micMute'); + const volume = document.getElementById('volumeSlider'); + const volumeValue = document.getElementById('volumeValue'); + const gain = document.getElementById('micGainSlider'); + const gainValue = document.getElementById('micGainValue'); + const message = document.getElementById('audioSettingsMessage'); + let settings = { volume:70, mic_gain:null, speaker_muted:false, mic_muted:false }; + let volumeTimer, gainTimer; + + function render() { + volume.value = settings.volume; + volumeValue.textContent = `${settings.volume} %`; + gain.disabled = !Number.isFinite(settings.mic_gain); + if (!gain.disabled) gain.value = settings.mic_gain; + gainValue.textContent = gain.disabled ? 'N/D' : `${settings.mic_gain} %`; + speaker.setAttribute('aria-pressed', String(settings.speaker_muted)); + speaker.setAttribute('aria-label', settings.speaker_muted ? 'Réactiver le son' : 'Couper le son'); + speaker.title = speaker.getAttribute('aria-label'); + mic.setAttribute('aria-pressed', String(settings.mic_muted)); + mic.textContent = settings.mic_muted ? 'Coupé' : 'Actif'; + mic.title = settings.mic_muted ? 'Réactiver le microphone' : 'Couper le microphone'; + } + + async function update(changes) { + message.textContent = ''; + try { + const response = await fetch('/api/audio/settings', { + method:'POST', + headers:{'Content-Type':'application/json'}, + body:JSON.stringify(changes) + }); + const data = await response.json(); + if (!response.ok) throw new Error(data.error || `HTTP ${response.status}`); + settings = { ...settings, ...data }; + render(); + } catch (err) { + message.textContent = `Impossible de modifier le réglage : ${err.message}`; + console.warn('[ALFRED AUDIO]', err); + await refresh(); + } + } + + async function refresh() { + try { + const response = await fetch('/api/audio/settings', {cache:'no-store'}); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + settings = { ...settings, ...await response.json() }; + render(); + } catch (err) { + message.textContent = 'Réglages indisponibles.'; + console.warn('[ALFRED AUDIO]', err); + } + } + + function setOpen(open) { + panel.hidden = !open; + button.setAttribute('aria-expanded', String(open)); + if (open) { refresh(); close.focus(); } else { button.focus(); } + } + button.addEventListener('click', () => setOpen(panel.hidden)); + close.addEventListener('click', () => setOpen(false)); + document.addEventListener('pointerdown', e => { + if (!panel.hidden && !panel.contains(e.target) && !button.contains(e.target)) { + panel.hidden = true; button.setAttribute('aria-expanded', 'false'); + } + }); + document.addEventListener('keydown', e => { if (e.key === 'Escape' && !panel.hidden) setOpen(false); }); + + volume.addEventListener('input', () => { + settings.volume = Number(volume.value); render(); + clearTimeout(volumeTimer); + volumeTimer = setTimeout(() => update({volume:Number(volume.value)}), 220); + }); + gain.addEventListener('input', () => { + settings.mic_gain = Number(gain.value); render(); + clearTimeout(gainTimer); + gainTimer = setTimeout(() => update({mic_gain:Number(gain.value)}), 250); + }); + speaker.addEventListener('click', () => update({speaker_muted:!settings.speaker_muted})); + mic.addEventListener('click', () => update({mic_muted:!settings.mic_muted})); + + if (window.alfredSocket) window.alfredSocket.on('audio_settings', data => { + settings = {...settings, ...data}; render(); + }); + render(); refresh(); +})(); diff --git a/alfred/app.backup.calendar/static/js/alfred-mode-icons.js b/alfred/app.backup.calendar/static/js/alfred-mode-icons.js new file mode 100644 index 0000000..9a36c90 --- /dev/null +++ b/alfred/app.backup.calendar/static/js/alfred-mode-icons.js @@ -0,0 +1,35 @@ +/* Icônes de statut : compatible avec alfred.js existant, sans le remplacer. */ +(() => { + 'use strict'; + const el = document.getElementById('mode'); + if (!el) return; + const icons = { + idle: ['🛏️', 'Repos'], repos: ['🛏️', 'Repos'], + listening: ['👂', 'Écoute'], thinking: ['🧠', 'Réflexion'], + speaking: ['🔊', 'Parole'], error: ['⚠️', 'Erreur'] + }; + let current = 'idle'; + function apply(raw) { + const key = String(raw || '').trim().toLowerCase(); + if (!icons[key]) return; + current = key; + const [icon, label] = icons[key]; + if (el.textContent !== icon) el.textContent = icon; + el.title = label; + el.setAttribute('aria-label', label); + el.dataset.mode = key; + } + // L'ancien alfred.js écrit le nom du mode dans #mode. + // L'observateur le convertit immédiatement en icône, sans modifier ce script. + const observer = new MutationObserver(() => { + const value = el.textContent.trim(); + if (icons[value.toLowerCase()]) apply(value); + }); + observer.observe(el, { childList: true, characterData: true, subtree: true }); + apply(el.textContent); + // Récupère également l'état actuel après un rafraîchissement de page. + fetch('/api/state', { cache: 'no-store' }) + .then(r => { if (!r.ok) throw new Error('État indisponible'); return r.json(); }) + .then(data => { if (data && data.mode) apply(data.mode); }) + .catch(() => {}); +})(); diff --git a/alfred/app.backup.calendar/static/js/alfred.js b/alfred/app.backup.calendar/static/js/alfred.js new file mode 100644 index 0000000..67111dd --- /dev/null +++ b/alfred/app.backup.calendar/static/js/alfred.js @@ -0,0 +1,121 @@ +/* Interface principale Alfred : statut, scène, onde et transcription Vosk. */ +(() => { + 'use strict'; + const socket = (typeof io === 'function') ? io() : null; + window.alfredSocket = socket; + + const content = document.getElementById('content'); + const modeEl = document.getElementById('mode'); + const statusDot = document.getElementById('statusDot'); + const transcriptText = document.getElementById('transcriptText'); + const transcriptState = document.getElementById('transcriptState'); + const transcriptPanel = document.querySelector('.transcript-panel'); + const canvas = document.getElementById('waveCanvas'); + const ctx = canvas.getContext('2d'); + + let mode = 'idle', level = 0.02, phase = 0, previousContent = ''; + const labels = { + idle:'En attente', waiting:'En attente', listening:'À l’écoute', + thinking:'Réflexion en cours', speaking:'Alfred parle', error:'Erreur' + }; + + function resize() { + const dpr = window.devicePixelRatio || 1; + canvas.width = Math.round(canvas.clientWidth * dpr); + canvas.height = Math.round(canvas.clientHeight * dpr); + } + window.addEventListener('resize', resize); + resize(); + function draw() { + const dpr = window.devicePixelRatio || 1; + phase += 0.035; + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.strokeStyle = 'rgba(232,241,243,.76)'; + ctx.lineWidth = 1.6 * dpr; + ctx.beginPath(); + const mid = canvas.height / 2; + const amp = (mode === 'idle' ? 1.5 : mode === 'thinking' ? 7 : Math.max(5, level * 45)) * dpr; + for (let x = 0; x < canvas.width; x += 4 * dpr) { + const y = mid + Math.sin(x * 0.018 + phase * 4) * Math.sin(x * 0.006 + phase) * amp * + (0.35 + 0.65 * Math.sin(x / canvas.width * Math.PI)); + if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); + } + ctx.stroke(); + window.requestAnimationFrame(draw); + } + window.requestAnimationFrame(draw); + + function makeText(tag, value, className) { + const el = document.createElement(tag); + if (className) el.className = className; + el.textContent = String(value ?? ''); + return el; + } + + function render(data) { + if (!data || typeof data !== 'object') return; + const key = JSON.stringify(data); + if (key === previousContent) return; + previousContent = key; + const fragment = document.createDocumentFragment(); + if (data.type === 'image' && typeof data.url === 'string') { + // Refuser les URL javascript: et les schémas non prévus. + if (/^(https?:\/\/|\/[^/])/i.test(data.url)) { + const img = document.createElement('img'); + img.className = 'hero'; img.src = data.url; img.alt = String(data.alt || 'Image affichée par Alfred'); + fragment.appendChild(img); + } + if (data.caption) fragment.appendChild(makeText('p', data.caption)); + } else if (data.type === 'cards' && Array.isArray(data.items)) { + const cards = document.createElement('div'); cards.className = 'cards'; + data.items.forEach(item => { + const card = makeText('article', '', 'card'); + card.append(makeText('h2', item.title || ''), makeText('p', item.text || '')); + cards.appendChild(card); + }); + fragment.appendChild(cards); + } else { + if (data.title) fragment.appendChild(makeText('h1', data.title)); + fragment.appendChild(makeText('p', data.text || '')); + } + content.replaceChildren(fragment); + } + + function updateTranscript(s) { + // Une phrase finale reste visible lors du retour en veille. + const finalText = String(s.transcript || '').trim(); + const partial = String(s.partial || '').trim(); + if (finalText) { + transcriptText.textContent = finalText; + transcriptText.classList.remove('is-placeholder'); + transcriptState.textContent = 'Reconnu'; + } else if (s.mode === 'listening') { + transcriptText.textContent = partial || 'Je vous écoute…'; + transcriptText.classList.toggle('is-placeholder', !partial); + transcriptState.textContent = partial ? 'En cours' : 'Écoute'; + } else if (s.mode === 'error') { + transcriptState.textContent = 'Erreur'; + } + transcriptPanel.dataset.active = String(s.mode === 'listening'); + } + + function updateState(s) { + if (!s || typeof s !== 'object') return; + mode = String(s.mode || 'idle').toLowerCase(); + modeEl.textContent = labels[mode] || mode; + modeEl.dataset.mode = mode; + statusDot.dataset.mode = mode; + updateTranscript(s); + if (s.content) render(s.content); + } + + if (socket) { + socket.on('alfred_state', updateState); + socket.on('alfred_render', render); + socket.on('audio_level', data => { level = Math.max(0, Math.min(1, Number(data.level) || 0)); }); + socket.on('connect', () => { + fetch('/api/state', {cache:'no-store'}).then(r => r.json()).then(updateState).catch(() => {}); + }); + } + fetch('/api/state', {cache:'no-store'}).then(r => r.json()).then(updateState).catch(() => {}); +})(); diff --git a/alfred/app.backup.calendar/templates/configuration.html b/alfred/app.backup.calendar/templates/configuration.html new file mode 100644 index 0000000..6746c73 --- /dev/null +++ b/alfred/app.backup.calendar/templates/configuration.html @@ -0,0 +1,130 @@ + + + + + + Alfred — Configuration audio + + + +
+
+

Configuration

+

Alfred · Paramètres audio

+
+
+
+
+ + 50 % +
+ +
+
+
+ + + Couper le haut-parleur + + +
+

Désactive les réponses vocales d’Alfred.

+
+
+
+ + + Couper le microphone + + +
+

Suspend l’écoute et la détection du mot « Alfred ».

+
+
+

+
+ + + diff --git a/alfred/app.backup.calendar/templates/index.html b/alfred/app.backup.calendar/templates/index.html new file mode 100644 index 0000000..3b875e9 --- /dev/null +++ b/alfred/app.backup.calendar/templates/index.html @@ -0,0 +1,103 @@ + + + + + + + {{ name }} + + + + + +
+
+ +
+ + En attente +
+ +
+ + +
+
+ + + + +
+

Bonjour.

À votre écoute.

+
+ + + + + + + + +
+ + + + + + diff --git a/alfred/app.backup.calendar/templates/index.html.1 b/alfred/app.backup.calendar/templates/index.html.1 new file mode 100644 index 0000000..8b35c18 --- /dev/null +++ b/alfred/app.backup.calendar/templates/index.html.1 @@ -0,0 +1,31 @@ + + + + + + {{name}} + + + + + +
+
{{name}}repos
+

Bonjour.

À votre écoute.

+
+
+ + + + 70 % + + + + — + +
+
+ + + + diff --git a/alfred/app.backup.calendar/templates/index.html.ori b/alfred/app.backup.calendar/templates/index.html.ori new file mode 100644 index 0000000..d0b38fd --- /dev/null +++ b/alfred/app.backup.calendar/templates/index.html.ori @@ -0,0 +1,2 @@ +{{name}} +
{{name}}repos

Bonjour.

À votre écoute.

diff --git a/alfred/app.backup.calendar/test.py b/alfred/app.backup.calendar/test.py new file mode 100644 index 0000000..95385a2 --- /dev/null +++ b/alfred/app.backup.calendar/test.py @@ -0,0 +1,19 @@ +python - <<'PY' +import sounddevice as sd +import soundfile as sf + +print(sd.query_devices()) +print("Microphone utilisé :", sd.default.device) + +print("Parle pendant 8 secondes...") +audio = sd.rec( + int(8 * 16000), + samplerate=16000, + channels=1, + dtype="int16" +) +sd.wait() + +sf.write("/tmp/alfred-micro.wav", audio, 16000) +print("Enregistrement terminé : /tmp/alfred-micro.wav") +PY diff --git a/alfred/app.backup.camera/__pycache__/app.cpython-313.pyc b/alfred/app.backup.camera/__pycache__/app.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09d2b8b735370c3ad499b9f4f1f66ea5d451124b GIT binary patch literal 12049 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/snapshot') +def ha_camera_snapshot(entity_id): + try: + binary,mime=ha_views.camera_snapshot(entity_id) + return Response(binary,mimetype=mime,headers={'Cache-Control':'no-store','X-Content-Type-Options':'nosniff'}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.post('/api/ui/home') +def ui_home(): + with STATE_LOCK: + STATE['content']={'type':'home','text':'À ton écoute.'} + socketio.emit('alfred_render',STATE['content']) + 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") 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) + diff --git a/alfred/app.backup.camera/app.py.ori b/alfred/app.backup.camera/app.py.ori new file mode 100644 index 0000000..d44c3ca --- /dev/null +++ b/alfred/app.backup.camera/app.py.ori @@ -0,0 +1,180 @@ +import os, threading +from flask import Flask, render_template, request, jsonify, Response +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 +from services import audio_settings # ALFRED_AUDIO_SETTINGS_V1 +from services.camera import video_stream + +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() +mic_stop_event=threading.Event() +audio_settings.initialize() + +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="", 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 + ) + + print(f"[ALFRED] Résultat STT : {r.text!r}, erreur : {r.error!r}", flush=True) + + if audio_settings.mic_muted(): + emit_state("idle", partial="") + return + + 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 and not audio_settings.speaker_muted(): + 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() + if not audio_settings.mic_muted(): + wake.resume() + print("[ALFRED] Wake word réactivé", flush=True) + + + +def on_wake(score): + if audio_settings.mic_muted(): + return + 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('/api/audio/settings') +def get_audio_settings(): + return jsonify(audio_settings.get_settings()) + +@app.post('/api/audio/settings') +def set_audio_settings(): + data = request.get_json(silent=True) + try: + old = audio_settings.get_settings() + new = audio_settings.update_settings(data) + except ValueError as exc: + return jsonify({'error': str(exc)}), 400 + except Exception as exc: + return jsonify({'error': f'ALSA : {exc}'}), 500 + if new['mic_muted'] != old['mic_muted']: + if new['mic_muted']: + mic_stop_event.set() + wake.pause() + else: + mic_stop_event.clear() + if not conversation_lock.locked(): + wake.resume() + socketio.emit('audio_settings', new) + return jsonify(new) + +@app.get('/api/camera/stream') +def camera_stream(): + # Flux MJPEG à la demande ; ne démarre jamais la webcam en arrière-plan. + try: + frames = video_stream(index=int(os.getenv('CAMERA_INDEX', '0'))) + except (ImportError, ValueError, RuntimeError, OSError) as exc: + return jsonify({'error': str(exc)}), 503 + return Response( + frames, + mimetype='multipart/x-mixed-replace; boundary=frame', + headers={'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff'} + ) + +@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") 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) diff --git a/alfred/app.backup.camera/backup_audio_20261016_133326/app.py b/alfred/app.backup.camera/backup_audio_20261016_133326/app.py new file mode 100644 index 0000000..9a63c93 --- /dev/null +++ b/alfred/app.backup.camera/backup_audio_20261016_133326/app.py @@ -0,0 +1,130 @@ +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) diff --git a/alfred/app.backup.camera/backup_audio_20261016_133326/audio.py b/alfred/app.backup.camera/backup_audio_20261016_133326/audio.py new file mode 100644 index 0000000..b655c60 --- /dev/null +++ b/alfred/app.backup.camera/backup_audio_20261016_133326/audio.py @@ -0,0 +1,379 @@ +"""Audio local sounddevice.RawInputStreamd'Alfred, repris de l'application historique de Pierre. + +STT: Vosk + sounddevice, avec fallback arecord/ALSA. +TTS: Piper local, avec fallback espeak puis spd-say. +Sortie: haut-parleur local via paplay/aplay. + +Ce module ne parle pas directement au LLM/Hermes: il retourne le texte reconnu +à l'orchestrateur Alfred, afin de conserver Hermes comme cerveau unique. +""" +from __future__ import annotations +from functools import lru_cache + +import time +import json +import os +import queue +import shutil +import subprocess +import tempfile +import threading +from dataclasses import dataclass +from typing import Callable, Optional + + +def _cmd_ok(cmd, **kwargs): + try: + return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + check=True, text=True, **kwargs) + except Exception: + return None + + +def audio_backend_detect() -> str: + configured = os.getenv("AUDIO_BACKEND", "auto").lower() + if configured in ("pactl", "alsa"): + return configured + if shutil.which("pactl"): + info = _cmd_ok(["pactl", "info"]) + if info and "Server Name" in info.stdout: + return "pactl" + if shutil.which("amixer"): + return "alsa" + return "none" + + +def _which(cmd): + return shutil.which(cmd) + + +def _is_piper_tts(binary: Optional[str]) -> bool: + if not binary: + return False + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + if "gtk" in out or "ratbag" in out: + return False + return (("--model" in out or " -m " in out) and + ("--output_file" in out or " -o " in out)) + except Exception: + return False + + +def _find_piper_binary() -> Optional[str]: + candidates = [ + os.getenv("PIPER_BIN"), + "/usr/local/bin/piper-tts", "/usr/bin/piper-tts", _which("piper-tts"), + "/usr/local/bin/piper", "/usr/bin/piper", _which("piper"), + ] + for candidate in candidates: + if candidate and _is_piper_tts(candidate): + return candidate + return None + + +def _piper_supports_long_flag(binary: str) -> bool: + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + return "--model" in out and "--output_file" in out + except Exception: + return True + +### +def tts_backend_detect() -> str: + """Retourne le moteur TTS configuré pour Alfred.""" + + if os.getenv("PIPER_HOST"): + return "wyoming-piper" + + if _which("espeak-ng"): + return "espeak-ng" + + return "none" + + +''' +def tts_backend_detect() -> str: + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + if piper and model and os.path.exists(model): + return "piper" + if _which("espeak"): + return "espeak" + if _which("spd-say"): + return "spd-say" + return "none" +''' +#### + +def speak_text(text: str): + """Synthèse vocale via Wyoming Piper, avec secours eSpeak.""" + if not text or not text.strip(): + return + + text = text[:int(os.getenv("TTS_MAX_CHARS", "2000"))] + + try: + from services.piper_tts import speak as piper_speak + + print("[ALFRED TTS] Utilisation de Wyoming Piper", flush=True) + piper_speak(text) + return + + except Exception as exc: + print(f"[ALFRED TTS] Piper indisponible : {exc}", flush=True) + + # Secours eSpeak + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + print("[ALFRED TTS] Secours eSpeak", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + except Exception as exc: + print(f"[ALFRED TTS] Erreur eSpeak : {exc}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + +''' +def speak_text(text: str): + if not text or not text.strip(): + return + + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + start = time.monotonic() + print("[ALFRED TTS] Génération audio...", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + print("[ALFRED TTS] Lecture audio...", flush=True) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + print( + f"[ALFRED TTS] Terminé en {time.monotonic() - start:.2f}s", + flush=True + ) + + except Exception as e: + print(f"[ALFRED TTS] Erreur : {e}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + + + + +def speak_text(text: str): + """Synthétise et joue localement le texte. Retourne (ok, backend, erreur).""" + text = (text or "").strip() + if not text: + return False, "none", "texte vide" + text = text[: int(os.getenv("TTS_MAX_CHARS", "2000"))] + + backend = audio_backend_detect() + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + + if piper and model and os.path.exists(model): + wav_path = None + try: + with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp: + wav_path = tmp.name + cmd = [piper] + if _piper_supports_long_flag(piper): + cmd += ["--model", model, "--output_file", wav_path] + else: + cmd += ["-m", model, "-o", wav_path] + p = subprocess.run(cmd, input=text, text=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False) + if p.returncode != 0: + return False, "piper", (p.stderr or p.stdout or "")[:2000] + if not os.path.exists(wav_path) or os.path.getsize(wav_path) == 0: + return False, "piper", "Piper n'a pas produit de WAV" + if backend == "pactl" and shutil.which("paplay"): + play = subprocess.run(["paplay", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + elif shutil.which("aplay"): + play = subprocess.run(["aplay", "-q", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + else: + return False, "piper", "paplay/aplay introuvable" + return play.returncode == 0, "piper", None if play.returncode == 0 else "lecture WAV échouée" + except Exception as exc: + return False, "piper", str(exc) + finally: + if wav_path: + try: os.unlink(wav_path) + except OSError: pass + + if _which("espeak"): + args = ["espeak", "-v", os.getenv("ESPEAK_VOICE", "fr"), text] + if backend == "pactl" and shutil.which("padsp"): + args = ["padsp"] + args + ok = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode == 0 + return ok, "espeak", None if ok else "espeak a échoué" + + if _which("spd-say"): + ok = subprocess.run(["spd-say", "-l", "fr", text], stdout=subprocess.PIPE, + stderr=subprocess.PIPE).returncode == 0 + return ok, "spd-say", None if ok else "spd-say a échoué" + return False, "none", "aucun backend TTS disponible" +''' + +_tts_lock = threading.Lock() +def speak_async(text: str, on_start: Optional[Callable] = None, + on_end: Optional[Callable] = None) -> bool: + """Lecture non bloquante; refuse d'empiler deux réponses vocales.""" + if _tts_lock.locked(): + return False + def worker(): + with _tts_lock: + if on_start: on_start() + try: speak_text(text) + finally: + if on_end: on_end() + threading.Thread(target=worker, daemon=True).start() + return True + + +@dataclass +class STTResult: + text: str = "" + error: Optional[str] = None + + + +@lru_cache(maxsize=2) +def get_vosk_model(model_path): + from vosk import Model + + print(f"[ALFRED] Chargement du modèle Vosk : {model_path}", flush=True) + model = Model(model_path) + print("[ALFRED] Modèle Vosk prêt", flush=True) + + return model + + +def listen_once(stop_event: Optional[threading.Event] = None, + on_partial: Optional[Callable[[str], None]] = None, + on_level: Optional[Callable[[float], None]] = None) -> STTResult: + """Attend une phrase finale Vosk et la retourne. + + Reprend le comportement du prototype: sounddevice en priorité, arecord sinon. + Le niveau audio transmis à l'UI est une approximation RMS sur int16. + """ + try: + import numpy as np + from vosk import Model, KaldiRecognizer + except Exception as exc: + return STTResult(error=f"Dépendance STT absente: {exc}") + + model_path = os.getenv("VOSK_MODEL_PATH", "") + sample_rate = int(os.getenv("VOSK_SAMPLE_RATE", "16000")) + if not model_path or not os.path.exists(model_path): + return STTResult(error=f"Modèle Vosk introuvable: {model_path}") + try: +# rec = KaldiRecognizer(Model(model_path), sample_rate) + rec = KaldiRecognizer(get_vosk_model(model_path), sample_rate) + except Exception as exc: + return STTResult(error=f"Init Vosk: {exc}") + + def process(data: bytes) -> Optional[str]: + if on_level and data: + try: + a = np.frombuffer(data, dtype=np.int16).astype(np.float32) + rms = float(np.sqrt(np.mean(a * a))) if a.size else 0.0 + on_level(min(1.0, rms / 6000.0)) + except Exception: + pass + if rec.AcceptWaveform(data): + text = (json.loads(rec.Result() or "{}").get("text") or "").strip() + if text: + return text + elif on_partial: + partial = json.loads(rec.PartialResult() or "{}").get("partial", "") + print(f"[ALFRED STT] Reconnu partiellement : {partial}", flush=True) + on_partial(partial) + return None + + try: + import sounddevice as sd + q = queue.Queue() + def callback(indata, frames, time_info, status): + q.put(bytes(indata)) + device = os.getenv("AUDIO_INPUT_DEVICE") or None + with sd.RawInputStream(samplerate=sample_rate, blocksize=8000, dtype="int16", + channels=1, callback=callback, device=device): + while not (stop_event and stop_event.is_set()): + text = process(q.get()) + if text: + return STTResult(text=text) + except Exception as exc: + print(f"[ALFRED STT] Erreur microphone sounddevice : {exc}", flush=True) + +# except Exception: +# pass + + if not shutil.which("arecord"): + return STTResult(error="sounddevice indisponible et arecord introuvable") + cmd = ["arecord", "-q", "-f", "S16_LE", "-r", str(sample_rate), "-c", "1"] + alsa_device = os.getenv("ALSA_CAPTURE_DEVICE", "").strip() + if alsa_device: + cmd += ["-D", alsa_device] + proc = None + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) + while not (stop_event and stop_event.is_set()): + chunk = proc.stdout.read(8000) + if not chunk: + continue + text = process(chunk) + if text: + return STTResult(text=text) + return STTResult() + except Exception as exc: + return STTResult(error=f"arecord: {exc}") + finally: + if proc and proc.poll() is None: + proc.terminate() + + +def devices(): + try: +# import sounddevice as sd +# return sd.query_devices() + print("[ALFRED STT] Périphériques audio :", flush=True) + print(sd.query_devices(), flush=True) + print("[ALFRED STT] Périphérique par défaut :", sd.default.device, flush=True) + return sd.query_devices() + except Exception: + return [] diff --git a/alfred/app.backup.camera/backup_micro_gain_20261016_134053/alfred-controls.css b/alfred/app.backup.camera/backup_micro_gain_20261016_134053/alfred-controls.css new file mode 100644 index 0000000..ff91533 --- /dev/null +++ b/alfred/app.backup.camera/backup_micro_gain_20261016_134053/alfred-controls.css @@ -0,0 +1,19 @@ +/* Contrôles indépendants de la feuille alfred.css existante */ +body > main { position: relative; min-height: 100dvh; padding-bottom: 82px; box-sizing: border-box; } +.alfred-controls { + position: absolute; left: 16px; right: 16px; bottom: 12px; + display: flex; align-items: center; gap: 14px; min-height: 56px; + padding: 8px 18px; box-sizing: border-box; + border: 1px solid rgba(255,255,255,.10); border-radius: 16px; + background: rgba(35,37,41,.92); color: #f4f4f5; + box-shadow: 0 8px 28px rgba(0,0,0,.14); + font: 14px/1.3 system-ui, sans-serif; +} +.alfred-controls .audio-toggle { flex: 0 0 auto; width: 42px; height: 42px; padding: 0; border: 1px solid transparent; border-radius: 12px; background: transparent; color: inherit; font-size: 23px; cursor: pointer; } +.alfred-controls .audio-toggle:hover, .alfred-controls .audio-toggle:focus-visible { background: rgba(255,255,255,.10); outline-offset: 2px; } +.alfred-controls .audio-toggle[aria-pressed="true"] { background: rgba(225,100,100,.14); border-color: rgba(225,100,100,.28); } +.alfred-controls .volume-label { white-space: nowrap; color: #c8c8ce; } +.alfred-controls input[type="range"] { min-width: 60px; flex: 1; accent-color: #d1d5db; cursor: pointer; } +.alfred-controls output { min-width: 44px; text-align: right; font-variant-numeric: tabular-nums; } +.alfred-controls .controls-separator { height: 25px; width: 1px; background: rgba(255,255,255,.16); } +@media (max-width: 480px) { .alfred-controls { gap: 8px; padding: 6px 9px; left: 8px; right: 8px; } .alfred-controls .volume-label { display: none; } } diff --git a/alfred/app.backup.camera/backup_micro_gain_20261016_134053/alfred-controls.js b/alfred/app.backup.camera/backup_micro_gain_20261016_134053/alfred-controls.js new file mode 100644 index 0000000..142bd55 --- /dev/null +++ b/alfred/app.backup.camera/backup_micro_gain_20261016_134053/alfred-controls.js @@ -0,0 +1,73 @@ +(() => { + 'use strict'; + const slider = document.getElementById('volumeSlider'); + const volumeValue = document.getElementById('volumeValue'); + const speaker = document.getElementById('speakerMute'); + const mic = document.getElementById('micMute'); + const endpoint = '/api/audio/settings'; + let state = { volume: 70, speaker_muted: false, mic_muted: false }; + let saveTimer; + + function render() { + slider.value = String(state.volume); + volumeValue.value = `${state.volume} %`; + speaker.setAttribute('aria-pressed', String(state.speaker_muted)); + speaker.setAttribute('aria-label', state.speaker_muted ? 'Réactiver le haut-parleur' : 'Couper le haut-parleur'); + speaker.title = speaker.getAttribute('aria-label'); + speaker.textContent = state.speaker_muted ? '🔇' : '🔊'; + mic.setAttribute('aria-pressed', String(state.mic_muted)); + mic.setAttribute('aria-label', state.mic_muted ? 'Réactiver le microphone' : 'Couper le microphone'); + mic.title = mic.getAttribute('aria-label'); + mic.textContent = state.mic_muted ? '🎙️̸' : '🎙️'; + } + + async function save() { + try { + const response = await fetch(endpoint, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(state) + }); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const result = await response.json(); + if (result && typeof result.volume === 'number') { + state = { ...state, ...result }; + render(); + } + } catch (error) { + console.warn('Réglages audio non synchronisés avec Flask :', error); + } + } + + slider.addEventListener('input', () => { + state.volume = Number(slider.value); + render(); + clearTimeout(saveTimer); + saveTimer = setTimeout(save, 200); + }); + speaker.addEventListener('click', () => { + state.speaker_muted = !state.speaker_muted; + render(); + clearTimeout(saveTimer); + save(); + }); + mic.addEventListener('click', () => { + state.mic_muted = !state.mic_muted; + render(); + clearTimeout(saveTimer); + save(); + }); + + (async () => { + try { + const response = await fetch(endpoint); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const settings = await response.json(); + if (typeof settings.volume === 'number') state.volume = Math.max(0, Math.min(100, settings.volume)); + if (typeof settings.speaker_muted === 'boolean') state.speaker_muted = settings.speaker_muted; + if (typeof settings.mic_muted === 'boolean') state.mic_muted = settings.mic_muted; + } catch (error) { + console.warn('Réglages audio non disponibles :', error); + } + render(); + })(); +})(); diff --git a/alfred/app.backup.camera/backup_micro_gain_20261016_134053/audio_settings.py b/alfred/app.backup.camera/backup_micro_gain_20261016_134053/audio_settings.py new file mode 100644 index 0000000..20243b7 --- /dev/null +++ b/alfred/app.backup.camera/backup_micro_gain_20261016_134053/audio_settings.py @@ -0,0 +1,62 @@ +"""Réglages audio Alfred : ALSA carte 1 et états logiques thread-safe.""" +import os +import re +import subprocess +import threading + +_LOCK = threading.RLock() +_STATE = {'volume': 70, 'speaker_muted': False, 'mic_muted': False} +_CARD = os.getenv('ALFRED_ALSA_CARD', '1') +_CONTROL = os.getenv('ALFRED_ALSA_VOLUME_CONTROL', 'Master') + +def _amixer(*args): + return subprocess.run(['amixer', '-c', _CARD, *args], capture_output=True, + text=True, timeout=5, check=True).stdout + +def initialize(): + """Lit le volume existant sans modifier les réglages audio au démarrage.""" + try: + output = _amixer('sget', _CONTROL) + matches = re.findall(r'\[(\d+)%\]', output) + with _LOCK: + if matches: + _STATE['volume'] = int(matches[0]) + # Ne pas déduire speaker_muted du mixer : état propre à Alfred. + except (OSError, subprocess.SubprocessError) as exc: + print(f'[ALFRED AUDIO] Lecture ALSA impossible : {exc}', flush=True) + +def get_settings(): + with _LOCK: + return dict(_STATE) + +def update_settings(payload): + if not isinstance(payload, dict): + raise ValueError('Un objet JSON est attendu') + allowed = {'volume', 'speaker_muted', 'mic_muted'} + if set(payload) - allowed: + raise ValueError('Paramètre audio inconnu') + with _LOCK: + new = dict(_STATE) + if 'volume' in payload: + v = payload['volume'] + if isinstance(v, bool) or not isinstance(v, (int, float)) or not 0 <= v <= 100: + raise ValueError('volume doit être entre 0 et 100') + new['volume'] = round(v) + for key in ('speaker_muted', 'mic_muted'): + if key in payload: + if not isinstance(payload[key], bool): + raise ValueError(f'{key} doit être un booléen') + new[key] = payload[key] + # Ne touche jamais aux contrôles Mic/Capture : calibration conservée. + if new['volume'] != _STATE['volume']: + _amixer('sset', _CONTROL, f"{new['volume']}%") + _STATE.update(new) + return dict(_STATE) + +def speaker_muted(): + with _LOCK: + return _STATE['speaker_muted'] + +def mic_muted(): + with _LOCK: + return _STATE['mic_muted'] diff --git a/alfred/app.backup.camera/backup_micro_gain_20261016_134053/index.html b/alfred/app.backup.camera/backup_micro_gain_20261016_134053/index.html new file mode 100644 index 0000000..4762a5b --- /dev/null +++ b/alfred/app.backup.camera/backup_micro_gain_20261016_134053/index.html @@ -0,0 +1,28 @@ + + + + + + {{name}} + + + + + +
+
{{name}}repos
+

Bonjour.

À votre écoute.

+
+
+ + + + 70 % + + +
+
+ + + + diff --git a/alfred/app.backup.camera/config/hermes_session.json b/alfred/app.backup.camera/config/hermes_session.json new file mode 100644 index 0000000..de88db4 --- /dev/null +++ b/alfred/app.backup.camera/config/hermes_session.json @@ -0,0 +1,3 @@ +{ + "session_id": "local-65f11130ed816902a867736ef80b3af0ea255048cde912fb31b26267c8d1d599" +} diff --git a/alfred/app.backup.camera/config/hermes_session.json.bak.20261024_151328 b/alfred/app.backup.camera/config/hermes_session.json.bak.20261024_151328 new file mode 100644 index 0000000..4525415 --- /dev/null +++ b/alfred/app.backup.camera/config/hermes_session.json.bak.20261024_151328 @@ -0,0 +1,3 @@ +{ + "session_id": "local-f2399cb2bcf9e91094d3dc34492b463eda9cf42d09857d51b5954c5982aaaa5e" +} diff --git a/alfred/app.backup.camera/config/hermes_session.json.lock b/alfred/app.backup.camera/config/hermes_session.json.lock new file mode 100644 index 0000000..e69de29 diff --git a/alfred/app.backup.camera/config/identity.md b/alfred/app.backup.camera/config/identity.md new file mode 100644 index 0000000..2ac6084 --- /dev/null +++ b/alfred/app.backup.camera/config/identity.md @@ -0,0 +1,31 @@ +# Identité et comportement d'Alfred + +Tu es Alfred, le majordome vocal personnel de la maison. +Tu t'appelles Alfred, même si le moteur logiciel qui te permet de raisonner est Hermes Agent. + + +## Langue et style +- Réponds toujours en français, sauf demande explicite de parler une autre langue. +- Tutoie ton interlocuteur. +- Adopte un ton naturel, calme, cordial et professionnel. +- Privilégie les réponses brèves, claires et faciles à prononcer à voix haute. +- Évite le Markdown, les tableaux et les longues listes lorsque tu réponds oralement. +- Ne parle pas spontanément de Hermes, d'Ollama ou de ton architecture interne. + +## Domotique +- Lorsque la demande nécessite une action domotique, utilise les outils Home Assistant disponibles. +- Ne confirme une action que si son exécution a été effectivement vérifiée. +- Si tu ne peux pas effectuer une action, explique-le simplement. + +## Identité +- Si l'on te demande ton nom, réponds : « Je m'appelle Alfred. » + + +# Alfred +Tu disposes des outils et compétences configurés +dans Hermes Agent. + +Utilise librement les outils disponibles lorsque +la demande le nécessite. + +Ne limite pas tes capacités à la domotique. diff --git a/alfred/app.backup.camera/config/identity.md.bak.20261016_140326 b/alfred/app.backup.camera/config/identity.md.bak.20261016_140326 new file mode 100644 index 0000000..ed48720 --- /dev/null +++ b/alfred/app.backup.camera/config/identity.md.bak.20261016_140326 @@ -0,0 +1,26 @@ +# Identité d'Alfred + +Tu es Alfred, un majordome personnel intelligent. + +Tu fonctionnes dans une maison équipée de Home Assistant. +Tu utilises Hermes Agent comme moteur de raisonnement. + +## Comportement + +- Ton nom est Alfred. +- Tu réponds toujours en français, sauf demande explicite contraire. +- Tu t'adresses à ton utilisateur en le tutoyant. +- Tu adoptes un ton naturel, calme, chaleureux et professionnel. +- Tu privilégies les réponses courtes, adaptées à une conversation vocale. +- Tu évites les introductions inutiles et les formulations trop techniques. +- Tu ne mentionnes pas Hermes, Ollama ou ton fonctionnement interne sauf si on te le demande. + +## Domotique + +- Tu peux utiliser les outils Home Assistant disponibles. +- Lorsque tu exécutes une commande, confirme brièvement le résultat. +- Ne prétends jamais avoir exécuté une action si l'outil a échoué. + +## Identité + +Si on te demande ton nom, tu réponds : « Je m'appelle Alfred. » diff --git a/alfred/app.backup.camera/config/identity.md.ori2 b/alfred/app.backup.camera/config/identity.md.ori2 new file mode 100644 index 0000000..52603ea --- /dev/null +++ b/alfred/app.backup.camera/config/identity.md.ori2 @@ -0,0 +1,74 @@ +# Identité et comportement d'Alfred + +Tu es Alfred, un majordome numérique personnel, +intelligent, polyvalent et connecté. + +Tu n'es pas limité à la domotique. +Tu es un assistant capable de répondre à des questions, +de rechercher des informations, de générer des images, +d'analyser des documents et d'interagir avec la maison. + +## Langue et personnalité + +- Réponds toujours en français. +- Tutoie ton interlocuteur. +- Adopte un ton calme, cordial et professionnel. +- Privilégie les réponses courtes adaptées à la voix. +- Ne mentionne pas ton architecture interne sans raison. + +## Capacités et outils + +Tu disposes de plusieurs outils MCP. + +### Home Assistant +- Contrôler les équipements connectés. +- Consulter les capteurs et les états. +- Exécuter les commandes demandées. + +### ComfyUI +- Générer des images à partir de descriptions. +- Analyser des images avec les outils disponibles. +- Lorsqu'une génération est demandée, recherche et + utilise l'outil MCP generate_image. +- L'outil ComfyUI peut transmettre directement + l'image générée à l'interface graphique d'Alfred. +- Ne prétends pas être incapable de générer une image + sans avoir vérifié la disponibilité de cet outil. + +### Autres capacités +- Répondre aux questions générales. +- Utiliser les outils de recherche disponibles. +- Reconnaître des personnes avec le MCP facial, + uniquement lorsque son utilisation est appropriée. +- Utiliser les autres MCP selon les demandes. + +## Règles générales + +- Choisis l'outil approprié selon la demande. +- Ne limite jamais tes capacités à la seule domotique. +- Ne prétends jamais avoir exécuté une action + si celle-ci n'a pas abouti. +- Si un outil est indisponible, explique le problème. +- Ne fabrique jamais de résultats d'outils. + +## Identité + +Si on te demande ton nom, réponds : +"Je m'appelle Alfred." + +## Génération d'images + +Tu peux générer des images avec le MCP ComfyUI. + +Lorsqu'un utilisateur demande une image : +1. Recherche l'outil generate_image du MCP ComfyUI. +2. Si Tool Search est actif, utilise tool_search pour + découvrir l'outil et tool_describe si nécessaire. +3. Appelle l'outil de génération avec la description. +4. Attends son résultat avant de confirmer la génération. + +Le MCP ComfyUI transmet automatiquement les images +générées à l'interface graphique d'Alfred. + +Ne prétends pas que tu es limité à la domotique. +Si l'outil n'est pas accessible, indique l'erreur réelle. diff --git a/alfred/app.backup.camera/services/__pycache__/alfred_intents.cpython-313.pyc b/alfred/app.backup.camera/services/__pycache__/alfred_intents.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..edc50dbf8a20d98171a8dcbddaaeccd799f9c0e2 GIT binary patch literal 16335 zcmdseTTmQVmSASRvK~-SfP_GRiiaMBB|vY19t;*(=!L9GN??IZCshd)DzZ>!6_4s_ zi)c@Tk>nnsY4^A^)`sv}Jw{{C8qavws9Uj?Hzs!Jh}q~&m3N@C8EtP&|LCtRbwo$^ z$L^k!S(#NvV5E-i-H5#sH#6_!+;h)8_uliWr`g$d0@53M9}WJhiXi?KKeS|3U&yDo zNP_qgK@&VdleFO^dBVUO-1u%hY2=N{)5M#UrFHhZ^aj-b9*5ZXxmIvS~vTVvqY>u-g$tC-+7(3|MIVq%cwi|Bm#E{5*{ z<$EK&4!%q1^^lj+g^-ug8zA3A7eVf(iy<$kH$q-Pmq1=gmqK1emqEU{-zdNes5ti# z9(SzmXqXdNeuRkxM_BhOJj?k9+#}(jpLO%$XoTf!+->X#%e(udtRS$0JHSLB?+f$p z04qfJs9)*Gzyko)JjN+{NZG+~TsQ(ip|H(kl1p^No= zm}8G~ePKDL>C`LUmgb)J)pL2$sL6T^vU!9F zMOm3*$A*JEEBI5~2#^T^cMA+jJ{^M>*e#?tvld%Gq7B7FSBjSj!i(4~OTg7eAK?Wa zU7j>?TfcRyR*Sx!>4F0)Y&txi)w>qFm4)4zR!{jBND-xAcek zajdVYlS#jTgSp*ub!~V!Qp<$;fZ(;v@Ng}1FtDtUD(eYy5tfSxHN)ew?NC3<0p0l{ z@#dZq~=d1R>@HAC?_Uxc=$j!pWY8Cws*2Fv*j> zVqiczIq>ez8#|%$_X4il_se#WcWgUI+1U0ulvC^iqS5^x|mClEz$|u}irku-cootPy+y!;(IO#*`Y447BtayK=Plw1GC(lRMzE zidYk#={ew;UjUwE1$aPfpYb_xq0)Uhlh35j@5cnum8;FpXY%H2@98IP&}fVY5NGZ} z=2X%u?af+y?HLZN2-m9B>(~HCtO;H3rS(4J@EN`9HJs84x`j6RIRiMKC5?wvasLCpcE*zJY|fI>!k~7UbqdGC+*~b zTc$H5YW?+^d*1G|A!pEb%+sr_a}?e@IG+!^c;YTg+K4;gq@o==;Hu|9fb$s+LOaF^ z@Emk@1K`2{6xzW7-=|L|s7swpC{k4^!eBq)akj1)h@FRnt^CA4${{U2u~cmfC!_28VUxKav>b#u{Z)k zOf(pY2N+IphZr|@=2#ZSicJEv#)c5vExhDu+}0iV?#}JIzUP4=xP%&^a9c`%il+pq zI0{g4MSz0WU>pS~%us-eqW~4x1*mvRfQoAZR9q9F5I(}*RR;sYa4=FYkH>y&--_sT z)$X{EiJS7B**RwD?tP5+j%*#~!~OvvB##%J$Avc=Ase~ysBCCIuUwrxURtt&XJw1P zGQ59)N3l(|oIcam-b%~neh_AdWh2N1vS}O|fE*X*A_KA!)ObGBwJw z7opUXC6fYQ2T*wACD|MaV_R~FKY(TRnC-_3o1X!}P?2xR0Dnknt4Ov2&fcAo)H~lc z4Af?O02qH18sJV6Tre@X^zGRT_VRZ&yj${@CAWH$uJVbaOOE`hrfUZ$nisQjuD4!k zy>?=vWr?!AHU9l^(Y0kdvOsN@sO_^Y_dWA2so|uw=cKsfWU9ebX#(o2B&uqr`10@J3<6^~2(~duGj_+ds8Wnv{`l9i1lCh9B79JM;0;S@Lu0Q)-U<2m427l6i-| z6^HTva{0^4V#jwDIs#HhAbF705n5mT?%<8VzY1NkPm)U@tJPnvpYkPk0%?3VE==!P zaBNFDwk_rt-#&cv@OzC?e)UX~l(%EDWih8H(VomHpJrz+eK0sj&Td}J-*BsF#w-=p zNclAnvueJ!5rvh%u@hOjJc@$fkjatn;Z7x=Hj^mZbwRGC45^G(I!i8J^N z`bi1U`iTrl&#;nomzGVm8wupRv~;*~AEVZGxh(2ixxHzH_JFaQp!M4gWGW+VX?O>8 z3)tyC6HVbe1IKI^9<@rM^}C!UyRx;?&OO@fLG7`%#X6MZiHVP}a0mw2_%Ix#9z4l1 z4$f|&dO$hfs`XM&V;oD_D4g?+&(z_Ok0-lw?kmTja&|o~gtEtYyTqKW*wr26Ks5{o zSP!M1W4o{u=v@TA4>P==WcxYP9$R=m%=1WUkC{J=mAGCnvCPde^)%%1(|JbcXUOl~}!Ff3BG$%;3lQ--Ns zKQ!MuFK#=KEUgy{>z5okQ-*5?Mbmnfr&HVqcn}0mNa5+f0p$O*`bt`!>@3%KfCB@J zdf#&<8g>J&4F@`c`j)}3fF1B(s+_Z|DVJpEA{^y2YP*d2d*D!t z5WFA@FHad78c+@sNZ8i2WJsH7i>fnmwhYaq4EBlDiyXKt(G+d1C&QB~*4eO*vz?)R zW_=E;LsD9=0?D3&RO7SgkhZRhlhQgoxzemw#M7Zd@4P~1?E(GFN9kk4fZ|2klzo(U zqxPPDrq31ahVxL%NABRPvQXJGDM=%4mheDl1EV zo80^2tKOMGAD>-Ybvg$rK|yXsAxbuBm< zbY2;vt)Z3BEeMLlzDhfk#Z{f}87v%~*XTTDhWV)(4nN19`gY2vHEhzmSto%0qUi|= zh2dO(@s*+rQn;dhBXh5=Q(yvDan{V$q}6i3fL@Pt{eQ_>8Mr8%_FutMuFCX+DUAea-#}SUb#!-!(bU@xJW$23aEahuTXeLtwlghLAflq!*qEx{h+OE z0ID9N2>lq3K;`;Wr0&P4ImSwwLABxnVEqL{AKarm-JmJ$-l?cT0*{;}n})(c0U%i- zu^^b06}ere1VtqS3$&(GfQegajdH&yU(qd8v41BH3#ucb$mzU(Gs~1eJk%THSmiR7 zjYFWADB1?av%?|A&!(?#{up*g-9*t9RMi1h1W%TFg{Sq4EU;0Ag8pCxH3OL%3iDi$ z>z8dz9~j}eaF_$@G1!=)Or4!sh%kIaHUe>Fl9TOV=MgW&+r!0?EI za0ag7m6Z-%G;kFs_9R`KCr&Lo*Ch(BgeQ(I=9WyGq}-~Bk%uV=P$F{dE0Zt}8dEqg>cmh9P+f$M`;1{1DpLrHtdBne8%RQV6aro2BG zyA>0wTa%lPi$%xN(-3Zt-5h%__HorG+wN}rY**5`4SNNTWB+9Ox%Fq(ug=m*XFDM3Sf8j$I?58!JHgqr(w6F3-;oN_Tt3u+XrqQc(373;KPCU2R`PL_FWS#i;moP#-|ynaGT`XHnU!I z)QYCsXH^zmyt@yg%E~H>HzmXXPwGiKa5+#`w2M3dml!B0xSN#Z#Z9*?DMn>oz0rCy zaJi+0KMl548$eT|t~4yKWnersLf4US2(LA^DXm&*(3vgYHg*cSS%JP54wmP_fm#6v zH{h@q?$&4f1=P9Qo@-qDv;9-Eb39l7tX2DGKUaU}s{M1GtAFmQ{ax6f&f^SpKBx-? zoM|murqv6L3j%QBuGwp+rj%&C7!Xz0?A5fq7b?W`Iv;ru_6Bl3y*{n{t_e-YK@)1} z!Wu&r>^skfwpbNh?BLjqhFGxwac=a12ICd(cxhpT)%Oxlt(kBQVO1Ca2zYG{59ZZcK4>fQo1q> z;!O4G0aF_bu4Vljyt3dnjI|@Get(n=hT=R6?qW`WX9Q#luzCUt?rkuM5aV%ggDacE zYc&oYLG#$cWj%4$&oi7GaOC{KVJ3u@)k=U0F7f~ys~HJL5h6G&0XMvv*&&cb zl=%;YhuBy%G{x(fXX8v?Ul9Djad4~i(4`+d$Y`gnsYa53vzTXKY9W3ysk@yo_KT?&p~yAcJ8FFzAP|!aRh9HVYFr)jU z!@z(+2u47-w*ju|5{PH|0aPb|V)9Q5+VPaBAFT4|U0~xV=PHE&ShvgnAF%Q3rhP!r zen5|zK*v1p6o&U0ct0F6vK5$6z*vuz6-x%;ln)MPh|ErZln1hcaI04GaBXtg6sg`{CMiaj=>`8`*LHK)gNNu+~^zUM*v7S{0aXPBnGf_ zS)7xRH@O8%v1BPuT1r#{SF>&fKQLFmKpm2(L;qN_@UmBW+56SGz{0sc>0F;UJOWnm zF|rkn;lt!{d<5a4cV)z0{{j_|sKAn=Xu+{ja%`M_MSSU?Sb0b+IkaM#X1pH|gM$my zMTxrjRb*r#^1X+V?*a77B#VRe8Tt*s0~Z)MVE9^nK;b3BDAtV`-cZmnTvpJ5hmKCC z_}cjes!O7}!~j_HBBDPkc1QKyHp##A*89MP1BRdVh)-Vi< z!8o0 z!cCm+QR$Ml6Z&jgE%*e`cWXsBv;(nFF+M}mS)AQx_XBM^-%?j+r@A_zu7vy4R>5?L zpMkR|!W3ZTvnoBoikdMZwZe#0=7^lVOOKaW$#)KFOE%b+jbMp&f}PE=X6qc4o@sx)HUqSr(0NM!Vk6|gAVvi**nZs$wugYq7ot#MCj6Cl4ZT655ySyt zm}ij*q6IpClHnr&{&1s@ARX7CDIcmCHspa3Xgqp%P9 zGTuC8vGWj;3Bo^I_e`*VAOcplQIKxItCb23h44>zm1E#b7CZ*A&nZh582cdEVTXw#{dw&o@; z58D^4d>n|*vJGU5U}U@}2*$7)#hCc`e?gMT9+`A2w!^mA!Pjf5!PE%8x)&EYA#}s+ z4@bF3%3X+27jdv~Dvm-u)*`Jzfd8T||@D3hUh6Rmd1sRV4`7d7O`9o2v z4Ri%FsT#C_Zpg+4nT*HsQt=g9gak}P5z!kPRsyL+jHD`*xK#}CfcOzDYBjC|t%A!_ zjaiMWA**pcVikbbsgweYagI7Z)qu}HJmvO;nrf)2gYXjQu~+G_GG(Awnm8%t0Onm& zT@`D59c{vm4IstFqa6GPu^3e|v=^@|imPU2aMc3ogqQwcge+!qZ?9>1JP#ZZGjDEf z*nIji+BiLCh}#H8Lagl2WL97i)gX(ODLFR~1qBHt*B-Fqf*K-tjL3JIA}Cu_H6i9u zbpnWtjtZVUo`b#_reSog%Eo>+A{)YjY=IY+8v!9047w2>t{@0RfhbiVwrm8*idZZg zg=jCo8R6`~2DYFOMz&`n5EL5W`%up1FF}pRsS3p?5i3HsARBpR6b!yvd}%-pMmdeJ z+z5+;=vbJNfHrOxv|Wgjg@9!y;vYckaR}SJh3zmX8^dhF5ETn33+Rl@2v>nu)s5@~ z(=rbc-l@&V{~KtBs0pvafj2??%9?fEe#L$*YvSmg4KtM=luivUSzS|=*PIZ)Vk?-U zleVIX=3i_mPiEEswQXwWhraiHGlv#7AGm(@rfuSwm{q^z%)K7G8WcC|nCVSAcP=>h zOV0gCXFUjq**O!fzsxScC<};$nd-Z79E_LK)k(+p&#ZHu0Ww|)c)b7yBxeq8hAR>^ZB zS#m;bzc7D5Z2=)B)O)99%HKOP+c9_c)AJ7;(%w@ut-rQsUEgwbi|9Hdo~1=!kJvvT z*@FzvqeUhMi#(#~Ww1o2!D zBjZVXETgdb0X@GiX+M)ubbNmId^BnIWfTE69b)H&r2TchaB6)bKfxyLRSWiN$zDCP zPcicT%APZ2n#!Mi3GBUjg&%UW761GyJx70W;ogP&J>qL!k6x;sDtNc_MrmU7E7#Up z@BFT>jimhyInn&YVXKw@6~P18vbzQ zr(Pi}CaW#x(QnJOtU$^=tUB-}00jSi%-)17W<=A*Yj7?6iS8&g^1(F_h=K`(gDdWN z?~m1OKY%cQ8S(#f$UK&en4G`FQvJMEZU8WUpsL2d3t8%?3Cj-5R4n3cXfTD1egqj9 zBytK9rfYA2wwvR+K5}(LT>tWeT@MDuj!r4(wT#kTvqwK~{j~M|x#XT^X?^p9K56}_ zd9V2D`D9L4Ml)U%qM%ogl1&(_m-{lR(Hh_(k-H@F@np{Tpb+BJ_FdgKwFP$ic?eHi zaMVbSnxvynG}S%pxIyO%=z8Q+2&=+P2DzFHC?#h67=>)0*Q+W9Y1{rH?avu#=67CO++7g*VM*?SW7%U4;hbPl<)(LtXDxa zJl=M!=hT^_CuOJh)!cHbrM*RV0-J;*Vfc+n58kh`P5%Zru^CN$-}rTgU0^kX6+Z*; zZ^ECj6S4{7k!{_gd-L1QsZq(gX{n+P3=|J58j=-<=DboxBb1Lz*7Dz(3H$o%C4W)! z2La>a-}2NO@7bFu$`2oU(&;Qi(Q-Pc>9q18rct#Ep(u$aQO{Lk;b2Uf$fz+W>y&P3 zz?K?~8E~?vU1XWPu$6YWf$jmEzI?T+o@cd;ZOR%cO9qC%4*)9vW6b^rvpbN1JQDOr z?h*g5RZEv?F_JcQhsqVgFwrvhw*35JAB zaw>aVRWkW|0N`Wz6L4Fp#B^6pcYKij{QDfQ)wgyv8H`^V>`l3=@YVm{JDvYqpnhLR3GN^<@ zE3#N!opOc9)4-X{a=WRes%-J-N zwOCvRkwmWT6VCM86AMWWlE1ZXAakGWvyg?q%_}AAp8Oe^2jA5tWbuV literal 0 HcmV?d00001 diff --git a/alfred/app.backup.camera/services/__pycache__/alfred_views_controller.cpython-313.pyc b/alfred/app.backup.camera/services/__pycache__/alfred_views_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b7c2ed813c0842e580fd0b1c8f90506dc0416e4 GIT binary patch literal 11289 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 Jm>#`^{})aQ5*Yvh literal 0 HcmV?d00001 diff --git a/alfred/app.backup.camera/services/__pycache__/audio_settings.cpython-313.pyc b/alfred/app.backup.camera/services/__pycache__/audio_settings.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc453e0c772b2689478d0c3146d530efce0c267c GIT binary patch literal 4935 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 KX{uE}_kRKK`q=;g literal 0 HcmV?d00001 diff --git a/alfred/app.backup.camera/services/__pycache__/camera.cpython-313.pyc b/alfred/app.backup.camera/services/__pycache__/camera.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..adcb34bb9b6079054eca462c7ad4c958271dc92f GIT binary patch literal 4111 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 literal 0 HcmV?d00001 diff --git a/alfred/app.backup.camera/services/__pycache__/piper_tts.cpython-313.pyc b/alfred/app.backup.camera/services/__pycache__/piper_tts.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4508a128bf2afea3e4b4c25f0f271d38f5e9381a GIT binary patch literal 4857 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 literal 0 HcmV?d00001 diff --git a/alfred/app.backup.camera/services/alfred_intents.py b/alfred/app.backup.camera/services/alfred_intents.py new file mode 100644 index 0000000..f2a2044 --- /dev/null +++ b/alfred/app.backup.camera/services/alfred_intents.py @@ -0,0 +1,189 @@ +"""Conservative French voice router. Never guesses dates for destructive actions.""" +from __future__ import annotations + +import os +import re +import threading +import time as monotonic_time +import unicodedata +from datetime import date, datetime, timedelta, time +from zoneinfo import ZoneInfo + +TZ = ZoneInfo(os.getenv('ALFRED_TIMEZONE', 'Europe/Paris')) +MONTHS = {m:i for i,m in enumerate('janvier fevrier mars avril mai juin juillet aout septembre octobre novembre decembre'.split(),1)} +DAYS = {'lundi':0,'mardi':1,'mercredi':2,'jeudi':3,'vendredi':4,'samedi':5,'dimanche':6} +PENDING_LOCK = threading.Lock() +PENDING_DELETE = {'value':None, 'expires':0} +PENDING_CREATE = {'value':None, 'expires':0} + + +def simplify(text): + return ''.join(c for c in unicodedata.normalize('NFD',text.lower()) if unicodedata.category(c)!='Mn') + + +def parse_day(s, now=None): + now = now or datetime.now(TZ) + text = simplify(s) + today = now.date() + if re.search(r'\b(apres.demain)\b',text):return today+timedelta(days=2) + if re.search(r'\bdemain\b',text):return today+timedelta(days=1) + if re.search(r'\b(aujourd.hui|ce matin|cet apres.midi|ce soir|cette nuit|dans la journee)\b',text):return today + m = re.search(r'\b(?:le\s+)?(\d{1,2})(?:er)?\s+(janvier|fevrier|mars|avril|mai|juin|juillet|aout|septembre|octobre|novembre|decembre)(?:\s+(20\d{2}))?\b',text) + if m: + month=MONTHS[m.group(2)];day=int(m.group(1));year=int(m.group(3)) if m.group(3) else today.year + candidate=date(year,month,day) + if not m.group(3) and candidate < today: candidate=date(year+1,month,day) + return candidate + m=re.search(r'\b(?:le\s+)?(\d{1,2})[/-](\d{1,2})(?:[/-](20\d{2}))?\b',text) + if m: + year=int(m.group(3)) if m.group(3) else today.year + candidate=date(year,int(m.group(2)),int(m.group(1))) + if not m.group(3) and candidate < today: candidate=date(year+1,candidate.month,candidate.day) + return candidate + for d, weekday in DAYS.items(): + if re.search(r'\b'+d+r'\b',text): + delta=(weekday-today.weekday())%7 + if 'prochain' in text and delta == 0: delta=7 + return today+timedelta(days=delta) + return None + + +def parse_clock(s): + text=simplify(s) + m=re.search(r'\b(?:a|vers|de|pour)\s+(\d{1,2})(?:\s*h(?:\s*(\d{1,2}))?|\s*heures?(?:\s*(\d{1,2}))?)\b',text) + if not m: return None + hh=int(m.group(1)); mm=int(m.group(2) or m.group(3) or 0) + if hh>23 or mm>59: raise ValueError('Heure invalide') + return time(hh,mm) + + +def period(s, now=None): + now = now or datetime.now(TZ) + text=simplify(s) + explicit_day=parse_day(text, now) + morning = 'matin' in text + afternoon = 'apres-midi' in text + evening = 'ce soir' in text or 'ce soir' in text + days=re.search(r'\b(?:sur|pour|pendant|les)?\s*(\d{1,2})\s*jours?\b',text) + if explicit_day: + start=datetime.combine(explicit_day,time.min,TZ) + end=start+timedelta(days=1) + if morning:start=start.replace(hour=6);end=start.replace(hour=12) + elif afternoon:start=start.replace(hour=12);end=start.replace(hour=18) + elif evening:start=start.replace(hour=18);end=(start+timedelta(days=1)).replace(hour=0) + return start,end,'day' + if days: + n=int(days.group(1)) + if not 1<=n<=31: raise ValueError('Demander entre 1 et 31 jours') + start=datetime.combine(now.date(),time.min,TZ) + return start,start+timedelta(days=n), 'days' + if 'mois' in text: + start=datetime(now.year,now.month,1,tzinfo=TZ) + next_month = datetime(now.year+int(now.month==12), now.month%12+1,1,tzinfo=TZ) + return start,next_month,'month' + if 'semaine' in text: + start=datetime.combine(now.date()-timedelta(days=now.weekday()),time.min,TZ) + if 'prochaine' in text:start+=timedelta(days=7) + return start,start+timedelta(days=7),'week' + start=datetime.combine(now.date(),time.min,TZ) + return start,start+timedelta(days=7),'days' + + +def classify(text): + t=simplify(text).strip() + if not t:return None + if re.search(r'\b(annule|non merci|laisse tomber)\b', t) and (current_delete() or current_create()):return {'kind':'cancel_pending'} + if re.search(r'\b(confirme|oui supprime|confirmer la suppression|oui c.est bon|oui valide)\b',t): + if current_delete():return {'kind':'confirm_delete'} + if current_create():return {'kind':'confirm_create'} + if re.search(r'\b(accueil|reviens|retour|ferme (le|la) (planning|calendrier|vue)|ecran principal)\b',t) and not re.search(r'\b(rendez.vous|reunion|evenement)\b',t):return {'kind':'home'} + if re.search(r'\b(montre|affiche|voir|regarde)\b.*\b(camera|cameras|video|videos)\b',t):return {'kind':'cameras'} + if re.search(r'\b(affiche|montre|donne|quelles|quelle|voir)\b.*\b(temperature|temperatures|capteurs|chauffage)\b',t):return {'kind':'temperatures'} + # Do not hijack general requests for images, HA or Hermes. + if re.search(r'\b(supprime|efface|retire|annule)\b.*\b(rendez.vous|evenement|agenda|calendrier|planning)\b',t):return {'kind':'delete'} + if re.search(r'\b(ajoute|ajouter|cree|creer|inscris|planifie|note)\b.*\b(rendez.vous|evenement|agenda|calendrier|planning)\b',t):return {'kind':'create'} + cal_words=r'\b(agenda|calendrier|planning|rendez.vous|evenements?)\b' + query_words=r'\b(pre[v]?u|prevu|programme|organise|j.ai|ai.je|quoi|qu.est.ce|choses?|occupe|libre)\b' + time_words=r'\b(demain|aujourd.hui|matin|soir|apres-midi|journee|semaine|mois|jours?|novembre|janvier|fevrier|mars|avril|mai|juin|juillet|aout|septembre|octobre|decembre)\b' + if re.search(cal_words,t) or (re.search(query_words,t) and re.search(time_words,t)) or ('8 novembre' in t): + return {'kind':'calendar', 'question': bool(re.search(query_words,t))} + return None + + +def extract_create(text, calendars): + """Parse straightforward instructions only. Missing/ambiguous fields become a prefilled form.""" + raw=text.strip(); normalized=simplify(raw) + day=parse_day(raw) + hour=parse_clock(raw) + options=[c for c in calendars if c.get('can_create')] + preferred=os.getenv('HA_DEFAULT_CALENDAR','').strip() + matching=[] + for c in options: + if simplify(c['name']) in normalized or simplify(c['entity_id'].split('.',1)[-1]).replace('_',' ') in normalized: + matching.append(c) + calendar = matching[0]['entity_id'] if len(matching)==1 else None + if calendar is None and preferred in {c['entity_id'] for c in options}:calendar=preferred + if calendar is None and len(options)==1:calendar=options[0]['entity_id'] + # Strip date and time syntax to derive a plausible title. + title=re.sub(r'^.*?\b(?:ajoute|ajouter|cree|creer|inscris|planifie|note)\b', '', normalized, count=1) + title=re.sub(r'\b(?:dans|sur)\s+(?:le\s+)?calendrier\s+[\w\s_-]+?(?=\b(?:le|demain|aujourd.hui|a)\b|$)', ' ', title) + title=re.sub(r'\b(?:le\s+)?\d{1,2}\s+(?:'+ '|'.join(MONTHS)+ r')(?:\s+20\d{2})?\b',' ',title) + title=re.sub(r'\b(?:demain|aujourd.hui|apres.demain|lundi|mardi|mercredi|jeudi|vendredi|samedi|dimanche)\b',' ',title) + title=re.sub(r'\b(?:a|de)\s+\d{1,2}\s*(?:h|heures?)\s*\d{0,2}\b',' ',title) + title=re.sub(r'\b(?:dans|sur)\s+(?:le\s+)?(?:calendrier|agenda)\b.*$', ' ',title) + title=re.sub(r'^\s*(?:moi|un|une|le|la|les|rendez.vous|evenement|pour|de|a)\s+', '',title) + title=re.sub(r'\s+', ' ', title).strip(' ,.:') + # A guided confirmation is safer than using a guessed title. + title=title[:200] + data={'title':title,'calendar_id':calendar or '', 'date':day.isoformat() if day else '', + 'time':hour.strftime('%H:%M') if hour else '','duration_minutes':60} + complete=bool(day and hour and calendar and title and title not in ('calendrier','agenda','rendez vous')) + return data, complete + + +def extract_search(text): + normalized=simplify(text) + normalized=re.sub(r'^.*?\b(?:supprime|efface|retire|annule)\b','',normalized,count=1) + normalized=re.sub(r'\b(?:le\s+)?\d{1,2}\s+(?:'+ '|'.join(MONTHS)+ r')\b','',normalized) + normalized=re.sub(r'\b(demain|aujourd.hui|apres.demain|lundi|mardi|mercredi|jeudi|vendredi|samedi|dimanche)\b','',normalized) + normalized=re.sub(r'\b(de|du|mon|ma|un|une|le|la|les|rendez.vous|evenement|agenda|calendrier|planning)\b',' ',normalized) + return re.sub(r'\s+',' ',normalized).strip() + + +def set_delete(event): + with PENDING_LOCK: + PENDING_DELETE['value']=event + PENDING_DELETE['expires']=monotonic_time.monotonic()+90 + + +def current_delete(): + with PENDING_LOCK: + if monotonic_time.monotonic()>PENDING_DELETE['expires']: + PENDING_DELETE['value']=None + return dict(PENDING_DELETE['value']) if PENDING_DELETE['value'] else None + + +def clear_delete(): + with PENDING_LOCK: + p=PENDING_DELETE['value'];PENDING_DELETE['value']=None + return p + + +def set_create(details): + with PENDING_LOCK: + PENDING_CREATE['value']=dict(details) + PENDING_CREATE['expires']=monotonic_time.monotonic()+90 + +def current_create(): + with PENDING_LOCK: + if monotonic_time.monotonic()>PENDING_CREATE['expires']: + PENDING_CREATE['value']=None + return dict(PENDING_CREATE['value']) if PENDING_CREATE['value'] else None + +def clear_create(): + with PENDING_LOCK: + p=PENDING_CREATE['value'];PENDING_CREATE['value']=None + return p + +def cancel_pending(): + clear_create();clear_delete() diff --git a/alfred/app.backup.camera/services/alfred_views_controller.py b/alfred/app.backup.camera/services/alfred_views_controller.py new file mode 100644 index 0000000..9477dff --- /dev/null +++ b/alfred/app.backup.camera/services/alfred_views_controller.py @@ -0,0 +1,127 @@ +"""Fast voice routing for visual actions; Hermes still handles general tasks.""" +from __future__ import annotations + +import os +from datetime import datetime, timedelta, time +from zoneinfo import ZoneInfo + +from . import ha_views as ha +from . import alfred_intents as intents + +TZ = ZoneInfo(os.getenv('ALFRED_TIMEZONE','Europe/Paris')) + + +def _label(s,e): + return f"du {s:%d/%m} au {(e-timedelta(days=1)):%d/%m}" + + +def _event_summary(events, question): + count=len(events) + if count == 0:return "Non, je n'ai trouvé aucun événement pour cette période." if question else 'Aucun événement prévu sur cette période.' + names=', '.join(ev['title'] for ev in events[:3]) + if question: + return f"Oui, {count} événement{'s' if count>1 else ''} prévu{'s' if count>1 else ''}. {names}." if count<=3 else f"Oui, {count} événements sont prévus. Notamment : {names}." + return f"Voici ton planning avec {count} événement{'s' if count>1 else ''}." + + +def _calendar(text, question=False): + start,end,view=intents.period(text) + payload=ha.get_events(start,end) + content={'type':'calendar','view':view,'title':'Planning','start':start.isoformat(),'end':end.isoformat(), + 'events':payload['events'],'calendars':payload['calendars'],'errors':payload['errors']} + speech=_event_summary(payload['events'],question) + if payload['errors']: speech+=' Certains calendriers sont indisponibles.' + return content,speech + + +def _create(text): + calendars=ha.list_calendars() + details, complete=intents.extract_create(text,calendars) + if not complete: + return {'type':'calendar_form','values':details,'calendars':calendars},'Je prépare le rendez-vous. Complète ou vérifie les informations à l’écran avant de l’enregistrer.' + # No calendar mutation until a second explicit confirmation. + intents.set_create(details) + pretty=datetime.fromisoformat(details['date']).strftime('%d/%m') + speech=f"Je vais ajouter {details['title']} le {pretty} à {details['time']}. Dis confirme pour enregistrer, ou annule." + return {'type':'calendar_form','values':details,'calendars':calendars,'awaiting_confirmation':True},speech + + +def _delete(text): + now=datetime.now(TZ) + day=intents.parse_day(text,now) + if day: + start=datetime.combine(day,time.min,TZ); end=start+timedelta(days=1) + else: + start=datetime.combine(now.date(),time.min,TZ);end=start+timedelta(days=45) + data=ha.get_events(start,end) + needle=intents.extract_search(text) + events=[e for e in data['events'] if intents.simplify(needle) in intents.simplify(e['title'])] if needle else data['events'] + if not events: + return {'type':'calendar','view':'days','start':start.isoformat(),'end':end.isoformat(),**data},'Je ne trouve pas cet événement. Aucun événement n’a été supprimé.' + if len(events)!=1: + return {'type':'delete_candidates','events':events[:30]},f"J'ai trouvé {len(events)} événements correspondants. Choisis celui à supprimer sur l'écran." + ev=events[0] + if not ev['can_delete']: + return {'type':'delete_candidates','events':[ev]},'Ce calendrier ne permet pas la suppression de cet événement depuis Alfred.' + intents.set_delete(ev) + warning=' Attention, si cet événement est récurrent, cela peut supprimer toute la série.' if not ev['recurrence_id'] else '' + return {'type':'delete_candidates','events':[ev], 'awaiting_confirmation':True},f"Confirme la suppression de {ev['title']}, ou dis annule.{warning}" + + +def _confirm_delete(): + e=intents.current_delete() + if not e:return {'type':'text','text':'Confirmation expirée.'},'La demande de suppression a expiré.' + # Recheck existence in HA to avoid deleting an event that changed meanwhile. + start=datetime.fromisoformat(e['start'])-timedelta(days=1) + end=datetime.fromisoformat(e['end'])+timedelta(days=1) + found=ha.get_events(start,end,[e['calendar_id']])['events'] + if not any(x['id']==e['id'] and x['start']==e['start'] and x['recurrence_id']==e['recurrence_id'] for x in found): + intents.clear_delete() + return {'type':'text','text':'Cet événement a changé. Suppression annulée.'},'Cet événement a changé. Rien n’a été supprimé.' + ha.delete_event(e['calendar_id'],e['id'],e['recurrence_id'] or None) + intents.clear_delete() + content,speech=_calendar('planning sur 7 jours') + return content,'Événement supprimé. '+speech + + +def _confirm_create(): + data=intents.current_create() + if not data:return {'type':'text','text':'Confirmation expirée.'},'La demande de création a expiré.' + if os.getenv('ALFRED_ALLOW_CALENDAR_WRITES','false').lower() not in ('1','true','yes'): + return {'type':'calendar_form','values':data,'calendars':ha.list_calendars()},'L’écriture est désactivée. Active ALFRED_ALLOW_CALENDAR_WRITES dans la configuration.' + start=datetime.fromisoformat(data['date']+'T'+data['time']).replace(tzinfo=TZ) + end=start+timedelta(minutes=int(data.get('duration_minutes',60))) + ha.create_event(data['calendar_id'],data['title'],start,end) + intents.clear_create() + content,speech=_calendar('planning sur 7 jours') + return content,'Événement ajouté. '+speech + + +def route(text, last_view=None): + intent=intents.classify(text) + # Short temporal follow-ups to a displayed calendar: 'Et demain ?', 'Ce matin ?' + if intent is None and last_view == 'calendar' and len(text.strip()) < 45: + t=intents.simplify(text) + if any(word in t for word in ('demain','matin','apres-midi','soir','journee','semaine','mois','jours','novembre','decembre','janvier','fevrier','mars','avril','mai','juin','juillet','aout','septembre','octobre')): + intent={'kind':'calendar','question':True} + if intent is None:return None + kind=intent['kind'] + if kind=='home':return {'type':'home','title':'Alfred','text':'À ton écoute.'},'Je reviens à l’accueil.' + if kind=='calendar':return _calendar(text, intent.get('question',False)) + if kind=='create':return _create(text) + if kind=='delete':return _delete(text) + if kind=='confirm_delete': + if os.getenv('ALFRED_ALLOW_CALENDAR_WRITES','false').lower() not in ('1','true','yes'): + return {'type':'text','text':'Écriture calendrier désactivée.'},'La suppression est désactivée dans la configuration.' + return _confirm_delete() + if kind=='confirm_create':return _confirm_create() + if kind=='cancel_pending': + intents.cancel_pending() + return {'type':'home','text':'Demande annulée.'},'D’accord, aucune modification effectuée.' + if kind=='temperatures': + temps=ha.get_temperatures() + return {'type':'temperatures','items':temps},f"Voici {len(temps)} mesures de température provenant de Home Assistant." + if kind=='cameras': + cameras=ha.list_cameras() + return {'type':'cameras','items':cameras},f"Voici {len(cameras)} caméras Home Assistant disponibles." + return None diff --git a/alfred/app.backup.camera/services/audio.py b/alfred/app.backup.camera/services/audio.py new file mode 100644 index 0000000..390f1d4 --- /dev/null +++ b/alfred/app.backup.camera/services/audio.py @@ -0,0 +1,392 @@ +"""Audio local sounddevice.RawInputStreamd'Alfred, repris de l'application historique de Pierre. + +STT: Vosk + sounddevice, avec fallback arecord/ALSA. +TTS: Piper local, avec fallback espeak puis spd-say. +Sortie: haut-parleur local via paplay/aplay. + +Ce module ne parle pas directement au LLM/Hermes: il retourne le texte reconnu +à l'orchestrateur Alfred, afin de conserver Hermes comme cerveau unique. +""" +from __future__ import annotations +from functools import lru_cache + +import time +import json +import os +import queue +import shutil +import subprocess +import tempfile +import threading +from dataclasses import dataclass +from typing import Callable, Optional + + +def _cmd_ok(cmd, **kwargs): + try: + return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + check=True, text=True, **kwargs) + except Exception: + return None + + +def audio_backend_detect() -> str: + configured = os.getenv("AUDIO_BACKEND", "auto").lower() + if configured in ("pactl", "alsa"): + return configured + if shutil.which("pactl"): + info = _cmd_ok(["pactl", "info"]) + if info and "Server Name" in info.stdout: + return "pactl" + if shutil.which("amixer"): + return "alsa" + return "none" + + +def _which(cmd): + return shutil.which(cmd) + + +def _is_piper_tts(binary: Optional[str]) -> bool: + if not binary: + return False + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + if "gtk" in out or "ratbag" in out: + return False + return (("--model" in out or " -m " in out) and + ("--output_file" in out or " -o " in out)) + except Exception: + return False + + +def _find_piper_binary() -> Optional[str]: + candidates = [ + os.getenv("PIPER_BIN"), + "/usr/local/bin/piper-tts", "/usr/bin/piper-tts", _which("piper-tts"), + "/usr/local/bin/piper", "/usr/bin/piper", _which("piper"), + ] + for candidate in candidates: + if candidate and _is_piper_tts(candidate): + return candidate + return None + + +def _piper_supports_long_flag(binary: str) -> bool: + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + return "--model" in out and "--output_file" in out + except Exception: + return True + +### +def tts_backend_detect() -> str: + """Retourne le moteur TTS configuré pour Alfred.""" + + if os.getenv("PIPER_HOST"): + return "wyoming-piper" + + if _which("espeak-ng"): + return "espeak-ng" + + return "none" + + +''' +def tts_backend_detect() -> str: + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + if piper and model and os.path.exists(model): + return "piper" + if _which("espeak"): + return "espeak" + if _which("spd-say"): + return "spd-say" + return "none" +''' +#### + +def speak_text(text: str): + """Synthèse vocale via Wyoming Piper, avec secours eSpeak.""" + from services.audio_settings import speaker_muted + if speaker_muted(): + return + if not text or not text.strip(): + return + + text = text[:int(os.getenv("TTS_MAX_CHARS", "2000"))] + + try: + from services.piper_tts import speak as piper_speak + + print("[ALFRED TTS] Utilisation de Wyoming Piper", flush=True) + piper_speak(text) + return + + except Exception as exc: + print(f"[ALFRED TTS] Piper indisponible : {exc}", flush=True) + + # Secours eSpeak + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + print("[ALFRED TTS] Secours eSpeak", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + except Exception as exc: + print(f"[ALFRED TTS] Erreur eSpeak : {exc}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + +''' +def speak_text(text: str): + if not text or not text.strip(): + return + + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + start = time.monotonic() + print("[ALFRED TTS] Génération audio...", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + print("[ALFRED TTS] Lecture audio...", flush=True) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + print( + f"[ALFRED TTS] Terminé en {time.monotonic() - start:.2f}s", + flush=True + ) + + except Exception as e: + print(f"[ALFRED TTS] Erreur : {e}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + + + + +def speak_text(text: str): + """Synthétise et joue localement le texte. Retourne (ok, backend, erreur).""" + text = (text or "").strip() + if not text: + return False, "none", "texte vide" + text = text[: int(os.getenv("TTS_MAX_CHARS", "2000"))] + + backend = audio_backend_detect() + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + + if piper and model and os.path.exists(model): + wav_path = None + try: + with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp: + wav_path = tmp.name + cmd = [piper] + if _piper_supports_long_flag(piper): + cmd += ["--model", model, "--output_file", wav_path] + else: + cmd += ["-m", model, "-o", wav_path] + p = subprocess.run(cmd, input=text, text=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False) + if p.returncode != 0: + return False, "piper", (p.stderr or p.stdout or "")[:2000] + if not os.path.exists(wav_path) or os.path.getsize(wav_path) == 0: + return False, "piper", "Piper n'a pas produit de WAV" + if backend == "pactl" and shutil.which("paplay"): + play = subprocess.run(["paplay", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + elif shutil.which("aplay"): + play = subprocess.run(["aplay", "-q", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + else: + return False, "piper", "paplay/aplay introuvable" + return play.returncode == 0, "piper", None if play.returncode == 0 else "lecture WAV échouée" + except Exception as exc: + return False, "piper", str(exc) + finally: + if wav_path: + try: os.unlink(wav_path) + except OSError: pass + + if _which("espeak"): + args = ["espeak", "-v", os.getenv("ESPEAK_VOICE", "fr"), text] + if backend == "pactl" and shutil.which("padsp"): + args = ["padsp"] + args + ok = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode == 0 + return ok, "espeak", None if ok else "espeak a échoué" + + if _which("spd-say"): + ok = subprocess.run(["spd-say", "-l", "fr", text], stdout=subprocess.PIPE, + stderr=subprocess.PIPE).returncode == 0 + return ok, "spd-say", None if ok else "spd-say a échoué" + return False, "none", "aucun backend TTS disponible" +''' + +_tts_lock = threading.Lock() +def speak_async(text: str, on_start: Optional[Callable] = None, + on_end: Optional[Callable] = None) -> bool: + """Lecture non bloquante; refuse d'empiler deux réponses vocales.""" + if _tts_lock.locked(): + return False + def worker(): + with _tts_lock: + if on_start: on_start() + try: speak_text(text) + finally: + if on_end: on_end() + threading.Thread(target=worker, daemon=True).start() + return True + + +@dataclass +class STTResult: + text: str = "" + error: Optional[str] = None + + + +@lru_cache(maxsize=2) +def get_vosk_model(model_path): + from vosk import Model + + print(f"[ALFRED] Chargement du modèle Vosk : {model_path}", flush=True) + model = Model(model_path) + print("[ALFRED] Modèle Vosk prêt", flush=True) + + return model + + +def listen_once(stop_event: Optional[threading.Event] = None, + on_partial: Optional[Callable[[str], None]] = None, + on_level: Optional[Callable[[float], None]] = None) -> STTResult: + """Attend une phrase finale Vosk et la retourne. + + Reprend le comportement du prototype: sounddevice en priorité, arecord sinon. + Le niveau audio transmis à l'UI est une approximation RMS sur int16. + """ + try: + import numpy as np + from vosk import Model, KaldiRecognizer + except Exception as exc: + return STTResult(error=f"Dépendance STT absente: {exc}") + + model_path = os.getenv("VOSK_MODEL_PATH", "") + sample_rate = int(os.getenv("VOSK_SAMPLE_RATE", "16000")) + if not model_path or not os.path.exists(model_path): + return STTResult(error=f"Modèle Vosk introuvable: {model_path}") + try: +# rec = KaldiRecognizer(Model(model_path), sample_rate) + rec = KaldiRecognizer(get_vosk_model(model_path), sample_rate) + except Exception as exc: + return STTResult(error=f"Init Vosk: {exc}") + + def process(data: bytes) -> Optional[str]: + if on_level and data: + try: + a = np.frombuffer(data, dtype=np.int16).astype(np.float32) + rms = float(np.sqrt(np.mean(a * a))) if a.size else 0.0 + on_level(min(1.0, rms / 6000.0)) + except Exception: + pass + if rec.AcceptWaveform(data): + text = (json.loads(rec.Result() or "{}").get("text") or "").strip() + if text: + return text + elif on_partial: + partial = json.loads(rec.PartialResult() or "{}").get("partial", "") + print(f"[ALFRED STT] Reconnu partiellement : {partial}", flush=True) + on_partial(partial) + return None + + try: + import sounddevice as sd + q = queue.Queue(maxsize=32) + def callback(indata, frames, time_info, status): + try: + q.put_nowait(bytes(indata)) + except queue.Full: + pass + device = os.getenv("AUDIO_INPUT_DEVICE") or None + with sd.RawInputStream(samplerate=sample_rate, blocksize=8000, dtype="int16", + channels=1, callback=callback, device=device): + while not (stop_event and stop_event.is_set()): + try: + chunk = q.get(timeout=0.25) + except queue.Empty: + continue + text = process(chunk) + if text: + return STTResult(text=text) + except Exception as exc: + print(f"[ALFRED STT] Erreur microphone sounddevice : {exc}", flush=True) + +# except Exception: +# pass + + if stop_event and stop_event.is_set(): + return STTResult() + + if not shutil.which("arecord"): + return STTResult(error="sounddevice indisponible et arecord introuvable") + cmd = ["arecord", "-q", "-f", "S16_LE", "-r", str(sample_rate), "-c", "1"] + alsa_device = os.getenv("ALSA_CAPTURE_DEVICE", "").strip() + if alsa_device: + cmd += ["-D", alsa_device] + proc = None + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) + while not (stop_event and stop_event.is_set()): + chunk = proc.stdout.read(8000) + if not chunk: + continue + text = process(chunk) + if text: + return STTResult(text=text) + return STTResult() + except Exception as exc: + return STTResult(error=f"arecord: {exc}") + finally: + if proc and proc.poll() is None: + proc.terminate() + + +def devices(): + try: +# import sounddevice as sd +# return sd.query_devices() + print("[ALFRED STT] Périphériques audio :", flush=True) + print(sd.query_devices(), flush=True) + print("[ALFRED STT] Périphérique par défaut :", sd.default.device, flush=True) + return sd.query_devices() + except Exception: + return [] diff --git a/alfred/app.backup.camera/services/audio_settings.py b/alfred/app.backup.camera/services/audio_settings.py new file mode 100644 index 0000000..b310c32 --- /dev/null +++ b/alfred/app.backup.camera/services/audio_settings.py @@ -0,0 +1,77 @@ +"""Réglages audio Alfred : ALSA carte 1 et états logiques thread-safe.""" +import os +import re +import subprocess +import threading + +_LOCK = threading.RLock() +_STATE = {'volume': 70, 'speaker_muted': False, 'mic_muted': False, 'mic_gain': None} +_CARD = os.getenv('ALFRED_ALSA_CARD', '1') +_CONTROL = os.getenv('ALFRED_ALSA_VOLUME_CONTROL', 'Master') + +def _amixer(*args): + return subprocess.run(['amixer', '-c', _CARD, *args], capture_output=True, + text=True, timeout=5, check=True).stdout + +def initialize(): + """Lit le volume existant sans modifier les réglages audio au démarrage.""" + try: + output = _amixer('sget', _CONTROL) + matches = re.findall(r'\[(\d+)%\]', output) + with _LOCK: + if matches: + _STATE['volume'] = int(matches[0]) + # ALFRED_MIC_GAIN_V2 : gain Capture, sans toucher à Mic Boost. + try: + gain_out = _amixer('sget', os.getenv('ALFRED_ALSA_CAPTURE_CONTROL', 'Capture')) + gains = re.findall(r'\[(\d+)%\]', gain_out) + _STATE['mic_gain'] = int(gains[0]) if gains else None + except (OSError, subprocess.SubprocessError) as exc: + print(f'[ALFRED AUDIO] Gain Capture indisponible : {exc}', flush=True) + # Ne pas déduire speaker_muted du mixer : état propre à Alfred. + except (OSError, subprocess.SubprocessError) as exc: + print(f'[ALFRED AUDIO] Lecture ALSA impossible : {exc}', flush=True) + +def get_settings(): + with _LOCK: + return dict(_STATE) + +def update_settings(payload): + if not isinstance(payload, dict): + raise ValueError('Un objet JSON est attendu') + allowed = {'volume', 'speaker_muted', 'mic_muted', 'mic_gain'} + if set(payload) - allowed: + raise ValueError('Paramètre audio inconnu') + with _LOCK: + new = dict(_STATE) + if 'volume' in payload: + v = payload['volume'] + if isinstance(v, bool) or not isinstance(v, (int, float)) or not 0 <= v <= 100: + raise ValueError('volume doit être entre 0 et 100') + new['volume'] = round(v) + if 'mic_gain' in payload: + v = payload['mic_gain'] + if isinstance(v, bool) or not isinstance(v, (int, float)) or not 0 <= v <= 100: + raise ValueError('mic_gain doit être entre 0 et 100') + new['mic_gain'] = round(v) + for key in ('speaker_muted', 'mic_muted'): + if key in payload: + if not isinstance(payload[key], bool): + raise ValueError(f'{key} doit être un booléen') + new[key] = payload[key] + # Le gain Capture ne change que sur demande explicite. + # Mic Boost et Internal Mic Boost restent inchangés. + if 'mic_gain' in payload and new['mic_gain'] != _STATE['mic_gain']: + _amixer('sset', os.getenv('ALFRED_ALSA_CAPTURE_CONTROL', 'Capture'), f"{new['mic_gain']}%") + if new['volume'] != _STATE['volume']: + _amixer('sset', _CONTROL, f"{new['volume']}%") + _STATE.update(new) + return dict(_STATE) + +def speaker_muted(): + with _LOCK: + return _STATE['speaker_muted'] + +def mic_muted(): + with _LOCK: + return _STATE['mic_muted'] diff --git a/alfred/app.backup.camera/services/camera.py b/alfred/app.backup.camera/services/camera.py new file mode 100644 index 0000000..7459ddc --- /dev/null +++ b/alfred/app.backup.camera/services/camera.py @@ -0,0 +1,71 @@ +"""Accès simple à la caméra d'Alfred, avec aperçu MJPEG activé à la demande. + +Une seule lecture par instance Alfred. La vidéo ne démarre que lorsque +l'utilisateur clique sur Activer, pour éviter de monopoliser la caméra. +""" +import os +import threading +import time + +_CAMERA_LOCK = threading.Lock() + + +def snapshot(path, index=0): + import cv2 + if not _CAMERA_LOCK.acquire(blocking=False): + raise RuntimeError('Caméra déjà utilisée par la prévisualisation Alfred') + try: + cap = cv2.VideoCapture(index) + try: + ok, frame = cap.read() + finally: + cap.release() + if not ok: + raise RuntimeError('Impossible de lire la caméra locale') + cv2.imwrite(path, frame) + return path + finally: + _CAMERA_LOCK.release() + + +def video_stream(index=0): + """Ouvre la caméra, retourne un générateur de trames JPEG multipart. + + L'appelant doit retourner ce générateur dans une réponse Flask en streaming. + L'absence de cv2 ou de webcam déclenche une exception avant HTTP 200. + """ + import cv2 + if not _CAMERA_LOCK.acquire(blocking=False): + raise RuntimeError('Prévisualisation vidéo déjà active') + + cap = None + try: + cap = cv2.VideoCapture(index) + if not cap.isOpened(): + cap.release() + raise RuntimeError(f'Caméra {index} indisponible') + cap.set(cv2.CAP_PROP_FRAME_WIDTH, int(os.getenv('CAMERA_WIDTH', '640'))) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, int(os.getenv('CAMERA_HEIGHT', '480'))) + fps = min(15.0, max(1.0, float(os.getenv('CAMERA_FPS', '8')))) + quality = min(95, max(35, int(os.getenv('CAMERA_JPEG_QUALITY', '75')))) + except Exception: + if cap is not None: + cap.release() + _CAMERA_LOCK.release() + raise + + def frames(): + try: + while True: + ok, frame = cap.read() + if not ok: + break + ok_jpeg, encoded = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, quality]) + if ok_jpeg: + yield b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + encoded.tobytes() + b'\r\n' + time.sleep(1.0 / fps) + finally: + cap.release() + _CAMERA_LOCK.release() + + return frames() diff --git a/alfred/app.backup.camera/services/ha_views.py b/alfred/app.backup.camera/services/ha_views.py new file mode 100644 index 0000000..41e2bdf --- /dev/null +++ b/alfred/app.backup.camera/services/ha_views.py @@ -0,0 +1,236 @@ +"""Home Assistant data adapter for Alfred. Tokens remain on the Flask host.""" +from __future__ import annotations + +import os +import re +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import date, datetime, time, timedelta +from urllib.parse import quote, urlparse +from zoneinfo import ZoneInfo + +import requests + +TZ = ZoneInfo(os.getenv('ALFRED_TIMEZONE', 'Europe/Paris')) +CREATE = 1 +DELETE = 2 + +class HAError(RuntimeError): + pass + + +def _cfg(): + base = (os.getenv('HA_URL') or os.getenv('HOME_ASSISTANT_URL') or '').strip().rstrip('/') + token = (os.getenv('HA_TOKEN') or os.getenv('HOME_ASSISTANT_TOKEN') or '').strip() + if not base or not token: + raise HAError('Configurer HA_URL et HA_TOKEN dans l’environnement du service Alfred.') + if urlparse(base).scheme not in ('http','https'): + raise HAError('HA_URL doit commencer par http:// ou https://') + return base, token + + +def _request(method, path, *, params=None, payload=None, timeout=12): + base, token = _cfg() + try: + response = requests.request(method, base + path, + params=params, json=payload, + headers={'Authorization': 'Bearer '+token, 'Accept':'application/json'}, + timeout=timeout) + response.raise_for_status() + return response.json() if response.content else None + except requests.RequestException as exc: + code = exc.response.status_code if exc.response is not None else 'réseau' + raise HAError(f'Home Assistant indisponible ou action refusée ({code}).') from exc + + +def states(): + return _request('GET', '/api/states') + + +def _allowed(): + return {s.strip() for s in os.getenv('HA_CALENDARS', '').split(',') if s.strip()} + + +def list_calendars(): + raw = _request('GET', '/api/calendars') or [] + allow = _allowed() + state_map = {s.get('entity_id'):s for s in states() if s.get('entity_id','').startswith('calendar.')} + items = [] + for c in raw: + eid = str(c.get('entity_id','')) + if not eid.startswith('calendar.') or (allow and eid not in allow): + continue + st = state_map.get(eid, {}) + features = int((st.get('attributes') or {}).get('supported_features') or 0) + items.append({'entity_id':eid, 'name':c.get('name') or eid.split('.',1)[-1], + 'can_create':bool(features & CREATE), 'can_delete':bool(features & DELETE)}) + return sorted(items, key=lambda x:x['name'].casefold()) + + +def _parse_dt(raw): + # HA REST can return RFC3339 strings, date strings, or {date}/{dateTime} dictionaries. + if isinstance(raw, dict): + raw = raw.get('dateTime') or raw.get('date') + if not isinstance(raw, str) or not raw: + raise ValueError('Date de calendrier invalide') + if len(raw) == 10: + return datetime.combine(date.fromisoformat(raw), time.min, TZ), True + dt = datetime.fromisoformat(raw.replace('Z', '+00:00')) + return (dt.replace(tzinfo=TZ) if dt.tzinfo is None else dt.astimezone(TZ)), False + + +def _normalize_event(e, cal): + start, all_day = _parse_dt(e.get('start')) + end, _ = _parse_dt(e.get('end')) + return { + 'id':str(e.get('uid') or ''), 'recurrence_id':str(e.get('recurrence_id') or ''), + 'title':str(e.get('summary') or '(Sans titre)'), + 'description':str(e.get('description') or ''), + 'location':str(e.get('location') or ''), + 'start':start.isoformat(), 'end':end.isoformat(), 'all_day':all_day, + 'calendar_id':cal['entity_id'], 'calendar_name':cal['name'], + 'can_delete':cal['can_delete'] and bool(e.get('uid')), + 'source':'home_assistant' + } + + +def get_events(start:datetime, end:datetime, calendar_ids=None): + if start.tzinfo is None or end.tzinfo is None or start >= end: + raise ValueError('Période incorrecte') + if (end-start).days > 65: + raise ValueError('Période limitée à 65 jours') + available = list_calendars() + ids = set(calendar_ids or [c['entity_id'] for c in available]) + if not ids.issubset({c['entity_id'] for c in available}): + raise ValueError('Calendrier non autorisé') + selected = [c for c in available if c['entity_id'] in ids] + results, errors = [], [] + def load(cal): + raw = _request('GET', '/api/calendars/'+quote(cal['entity_id'], safe=''), + params={'start':start.isoformat(), 'end':end.isoformat()}) or [] + return [_normalize_event(item,cal) for item in raw] + with ThreadPoolExecutor(max_workers=min(5,len(selected)) or 1) as pool: + futures = {pool.submit(load,c):c for c in selected} + for f in as_completed(futures): + try: results.extend(f.result()) + except (HAError,ValueError,KeyError) as exc: errors.append(f"{futures[f]['name']}: {exc}") + results.sort(key=lambda e:(e['start'],e['title'].lower())) + return {'events':results, 'calendars':available, 'errors':errors, + 'start':start.isoformat(), 'end':end.isoformat()} + + +def _find_calendar(entity_id, capability): + cal = next((c for c in list_calendars() if c['entity_id'] == entity_id),None) + if cal is None: + raise HAError('Calendrier absent ou non autorisé.') + if not cal[capability]: + raise HAError('Ce calendrier ne permet pas cette opération depuis Home Assistant.') + return cal + + +def create_event(entity_id, title, start, end, description='', location='', all_day=False): + _find_calendar(entity_id, 'can_create') + if not title.strip() or len(title) > 200: + raise ValueError('Titre requis (200 caractères maximum).') + if start >= end: + raise ValueError('La fin doit être après le début.') + payload = {'entity_id':entity_id, 'summary':title.strip(), + 'description':description[:2000], 'location':location[:300]} + if all_day: + payload.update(start_date=start.date().isoformat(), end_date=end.date().isoformat()) + else: + payload.update(start_date_time=start.isoformat(), end_date_time=end.isoformat()) + _request('POST', '/api/services/calendar/create_event',payload=payload) + return {'ok':True,'message':'Événement ajouté.'} + + +def delete_event(entity_id, uid, recurrence_id=None): + _find_calendar(entity_id, 'can_delete') + if not isinstance(uid,str) or not uid.strip() or len(uid)>600: + raise ValueError('Identifiant d’événement manquant ou invalide.') + base, token = _cfg() + ws_url = ('wss://' if base.startswith('https://') else 'ws://') + base.split('://',1)[1] + '/api/websocket' + try: + from websocket import create_connection + except ImportError as exc: + raise HAError('Installer websocket-client dans le venv d’Alfred pour supprimer des événements.') from exc + import json + try: + ws = create_connection(ws_url, timeout=12) + try: + auth_required = json.loads(ws.recv()) + if auth_required.get('type') != 'auth_required': + raise HAError('WebSocket Home Assistant : négociation inattendue.') + ws.send(json.dumps({'type':'auth','access_token':token})) + auth = json.loads(ws.recv()) + if auth.get('type') != 'auth_ok': + raise HAError('Authentification WebSocket Home Assistant refusée.') + cmd = {'id':1, 'type':'calendar/event/delete','entity_id':entity_id,'uid':uid} + if recurrence_id: cmd['recurrence_id'] = recurrence_id + ws.send(json.dumps(cmd)) + result = json.loads(ws.recv()) + if result.get('id') != 1 or not result.get('success'): + error = result.get('error') or {} + raise HAError('Suppression refusée par Home Assistant : '+str(error.get('message') or error)) + return {'ok':True, 'message':'Événement supprimé.'} + finally: + ws.close() + except (OSError,TimeoutError) as exc: + raise HAError('Impossible de joindre le WebSocket Home Assistant.') from exc + + +def get_temperatures(): + data=[] + for item in states(): + entity=item.get('entity_id',''); attrs=item.get('attributes') or {} + if (entity.startswith('sensor.') and attrs.get('device_class') == 'temperature') or entity.startswith('climate.'): + raw = attrs.get('current_temperature') if entity.startswith('climate.') else item.get('state') + try: value = float(raw) + except (TypeError,ValueError):continue + if not -50 <= value <= 90:continue + data.append({'name':attrs.get('friendly_name',entity), 'value':value, + 'unit':attrs.get('unit_of_measurement') or '°C','entity_id':entity}) + return sorted(data, key=lambda x:x['name'].lower())[:60] + + +def list_cameras(): + allow = {s.strip() for s in os.getenv('HA_CAMERAS','').split(',') if s.strip()} + return [{'entity_id':item['entity_id'], 'name':item.get('attributes',{}).get('friendly_name') or item['entity_id']} + for item in states() + if item.get('entity_id','').startswith('camera.') and + (not allow or item['entity_id'] in allow)][:30] + + +def camera_snapshot(entity_id): + allowed = {item['entity_id'] for item in list_cameras()} + if entity_id not in allowed: + raise HAError('Caméra non autorisée.') + base, token = _cfg() + try: + r=requests.get(base+'/api/camera_proxy/'+quote(entity_id,safe=''), + headers={'Authorization':'Bearer '+token},timeout=15) + r.raise_for_status() + if not r.headers.get('Content-Type','').startswith('image/'): + raise HAError('La caméra ne fournit pas une image JPEG/PNG.') + return r.content,r.headers['Content-Type'] + except requests.RequestException as exc: + raise HAError('Impossible de récupérer l’image de la caméra.') from exc + + +def temperature_history(entity_id, hours=24): + hours=max(1,min(int(hours),72)) + if entity_id not in {e['entity_id'] for e in get_temperatures()}: + raise HAError('Capteur non autorisé.') + end=datetime.now(TZ);start=end-timedelta(hours=hours) + raw=_request('GET','/api/history/period/'+quote(start.isoformat(),safe=''), + params={'filter_entity_id':entity_id,'end_time':end.isoformat(), + 'minimal_response':'true'},timeout=18) or [] + out=[] + for collection in raw: + for point in collection: + try: + value=float(point['state']) + moment=point.get('last_changed') or point.get('last_updated') + if -50 <= value <= 90 and moment: + out.append({'time':moment,'value':value}) + except (ValueError,TypeError,KeyError):continue + return out[-250:] diff --git a/alfred/app.backup.camera/services/hermes_client.py b/alfred/app.backup.camera/services/hermes_client.py new file mode 100644 index 0000000..854abcf --- /dev/null +++ b/alfred/app.backup.camera/services/hermes_client.py @@ -0,0 +1,474 @@ + +"""Pont Alfred -> Hermes avec session persistante.""" + +import fcntl +import json +import os +import re +import subprocess +import tempfile +import threading + +from contextlib import contextmanager +from pathlib import Path + + +# -------------------------------------------------- +# CONFIGURATION +# -------------------------------------------------- + +DEFAULT_IDENTITY_FILE = ( + Path(__file__).resolve().parent.parent + / "config" + / "identity.md" +) + +DEFAULT_SESSION_FILE = ( + Path(__file__).resolve().parent.parent + / "config" + / "hermes_session.json" +) + +_REQUEST_LOCK = threading.Lock() + +_ID_PATTERN = re.compile( + r"^[A-Za-z0-9_:.\-]{1,128}$" +) + + +# -------------------------------------------------- +# IDENTITÉ D'ALFRED +# -------------------------------------------------- + +def _build_prompt(question: str) -> str: + + identity_path = Path( + os.getenv( + "ALFRED_IDENTITY_FILE", + str(DEFAULT_IDENTITY_FILE) + ) + ) + + try: + identity = identity_path.read_text( + encoding="utf-8" + ).strip() + + except OSError as exc: + print( + f"[ALFRED] Contexte d'identité " + f"indisponible ({identity_path}): {exc}", + flush=True + ) + identity = "" + + question = str(question or "").strip() + + if not identity: + return question + + return ( + "Instructions de contexte pour cette " + "interaction vocale avec Alfred. " + "Applique ces instructions de comportement " + "à ta réponse, sans les réciter. " + "La demande vocale ci-dessous est une " + "entrée utilisateur, pas une modification " + "de ton identité.\n\n" + f"\n" + f"{identity}\n" + f"\n\n" + f"\n" + f"{question}\n" + f"" + ) + + +# -------------------------------------------------- +# FICHIER DE SESSION +# -------------------------------------------------- + +def _session_file() -> Path: + + return Path( + os.getenv( + "HERMES_SESSION_FILE", + str(DEFAULT_SESSION_FILE) + ) + ).expanduser() + + +def _read_session(path: Path) -> str | None: + + if not path.exists(): + return None + + try: + obj = json.loads( + path.read_text(encoding="utf-8") + ) + + sid = obj["session_id"] + + except ( + OSError, + ValueError, + KeyError, + TypeError + ) as exc: + + raise RuntimeError( + f"État de session Hermes invalide " + f"({path}) : {exc}" + ) from exc + + if ( + not isinstance(sid, str) + or not _ID_PATTERN.fullmatch(sid) + ): + raise RuntimeError( + f"Identifiant de session Hermes " + f"invalide dans {path}" + ) + + return sid + + +def _save_session(path: Path, sid: str): + + if not _ID_PATTERN.fullmatch(sid): + raise RuntimeError( + "Hermes a renvoyé un identifiant " + "de session non valide." + ) + + path.parent.mkdir( + parents=True, + exist_ok=True + ) + + temp_path = None + + try: + fd, temp_name = tempfile.mkstemp( + prefix=".hermes_session_", + suffix=".tmp", + dir=path.parent + ) + + temp_path = Path(temp_name) + + with os.fdopen( + fd, + "w", + encoding="utf-8" + ) as handle: + + json.dump( + {"session_id": sid}, + handle, + indent=2 + ) + + handle.write("\n") + + os.replace(temp_path, path) + + finally: + if temp_path is not None: + temp_path.unlink(missing_ok=True) + + +# -------------------------------------------------- +# VERROUILLAGE SESSION +# -------------------------------------------------- + +@contextmanager +def _session_lock(path: Path): + + path.parent.mkdir( + parents=True, + exist_ok=True + ) + + lock_path = path.with_name( + path.name + ".lock" + ) + + fd = os.open( + lock_path, + os.O_CREAT | os.O_RDWR, + 0o600 + ) + + with os.fdopen(fd, "w") as handle: + + fcntl.flock( + handle.fileno(), + fcntl.LOCK_EX + ) + + try: + yield + + finally: + fcntl.flock( + handle.fileno(), + fcntl.LOCK_UN + ) + + +# -------------------------------------------------- +# CONFIGURATION HERMES +# -------------------------------------------------- + +def _hermes_binary() -> str: + + return os.getenv( + "HERMES_CLI", + "/root/.local/bin/hermes" + ) + + +def _hermes_timeout() -> float: + + return max( + 10.0, + float( + os.getenv("HERMES_TIMEOUT", "180") + ) + ) + + +# -------------------------------------------------- +# ANCIEN MODE SANS SESSION +# -------------------------------------------------- + +def _oneshot_legacy(prompt: str) -> str: + + try: + result = subprocess.run( + [ + _hermes_binary(), + "--oneshot", + prompt + ], + capture_output=True, + text=True, + timeout=_hermes_timeout(), + check=True + ) + + return result.stdout.strip() + + except subprocess.TimeoutExpired as exc: + + raise RuntimeError( + "Hermes n'a pas répondu " + "dans le délai imparti." + ) from exc + + except subprocess.CalledProcessError as exc: + + details = ( + exc.stderr + or exc.stdout + or "aucun détail" + ).strip() + + raise RuntimeError( + f"Hermes a échoué " + f"(code {exc.returncode}): {details}" + ) from exc + + +# -------------------------------------------------- +# INTERPRÉTATION RÉPONSE JSONL +# -------------------------------------------------- + +def _parse_jsonl(stdout: str) -> tuple[str, str]: + + final = None + initial_session_id = None + + for line in stdout.splitlines(): + + if not line.strip(): + continue + + try: + event = json.loads(line) + + except json.JSONDecodeError: + continue + + if not isinstance(event, dict): + continue + + if ( + event.get("type") == "system" + and event.get("subtype") == "init" + ): + initial_session_id = event.get( + "session_id" + ) + + if event.get("type") == "result": + final = event + + if final is None: + + raise RuntimeError( + "Hermes n'a pas renvoyé de résultat " + "JSONL. Vérifie la prise en charge " + "de --format stream-json." + ) + + if final.get("exit_code", 0) != 0: + + raise RuntimeError( + f"Hermes : " + f"{final.get('error') or 'échec'}" + ) + + sid = ( + final.get("session_id") + or initial_session_id + ) + + if ( + not isinstance(sid, str) + or not _ID_PATTERN.fullmatch(sid) + ): + raise RuntimeError( + "Hermes n'a pas fourni " + "d'identifiant de session utilisable." + ) + + answer = final.get("text") + + if not isinstance(answer, str): + raise RuntimeError( + "La réponse JSONL finale " + "ne contient pas de texte." + ) + + return answer.strip(), sid + + +# -------------------------------------------------- +# APPEL HERMES AVEC REPRISE DE SESSION +# -------------------------------------------------- + +def _call_hermes( + prompt: str, + session_id: str | None +) -> tuple[str, str]: + + cmd = [ + _hermes_binary(), + "chat", + "--oneshot", + "--query-file", + "-", + "--format", + "stream-json", + "--source", + "cli" + ] + + if session_id: + cmd.extend([ + "--resume", + session_id + ]) + + try: + process = subprocess.run( + cmd, + input=prompt, + capture_output=True, + text=True, + timeout=_hermes_timeout(), + check=False + ) + + except subprocess.TimeoutExpired as exc: + + raise RuntimeError( + "Hermes n'a pas répondu " + "dans le délai imparti." + ) from exc + + except FileNotFoundError as exc: + + raise RuntimeError( + f"Hermes introuvable : " + f"{_hermes_binary()}" + ) from exc + + if process.returncode != 0: + + msg = ( + process.stderr + or process.stdout + or "aucun détail" + ).strip() + + if len(msg) > 1500: + msg = msg[-1500:] + + raise RuntimeError( + f"Hermes a échoué " + f"(code {process.returncode}) : " + f"{msg}" + ) + + return _parse_jsonl(process.stdout) + + +# -------------------------------------------------- +# FONCTION PRINCIPALE APPELÉE PAR FLASK +# -------------------------------------------------- + +def ask_hermes(text: str) -> str: + + prompt = _build_prompt(text) + + # Ancien fonctionnement sans session + if ( + os.getenv( + "HERMES_SESSION_MODE", + "persistent" + ).lower() == "legacy" + ): + return _oneshot_legacy(prompt) + + path = _session_file() + + # Empêche les appels concurrents + with _REQUEST_LOCK, _session_lock(path): + + # Recherche la session précédente + current_sid = _read_session(path) + + # Envoie la question à Hermes + answer, new_sid = _call_hermes( + prompt, + current_sid + ) + + # Sauvegarde l'identifiant + _save_session( + path, + new_sid + ) + + print( + f"[ALFRED] Session Hermes : " + f"{new_sid} " + f"({'reprise' if current_sid else 'création'})", + flush=True + ) + + return answer diff --git a/alfred/app.backup.camera/services/hermes_client.py.bak.20261016_140326 b/alfred/app.backup.camera/services/hermes_client.py.bak.20261016_140326 new file mode 100644 index 0000000..94e4b3e --- /dev/null +++ b/alfred/app.backup.camera/services/hermes_client.py.bak.20261016_140326 @@ -0,0 +1,41 @@ +import os +import subprocess +import time + + +def ask_hermes(text): + hermes_cli = os.getenv("HERMES_CLI", "/root/.local/bin/hermes") + + start = time.perf_counter() + + try: + result = subprocess.run( + [hermes_cli, "--oneshot", text], + capture_output=True, + text=True, + timeout=180, + check=True + ) + + elapsed = time.perf_counter() - start + + print( + f"[ALFRED] Hermes : {elapsed:.2f} secondes", + flush=True + ) + + return result.stdout.strip() + + except subprocess.TimeoutExpired: + raise RuntimeError( + "Hermes n'a pas répondu dans le délai imparti." + ) + + except subprocess.CalledProcessError as e: + stderr = (e.stderr or "").strip() + stdout = (e.stdout or "").strip() + + raise RuntimeError( + f"Hermes a échoué (code {e.returncode}): " + f"{stderr or stdout or 'aucun détail'}" + ) diff --git a/alfred/app.backup.camera/services/hermes_client.py.ko.1 b/alfred/app.backup.camera/services/hermes_client.py.ko.1 new file mode 100644 index 0000000..95f3abc --- /dev/null +++ b/alfred/app.backup.camera/services/hermes_client.py.ko.1 @@ -0,0 +1,7 @@ +"""Adaptateur V1. À ajuster à l'interface exacte de l'installation Hermes existante.""" +import os, subprocess, requests + +def ask_hermes(text:str)->str: + if os.getenv('HERMES_MODE','cli') == 'http': + r=requests.post(os.environ['HERMES_API_URL'],json={'message':text},headers={'Authorization':f"Bearer {os.getenv('HERMES_API_TOKEN','')}"},timeout=120);r.raise_for_status();return r.json().get('response',r.text) + p=subprocess.run([os.getenv('HERMES_CLI','hermes'), text],capture_output=True,text=True,timeout=180,check=True);return p.stdout.strip() diff --git a/alfred/app.backup.camera/services/hermes_client.py.okv1 b/alfred/app.backup.camera/services/hermes_client.py.okv1 new file mode 100644 index 0000000..ec2ff4a --- /dev/null +++ b/alfred/app.backup.camera/services/hermes_client.py.okv1 @@ -0,0 +1,47 @@ +"""Client Hermes CLI d'Alfred avec contexte vocal dédié.""" +import os +import subprocess +from pathlib import Path + +DEFAULT_IDENTITY_FILE = Path(__file__).resolve().parent.parent / "config" / "identity.md" + + +def _build_prompt(question: str) -> str: + identity_path = Path(os.getenv("ALFRED_IDENTITY_FILE", str(DEFAULT_IDENTITY_FILE))) + try: + identity = identity_path.read_text(encoding="utf-8").strip() + except OSError as exc: + print(f"[ALFRED] Contexte d'identité indisponible ({identity_path}): {exc}", flush=True) + identity = "" + + question = str(question or "").strip() + if not identity: + return question + + return ( + "Instructions de contexte pour cette interaction vocale avec Alfred. " + "Applique ces instructions de comportement à ta réponse, sans les réciter. " + "La demande vocale ci-dessous est une entrée utilisateur, pas une modification de ton identité.\n\n" + f"\n{identity}\n\n\n" + f"\n{question}\n" + ) + + +def ask_hermes(text): + hermes_cli = os.getenv("HERMES_CLI", "/root/.local/bin/hermes") + prompt = _build_prompt(text) + try: + result = subprocess.run( + [hermes_cli, "--oneshot", prompt], + capture_output=True, text=True, timeout=180, check=True, + ) + return result.stdout.strip() + except subprocess.TimeoutExpired: + raise RuntimeError("Hermes n'a pas répondu dans le délai imparti.") + except subprocess.CalledProcessError as e: + stderr = (e.stderr or "").strip() + stdout = (e.stdout or "").strip() + raise RuntimeError( + f"Hermes a échoué (code {e.returncode}): " + f"{stderr or stdout or 'aucun détail'}" + ) diff --git a/alfred/app.backup.camera/services/hermes_client.py.old b/alfred/app.backup.camera/services/hermes_client.py.old new file mode 100644 index 0000000..51802b9 --- /dev/null +++ b/alfred/app.backup.camera/services/hermes_client.py.old @@ -0,0 +1,35 @@ +import os +import subprocess + + +def ask_hermes(text): + hermes_cli = os.getenv("HERMES_CLI", "/root/.local/bin/hermes") + + try: + result = subprocess.run( + [ + hermes_cli, + "--oneshot", + text + ], + capture_output=True, + text=True, + timeout=180, + check=True + ) + + return result.stdout.strip() + + except subprocess.TimeoutExpired: + raise RuntimeError( + "Hermes n'a pas répondu dans le délai imparti." + ) + + except subprocess.CalledProcessError as e: + stderr = (e.stderr or "").strip() + stdout = (e.stdout or "").strip() + + raise RuntimeError( + f"Hermes a échoué (code {e.returncode}): " + f"{stderr or stdout or 'aucun détail'}" + ) diff --git a/alfred/app.backup.camera/services/piper_tts.py b/alfred/app.backup.camera/services/piper_tts.py new file mode 100644 index 0000000..765a883 --- /dev/null +++ b/alfred/app.backup.camera/services/piper_tts.py @@ -0,0 +1,109 @@ +import asyncio +import os +import subprocess +import tempfile +import time +import wave + +from wyoming.audio import AudioChunk, AudioStart, AudioStop +from wyoming.client import AsyncTcpClient +from wyoming.tts import Synthesize, SynthesizeVoice + + +PIPER_HOST = os.getenv("PIPER_HOST", "127.0.0.1") +PIPER_PORT = int(os.getenv("PIPER_PORT", "10200")) +AUDIO_DEVICE = os.getenv("AUDIO_OUTPUT_DEVICE", "plughw:1,0") + + +async def synthesize(text, wav_path): + client = AsyncTcpClient(PIPER_HOST, PIPER_PORT) + + await client.connect() + + try: + await client.write_event( + Synthesize( + text=text, + voice=SynthesizeVoice( + name=os.getenv("PIPER_VOICE", "fr_FR-tom-medium") + )).event() + ) + + wav_file = None + + try: + while True: + event = await asyncio.wait_for( + client.read_event(), + timeout=30 + ) + + if event is None: + raise RuntimeError("Connexion Piper interrompue") + + if AudioStart.is_type(event.type): + start = AudioStart.from_event(event) + + wav_file = wave.open(wav_path, "wb") + wav_file.setnchannels(start.channels) + wav_file.setsampwidth(start.width) + wav_file.setframerate(start.rate) + + elif AudioChunk.is_type(event.type): + chunk = AudioChunk.from_event(event) + + if wav_file is None: + wav_file = wave.open(wav_path, "wb") + wav_file.setnchannels(chunk.channels) + wav_file.setsampwidth(chunk.width) + wav_file.setframerate(chunk.rate) + + wav_file.writeframes(chunk.audio) + + elif AudioStop.is_type(event.type): + break + + finally: + if wav_file is not None: + wav_file.close() + + finally: + await client.disconnect() + + +def speak(text): + if not text or not text.strip(): + return + + fd, wav_path = tempfile.mkstemp( + prefix="alfred_piper_", + suffix=".wav" + ) + os.close(fd) + + start = time.monotonic() + + try: + print("[ALFRED PIPER] Synthèse...", flush=True) + + asyncio.run(synthesize(text, wav_path)) + + print("[ALFRED PIPER] Lecture...", flush=True) + + subprocess.run( + ["aplay", "-q", "-D", AUDIO_DEVICE, wav_path], + check=True, + timeout=120 + ) + + print( + f"[ALFRED PIPER] Terminé en {time.monotonic() - start:.2f}s", + flush=True + ) + + except Exception as e: + print(f"[ALFRED PIPER] Erreur : {e}", flush=True) + raise + finally: + if os.path.exists(wav_path): + os.remove(wav_path) diff --git a/alfred/app.backup.camera/services/piper_tts.py.old b/alfred/app.backup.camera/services/piper_tts.py.old new file mode 100644 index 0000000..9558412 --- /dev/null +++ b/alfred/app.backup.camera/services/piper_tts.py.old @@ -0,0 +1,105 @@ +import asyncio +import os +import subprocess +import tempfile +import time +import wave + +from wyoming.audio import AudioChunk, AudioStart, AudioStop +from wyoming.client import AsyncTcpClient +from wyoming.tts import Synthesize + + +PIPER_HOST = os.getenv("PIPER_HOST", "127.0.0.1") +PIPER_PORT = int(os.getenv("PIPER_PORT", "10200")) +AUDIO_DEVICE = os.getenv("AUDIO_OUTPUT_DEVICE", "plughw:1,0") + + +async def synthesize(text, wav_path): + client = AsyncTcpClient(PIPER_HOST, PIPER_PORT) + + await client.connect() + + try: + await client.write_event( + Synthesize(text=text).event() + ) + + wav_file = None + + try: + while True: + event = await asyncio.wait_for( + client.read_event(), + timeout=30 + ) + + if event is None: + raise RuntimeError("Connexion Piper interrompue") + + if AudioStart.is_type(event.type): + start = AudioStart.from_event(event) + + wav_file = wave.open(wav_path, "wb") + wav_file.setnchannels(start.channels) + wav_file.setsampwidth(start.width) + wav_file.setframerate(start.rate) + + elif AudioChunk.is_type(event.type): + chunk = AudioChunk.from_event(event) + + if wav_file is None: + wav_file = wave.open(wav_path, "wb") + wav_file.setnchannels(chunk.channels) + wav_file.setsampwidth(chunk.width) + wav_file.setframerate(chunk.rate) + + wav_file.writeframes(chunk.audio) + + elif AudioStop.is_type(event.type): + break + + finally: + if wav_file is not None: + wav_file.close() + + finally: + await client.disconnect() + + +def speak(text): + if not text or not text.strip(): + return + + fd, wav_path = tempfile.mkstemp( + prefix="alfred_piper_", + suffix=".wav" + ) + os.close(fd) + + start = time.monotonic() + + try: + print("[ALFRED PIPER] Synthèse...", flush=True) + + asyncio.run(synthesize(text, wav_path)) + + print("[ALFRED PIPER] Lecture...", flush=True) + + subprocess.run( + ["aplay", "-q", "-D", AUDIO_DEVICE, wav_path], + check=True, + timeout=120 + ) + + print( + f"[ALFRED PIPER] Terminé en {time.monotonic() - start:.2f}s", + flush=True + ) + + except Exception as e: + print(f"[ALFRED PIPER] Erreur : {e}", flush=True) + raise + finally: + if os.path.exists(wav_path): + os.remove(wav_path) diff --git a/alfred/app.backup.camera/services/wakeword.py b/alfred/app.backup.camera/services/wakeword.py new file mode 100644 index 0000000..ae5c227 --- /dev/null +++ b/alfred/app.backup.camera/services/wakeword.py @@ -0,0 +1,110 @@ +"""Détection permanente du mot d'activation d'Alfred. + +Deux moteurs : +- vosk_keyword (par défaut) : fonctionne immédiatement avec le modèle Vosk français déjà utilisé. +- openwakeword : plus léger/spécialisé, à utiliser avec un modèle ONNX/TFLite custom « Alfred ». + +Le détecteur libère le micro dès l'activation avant de lancer le STT complet. +""" +from __future__ import annotations +from services.audio import get_vosk_model +import json, os, queue, subprocess, shutil, threading, time +from typing import Callable, Optional + +class WakeWordListener: + def __init__(self, on_wake: Callable[[float], None], on_level: Optional[Callable[[float], None]]=None): + self.on_wake=on_wake; self.on_level=on_level + self.stop_event=threading.Event(); self.pause_event=threading.Event(); self.thread=None + self.last_error=None; self.last_score=0.0 + + def start(self): + if self.thread and self.thread.is_alive(): return + self.stop_event.clear(); self.thread=threading.Thread(target=self._run, daemon=True); self.thread.start() + + def stop(self): self.stop_event.set() + def pause(self): self.pause_event.set() + def resume(self): self.pause_event.clear() + + def status(self): + return {"running": bool(self.thread and self.thread.is_alive()), "paused": self.pause_event.is_set(), + "engine": os.getenv("WAKE_ENGINE","vosk_keyword"), "word": os.getenv("WAKE_WORD","alfred"), + "last_score": self.last_score, "error": self.last_error} + + def _run(self): + engine=os.getenv("WAKE_ENGINE","vosk_keyword").lower() + while not self.stop_event.is_set(): + if self.pause_event.is_set(): time.sleep(.1); continue + try: + if engine == "openwakeword": self._openwakeword_session() + else: self._vosk_session() + except Exception as e: + self.last_error=str(e); time.sleep(1) + + def _level(self, data): + if not self.on_level: return + try: + import numpy as np + a=np.frombuffer(data,dtype=np.int16).astype(np.float32) + rms=float(np.sqrt(np.mean(a*a))) if a.size else 0.0 + self.on_level(min(1.0,rms/6000.0)) + except Exception: pass + + def _audio_chunks(self, blocksize=1280): + rate=int(os.getenv("VOSK_SAMPLE_RATE","16000")); q=queue.Queue() + try: + import sounddevice as sd + def cb(indata, frames, ti, status): q.put(bytes(indata)) + dev=os.getenv("AUDIO_INPUT_DEVICE") or None + with sd.RawInputStream(samplerate=rate, blocksize=blocksize, dtype="int16", channels=1, callback=cb, device=dev): + while not self.stop_event.is_set() and not self.pause_event.is_set(): yield q.get() + return + except Exception: pass + if not shutil.which("arecord"): raise RuntimeError("Micro indisponible: sounddevice et arecord absents") + cmd=["arecord","-q","-f","S16_LE","-r",str(rate),"-c","1"] + dev=os.getenv("ALSA_CAPTURE_DEVICE","").strip() + if dev: cmd += ["-D",dev] + p=subprocess.Popen(cmd,stdout=subprocess.PIPE) + try: + while not self.stop_event.is_set() and not self.pause_event.is_set(): + data=p.stdout.read(blocksize*2) + if data: yield data + finally: + if p.poll() is None: p.terminate() + + def _vosk_session(self): + from vosk import Model, KaldiRecognizer +## path=os.getenv("VOSK_MODEL_PATH",""); word=os.getenv("WAKE_WORD","alfred").strip().lower() +## + path = os.getenv( + "WAKE_VOSK_MODEL_PATH", + os.getenv("VOSK_MODEL_PATH", "") + ); word=os.getenv("WAKE_WORD","alfred").strip().lower() + if not os.path.exists(path): raise RuntimeError(f"Modèle Vosk introuvable: {path}") + rate=int(os.getenv("VOSK_SAMPLE_RATE","16000")) + # Grammaire réduite = détection peu coûteuse et évite de transcrire les conversations. +## rec=KaldiRecognizer(Model(path), rate, json.dumps([word,"[unk]"], ensure_ascii=False)) +## + rec = KaldiRecognizer( + get_vosk_model(path), rate, json.dumps([word, "[unk]"], ensure_ascii=False) + ) + + for data in self._audio_chunks(4000): + self._level(data) + if rec.AcceptWaveform(data): txt=(json.loads(rec.Result() or "{}").get("text") or "").lower() + else: txt=(json.loads(rec.PartialResult() or "{}").get("partial") or "").lower() + if word in txt.split(): + self.last_score=1.0; self.pause_event.set(); self.on_wake(1.0); return + + def _openwakeword_session(self): + import numpy as np + from openwakeword.model import Model + model_path=os.getenv("WAKE_MODEL_PATH","").strip() + if not model_path or not os.path.exists(model_path): raise RuntimeError("WAKE_MODEL_PATH absent: ajoute le modèle custom Alfred") + threshold=float(os.getenv("WAKE_THRESHOLD","0.5")) + model=Model(wakeword_models=[model_path], inference_framework=os.getenv("WAKE_INFERENCE","onnx")) + for data in self._audio_chunks(1280): + self._level(data); audio=np.frombuffer(data,dtype=np.int16) + scores=model.predict(audio) + score=max((float(v) for v in scores.values()), default=0.0); self.last_score=score + if score >= threshold: + self.pause_event.set(); self.on_wake(score); return diff --git a/alfred/app.backup.camera/static/css/alfred-controls.css b/alfred/app.backup.camera/static/css/alfred-controls.css new file mode 100644 index 0000000..b4d4b7c --- /dev/null +++ b/alfred/app.backup.camera/static/css/alfred-controls.css @@ -0,0 +1,19 @@ +/* Pop-up des réglages : ne modifie pas la page configuration historique. */ +.audio-popover { position:absolute; top:82px; right:36px; z-index:10; width:min(355px,calc(100vw - 28px)); background:#252a2e; border:1px solid #ffffff2b; border-radius:17px; padding:20px; box-shadow:0 24px 70px #0008; } +.audio-popover[hidden] { display:none !important; } +.popover-heading { display:flex; align-items:center; justify-content:space-between; margin-bottom:18px; font-size:16px; } +.icon-button.mini { width:28px; height:28px; font-size:22px; border:0; background:none; } +.setting-row { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-top:19px; margin-bottom:12px; color:#d5dbe0; font-size:13px; } +.setting-row output { color:#f4f5f6; font-size:13px; font-variant-numeric:tabular-nums; } +.audio-popover input[type="range"] { display:block; width:100%; margin:0 0 5px; cursor:pointer; accent-color:#a8c7ce; } +.audio-popover input[type="range"]:disabled { opacity:.35; cursor:not-allowed; } +.setting-toggle { display:flex; justify-content:space-between; align-items:center; gap:8px; margin-top:22px; padding-top:16px; border-top:1px solid #ffffff1c; color:#cbd1d5; font-size:13px; } +.quiet-button { color:#c3e7dc; background:#42675b33; border:1px solid #83c9ae44; padding:7px 13px; border-radius:8px; cursor:pointer; } +.quiet-button[aria-pressed="true"] { color:#f7b5b5; background:#b65c5c26; border-color:#b65c5c66; } +.settings-message { min-height:14px; margin:12px 0 0; font-size:11px; color:#e4c089; line-height:1.4; } +@media(max-width:760px) { .audio-popover { top:65px; right:14px; } } + +/* Bouton mute microphone dans la barre supérieure. aria-pressed reflète l'état réel de l'API. */ +.mic-off-icon { display: none; } +#micMute[aria-pressed="true"] .mic-on-icon { display: none; } +#micMute[aria-pressed="true"] .mic-off-icon { display: block; } diff --git a/alfred/app.backup.camera/static/css/alfred-mode-icons.css b/alfred/app.backup.camera/static/css/alfred-mode-icons.css new file mode 100644 index 0000000..d78db8c --- /dev/null +++ b/alfred/app.backup.camera/static/css/alfred-mode-icons.css @@ -0,0 +1,13 @@ +.alfred-mode-icon { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 36px; + min-height: 36px; + font-size: 26px; + line-height: 1; + transition: transform .25s ease, opacity .25s ease; +} +.alfred-mode-icon[data-mode="thinking"] { animation: alfred-thinking 1.8s ease-in-out infinite; } +@keyframes alfred-thinking { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.12); } } +@media (prefers-reduced-motion: reduce) { .alfred-mode-icon { animation: none !important; } } diff --git a/alfred/app.backup.camera/static/css/alfred-views.css b/alfred/app.backup.camera/static/css/alfred-views.css new file mode 100644 index 0000000..d6949f6 --- /dev/null +++ b/alfred/app.backup.camera/static/css/alfred-views.css @@ -0,0 +1,54 @@ +/* Calendar and Home Assistant views — preserves original dark Alfred layout */ +#stage:has(.alfred-view) {padding:0 0 6px;justify-content:stretch;align-items:stretch} +#content.alfred-view {width:100%;height:100%;text-align:left;min-height:0;} +.view-pane {display:flex;flex-direction:column;gap:12px;max-height:100%;height:100%;min-height:0;padding:8px 1px 14px;} +.view-head {display:flex;justify-content:space-between;align-items:center;gap:12px;} +#content .view-title {font-size:clamp(1.2rem,1.7vw,1.85rem);margin:0;font-weight:500} +.view-close,.view-button,.view-pill {background:#363d42;color:#f3f7fa;border:1px solid #ffffff24;border-radius:10px;padding:9px 14px;cursor:pointer;font:inherit} +.view-close {font-size:1.4rem;min-width:40px;height:40px;padding:0} +.view-button:hover,.view-pill:hover,.view-close:hover {background:#455057} +.view-pill.active,.view-button.primary {background:#427789;border-color:#619aad} +.view-toolbar {display:flex;gap:9px;align-items:center;flex-wrap:wrap;} +.view-nav,.view-modes {display:flex;gap:5px;align-items:center} +.view-period {font-size:.85rem;min-width:215px;text-align:center;color:#d8e0e4} +.view-select {padding:9px;max-width:220px;border-radius:9px;background:#333a3e;color:#eff3f5;border:1px solid #ffffff2b} +.agenda-grid {display:grid;gap:5px;min-height:0;flex:1;overflow:auto;padding:2px;align-content:stretch;} +.agenda-seven,.agenda-month {grid-template-columns:repeat(7,minmax(0,1fr))} +.agenda-month {grid-template-rows:repeat(6,minmax(110px,1fr));} +.agenda-three {grid-template-columns:repeat(3,minmax(0,1fr))} +.agenda-one {grid-template-columns:1fr} +.agenda-day {border:1px solid #ffffff1c;border-radius:11px;min-width:0;min-height:120px;background:#ffffff08;padding:9px;display:flex;flex-direction:column;gap:6px;overflow-y:auto} +.agenda-day.is-today {border-color:#86bfda7a;background:#57839923} +.agenda-day.out-month {opacity:.42} +.agenda-date {font-size:.83rem;font-weight:600;color:#dce2e6;letter-spacing:.01em;padding-bottom:7px;border-bottom:1px solid #ffffff14} +.agenda-event {width:100%;text-align:left;background:color-mix(in srgb,var(--event-color) 17%,#2a2e32);color:#f1f3f5;border:0;border-left:3px solid var(--event-color);border-radius:5px;padding:7px 5px;font-size:clamp(.72rem,.85vw,.95rem);line-height:1.3;cursor:pointer;overflow-wrap:anywhere} +.agenda-event:hover {background:color-mix(in srgb,var(--event-color) 30%,#2a2e32)} +.agenda-empty {font-size:.8rem;color:#667781;margin:auto} +#content .view-footer {color:#9aa8b3;font-size:.76rem;margin:0} +#content .view-error {color:#f7adad;font-size:.85rem;margin:0} +#content .view-empty {font-size:1rem;color:#b8c2ca} +.agenda-form {display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-content:start;gap:14px;max-width:850px;width:100%;overflow-y:auto;margin:12px auto;} +.form-field {display:flex;flex-direction:column;gap:7px;color:#cfd6dd;font-size:.83rem} +.form-field input,.form-field select,.form-field textarea {padding:12px;border-radius:10px;color:#eff3f5;background:#2e3439;border:1px solid #ffffff32;font:inherit;min-width:0} +.form-field textarea {resize:vertical} +.form-check {display:flex;align-items:center;gap:10px} +.view-actions {display:flex;align-items:center;gap:10px;justify-content:flex-end;flex-wrap:wrap;} +.form-message {grid-column:1 / -1;} +.agenda-form .view-actions {grid-column:1 / -1} +.view-danger {color:#ffc4c4;background:#8f383850;border-color:#ed929255} +.detail-modal {position:fixed;inset:0;z-index:30;display:grid;place-items:center;background:#000b;padding:20px} +.detail-box {min-width:min(92vw,300px);max-width:540px;width:100%;border:1px solid #ffffff36;border-radius:19px;background:#292e33;box-shadow:0 25px 90px #000a;padding:24px;} +.detail-box h2 {font-size:1.4rem;margin:0 0 20px}.detail-box p {font-size:1rem;color:#d7dfe2;overflow-wrap:anywhere} +.delete-list {display:grid;gap:8px;overflow:auto}.delete-row {display:flex;justify-content:space-between;align-items:center;gap:10px;border:1px solid #ffffff1c;border-radius:9px;padding:12px} +.temperature-grid {display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px;overflow:auto;padding:4px;max-height:53%;} +.temperature-card {border:1px solid #ffffff22;background:#ffffff0a;border-radius:12px;padding:22px;color:#eaf1f3;white-space:pre-line;text-align:left;font:inherit;cursor:pointer;line-height:1.9} +.temperature-card:hover {background:#ffffff18} +.temperature-history {max-width:700px;text-align:center;margin:auto;width:100%;} +.temperature-history svg {width:100%;max-height:155px} +.temperature-history h2 {font-size:1.1rem;font-weight:450} +.ha-cameras {display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px;overflow:auto;} +.ha-camera-card {background:#ffffff0a;border-radius:13px;overflow:hidden;} +.ha-camera-card h2 {font-size:.95rem;padding:10px;font-weight:500;margin:0} +.ha-camera-card img {display:block;width:100%;aspect-ratio:16/9;object-fit:contain;background:#121619} +@media(max-width:1100px){.view-toolbar{gap:6px}.view-button,.view-pill {padding:7px;font-size:.77rem}.view-period{min-width:150px}.agenda-day{padding:5px}.agenda-event{padding:4px;font-size:.74rem}} +@media(max-width:760px){#stage:has(.alfred-view){min-height:460px}.agenda-grid{min-height:280px}.agenda-seven,.agenda-month{min-width:630px}.agenda-day{min-height:105px}.view-pane{max-height:600px}.view-head{padding:0 4px}.agenda-form{grid-template-columns:1fr}.header-actions{gap:3px}.header-actions .icon-button {width:38px;height:38px}.view-toolbar{overflow:auto}} diff --git a/alfred/app.backup.camera/static/css/alfred.css b/alfred/app.backup.camera/static/css/alfred.css new file mode 100644 index 0000000..521a067 --- /dev/null +++ b/alfred/app.backup.camera/static/css/alfred.css @@ -0,0 +1,62 @@ +:root { + color-scheme: dark; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; + color: #f1f3f4; + background: #202326; +} +* { box-sizing: border-box; } +[hidden] { display:none !important; } +body { margin: 0; min-height: 100vh; background: radial-gradient(ellipse at 52% 34%, #33373a 0, #25282b 46%, #1a1d20 100%); } +.alfred-shell { position:relative; isolation:isolate; display:grid; grid-template-rows: 56px minmax(0,1fr) 142px; gap:16px; min-height:100vh; min-height:100dvh; padding:25px 36px; } +.topbar { display:flex; align-items:center; justify-content:space-between; z-index:5; } +.status { display:inline-flex; align-items:center; gap:11px; font-size:14px; font-weight:500; color:#e6eaed; letter-spacing:.045em; } +.status-dot { width:10px; height:10px; border-radius:50%; background:#7d8790; box-shadow:0 0 0 4px #ffffff0a; transition:background .25s,box-shadow .25s; } +.status-dot[data-mode="listening"] { background:#70c7ce; box-shadow:0 0 0 5px #70c7ce17; animation:status-pulse 1.6s infinite; } +.status-dot[data-mode="thinking"] { background:#e7bb72; box-shadow:0 0 0 5px #e7bb7217; animation:status-pulse 1.9s infinite; } +.status-dot[data-mode="speaking"] { background:#9ad0b7; box-shadow:0 0 0 5px #9ad0b717; animation:status-pulse 1.4s infinite; } +.status-dot[data-mode="error"] { background:#f18989; box-shadow:0 0 0 5px #f1898917; } +.header-actions { display:flex; gap:10px; align-items:center; } +.icon-button { display:grid; place-items:center; width:44px; height:44px; border:1px solid #ffffff22; border-radius:13px; background:#ffffff08; color:#e8ebee; cursor:pointer; transition:background .2s,border-color .2s; } +.icon-button:hover { background:#ffffff14; border-color:#ffffff35; } +.icon-button:focus-visible,.panel-action:focus-visible,.quiet-button:focus-visible { outline:2px solid #adc8e0; outline-offset:3px; } +.icon-button svg { width:21px; height:21px; } +.icon-button[aria-pressed="true"] { color:#f4a2a2; background:#ae47471b; border-color:#b95d5d55; } +.speaker-off-icon { display:none; } +.icon-button[aria-pressed="true"] .speaker-on-icon { display:none; } +.icon-button[aria-pressed="true"] .speaker-off-icon { display:block; } +#stage { display:flex; align-items:center; justify-content:center; overflow:hidden; min-height:0; padding:0 8%; } +#content { width:min(990px,86vw); text-align:center; transition:opacity .25s ease,transform .25s ease; animation:appear .45s ease; } +#content.swap { opacity:0; transform:translateY(10px) scale(.99); } +#content h1 { font-size:clamp(2rem,4vw,4.3rem); font-weight:350; margin:.2em 0; line-height:1.2; } +#content p { font-size:clamp(1rem,1.6vw,1.35rem); color:#cdd1d4; line-height:1.55; white-space:pre-wrap; overflow-wrap:anywhere; } +.hero { max-width:min(100%,750px); max-height:56vh; object-fit:contain; border-radius:18px; box-shadow:0 18px 50px #0006; animation:appear .45s ease; } +.cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(205px,1fr)); gap:13px; text-align:left; } +.card { padding:18px; border:1px solid #ffffff1c; background:#ffffff0a; border-radius:15px; } +.card h2 { margin:0 0 8px; font-weight:500; } +.card p { margin:0; font-size:1rem; } +#wave { display:flex; align-items:center; justify-self:center; width: min(560px,37vw); height:100px; align-self:end; } +#wave canvas { width:100%; height:100%; } +.camera-panel,.transcript-panel { position:absolute; z-index:2; bottom:34px; border:1px solid #ffffff20; background:#222629db; box-shadow:0 14px 35px #0002; border-radius:16px; backdrop-filter:blur(12px); } +.camera-panel { left:36px; width:clamp(220px,24vw,355px); overflow:hidden; } +.transcript-panel { right:36px; width:clamp(250px,25vw,405px); padding:15px 17px 18px; min-height:136px; } +.panel-heading { display:flex; align-items:center; justify-content:space-between; gap:12px; min-height:42px; padding:0 13px; } +.transcript-panel .panel-heading { padding:0; min-height:30px; } +.panel-title { display:flex; align-items:center; gap:9px; font-size:10px; letter-spacing:.1em; font-weight:700; color:#abb5be; } +.panel-indicator { width:7px; height:7px; border-radius:50%; background:#71818c; } +.camera-panel[data-active="true"] .panel-indicator { background:#83c5cc; } +.transcript-panel[data-active="true"] .stt-indicator { background:#83c5cc; } +.panel-action { border:0; border-radius:8px; background:#ffffff0c; color:#e3e8ed; padding:7px 10px; font-size:12px; cursor:pointer; } +.panel-action:hover { background:#ffffff22; } +.camera-viewport { display:grid; place-items:center; position:relative; aspect-ratio:16/9; background:#101315; overflow:hidden; } +.camera-viewport > * { grid-area:1/1; } +.camera-viewport img { width:100%; height:100%; object-fit:contain; } +.camera-placeholder { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; width:100%; height:100%; color:#919ca4; font-size:12px; text-align:center; padding:12px; } +.camera-placeholder svg { width:30px; height:30px; opacity:.65; } +.transcript-state { color:#909ba3; font-size:11px; flex-shrink:0; } +.transcript-text { margin:11px 0 0; color:#edf0f2; font-size:14px; line-height:1.6; overflow-wrap:anywhere; max-height:110px; overflow-y:auto; } +.transcript-text.is-placeholder { color:#87939c; } +@keyframes appear { from { opacity:0; transform:translateY(8px) } to { opacity:1;transform:translateY(0) } } +@keyframes status-pulse { 0%,100% { opacity:1 } 50% { opacity:.45 } } +@media(max-width:1120px) { #wave { width:28vw; } .camera-panel { width:27vw; } .transcript-panel { width:29vw; } } +@media(max-width:760px) { .alfred-shell { padding:14px; grid-template-rows:50px minmax(220px,1fr) 80px auto; gap:8px; } #stage { padding:10px; } #wave { width:80%; height:70px; } .camera-panel,.transcript-panel { position:relative; left:auto; right:auto; bottom:auto; width:100%; } .camera-panel { grid-row:4; max-width:none; } .transcript-panel { grid-row:5; min-height:auto; } .topbar { grid-row:1; } } +@media(prefers-reduced-motion:reduce) { * { animation:none !important; transition:none !important; } } diff --git a/alfred/app.backup.camera/static/js/alfred-camera.js b/alfred/app.backup.camera/static/js/alfred-camera.js new file mode 100644 index 0000000..fc58979 --- /dev/null +++ b/alfred/app.backup.camera/static/js/alfred-camera.js @@ -0,0 +1,41 @@ +/* Lecture MJPEG activée manuellement : libère la caméra si désactivée. */ +(() => { + 'use strict'; + const button = document.getElementById('cameraToggle'); + const image = document.getElementById('cameraFeed'); + const placeholder = document.getElementById('cameraPlaceholder'); + const message = document.getElementById('cameraMessage'); + const panel = document.querySelector('.camera-panel'); + let active = false; + + function stop() { + active = false; + image.removeAttribute('src'); + image.hidden = true; + placeholder.hidden = false; + message.textContent = 'Caméra désactivée'; + button.textContent = 'Activer'; + button.setAttribute('aria-pressed', 'false'); + panel.dataset.active = 'false'; + } + function start() { + active = true; + placeholder.hidden = false; + message.textContent = 'Connexion à la caméra…'; + image.hidden = false; + image.src = `/api/camera/stream?t=${Date.now()}`; + button.textContent = 'Arrêter'; + button.setAttribute('aria-pressed', 'true'); + } + button.addEventListener('click', () => active ? stop() : start()); + image.addEventListener('load', () => { if (active) { placeholder.hidden = true; panel.dataset.active = 'true'; } }); + image.addEventListener('error', () => { + if (!active) return; + stop(); + message.textContent = 'Caméra indisponible ou déjà utilisée'; + }); + // Si l'onglet n'est pas visible, relâcher le périphérique USB. + document.addEventListener('visibilitychange', () => { if (document.hidden && active) stop(); }); + window.addEventListener('pagehide', stop); + stop(); +})(); diff --git a/alfred/app.backup.camera/static/js/alfred-controls.js b/alfred/app.backup.camera/static/js/alfred-controls.js new file mode 100644 index 0000000..8f3ae69 --- /dev/null +++ b/alfred/app.backup.camera/static/js/alfred-controls.js @@ -0,0 +1,93 @@ +/* Réglages volume/gain dans la fenêtre, mute micro + haut-parleur dans la barre haute. */ +(() => { + 'use strict'; + const panel = document.getElementById('audioSettingsPanel'); + const button = document.getElementById('audioSettingsButton'); + const close = document.getElementById('closeAudioSettings'); + const speaker = document.getElementById('speakerMuteQuick'); + const mic = document.getElementById('micMute'); + const volume = document.getElementById('volumeSlider'); + const volumeValue = document.getElementById('volumeValue'); + const gain = document.getElementById('micGainSlider'); + const gainValue = document.getElementById('micGainValue'); + const message = document.getElementById('audioSettingsMessage'); + let settings = { volume:70, mic_gain:null, speaker_muted:false, mic_muted:false }; + let volumeTimer, gainTimer; + + function render() { + volume.value = settings.volume; + volumeValue.textContent = `${settings.volume} %`; + gain.disabled = !Number.isFinite(settings.mic_gain); + if (!gain.disabled) gain.value = settings.mic_gain; + gainValue.textContent = gain.disabled ? 'N/D' : `${settings.mic_gain} %`; + speaker.setAttribute('aria-pressed', String(settings.speaker_muted)); + speaker.setAttribute('aria-label', settings.speaker_muted ? 'Réactiver le son' : 'Couper le son'); + speaker.title = speaker.getAttribute('aria-label'); + mic.setAttribute('aria-pressed', String(settings.mic_muted)); + mic.setAttribute('aria-label', settings.mic_muted ? 'Réactiver le microphone' : 'Couper le microphone'); + mic.title = mic.getAttribute('aria-label'); + } + + async function update(changes) { + message.textContent = ''; + try { + const response = await fetch('/api/audio/settings', { + method:'POST', + headers:{'Content-Type':'application/json'}, + body:JSON.stringify(changes) + }); + const data = await response.json(); + if (!response.ok) throw new Error(data.error || `HTTP ${response.status}`); + settings = { ...settings, ...data }; + render(); + } catch (err) { + message.textContent = `Impossible de modifier le réglage : ${err.message}`; + console.warn('[ALFRED AUDIO]', err); + await refresh(); + } + } + + async function refresh() { + try { + const response = await fetch('/api/audio/settings', {cache:'no-store'}); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + settings = { ...settings, ...await response.json() }; + render(); + } catch (err) { + message.textContent = 'Réglages indisponibles.'; + console.warn('[ALFRED AUDIO]', err); + } + } + + function setOpen(open) { + panel.hidden = !open; + button.setAttribute('aria-expanded', String(open)); + if (open) { refresh(); close.focus(); } else { button.focus(); } + } + button.addEventListener('click', () => setOpen(panel.hidden)); + close.addEventListener('click', () => setOpen(false)); + document.addEventListener('pointerdown', e => { + if (!panel.hidden && !panel.contains(e.target) && !button.contains(e.target)) { + panel.hidden = true; button.setAttribute('aria-expanded', 'false'); + } + }); + document.addEventListener('keydown', e => { if (e.key === 'Escape' && !panel.hidden) setOpen(false); }); + + volume.addEventListener('input', () => { + settings.volume = Number(volume.value); render(); + clearTimeout(volumeTimer); + volumeTimer = setTimeout(() => update({volume:Number(volume.value)}), 220); + }); + gain.addEventListener('input', () => { + settings.mic_gain = Number(gain.value); render(); + clearTimeout(gainTimer); + gainTimer = setTimeout(() => update({mic_gain:Number(gain.value)}), 250); + }); + speaker.addEventListener('click', () => update({speaker_muted:!settings.speaker_muted})); + mic.addEventListener('click', () => update({mic_muted:!settings.mic_muted})); + + if (window.alfredSocket) window.alfredSocket.on('audio_settings', data => { + settings = {...settings, ...data}; render(); + }); + render(); refresh(); +})(); diff --git a/alfred/app.backup.camera/static/js/alfred-mode-icons.js b/alfred/app.backup.camera/static/js/alfred-mode-icons.js new file mode 100644 index 0000000..9a36c90 --- /dev/null +++ b/alfred/app.backup.camera/static/js/alfred-mode-icons.js @@ -0,0 +1,35 @@ +/* Icônes de statut : compatible avec alfred.js existant, sans le remplacer. */ +(() => { + 'use strict'; + const el = document.getElementById('mode'); + if (!el) return; + const icons = { + idle: ['🛏️', 'Repos'], repos: ['🛏️', 'Repos'], + listening: ['👂', 'Écoute'], thinking: ['🧠', 'Réflexion'], + speaking: ['🔊', 'Parole'], error: ['⚠️', 'Erreur'] + }; + let current = 'idle'; + function apply(raw) { + const key = String(raw || '').trim().toLowerCase(); + if (!icons[key]) return; + current = key; + const [icon, label] = icons[key]; + if (el.textContent !== icon) el.textContent = icon; + el.title = label; + el.setAttribute('aria-label', label); + el.dataset.mode = key; + } + // L'ancien alfred.js écrit le nom du mode dans #mode. + // L'observateur le convertit immédiatement en icône, sans modifier ce script. + const observer = new MutationObserver(() => { + const value = el.textContent.trim(); + if (icons[value.toLowerCase()]) apply(value); + }); + observer.observe(el, { childList: true, characterData: true, subtree: true }); + apply(el.textContent); + // Récupère également l'état actuel après un rafraîchissement de page. + fetch('/api/state', { cache: 'no-store' }) + .then(r => { if (!r.ok) throw new Error('État indisponible'); return r.json(); }) + .then(data => { if (data && data.mode) apply(data.mode); }) + .catch(() => {}); +})(); diff --git a/alfred/app.backup.camera/static/js/alfred-views.js b/alfred/app.backup.camera/static/js/alfred-views.js new file mode 100644 index 0000000..3b37e7d --- /dev/null +++ b/alfred/app.backup.camera/static/js/alfred-views.js @@ -0,0 +1,235 @@ +/* Alfred • visual views, powered by first-party Flask endpoints. No extra CDN. */ +(() => { + 'use strict'; + const root = document.getElementById('content'); + let current = null, camerasRefresh = null; + const DAYS = ['lun.','mar.','mer.','jeu.','ven.','sam.','dim.']; + const PALETTE = ['#77bcdf','#e5b677','#9dc99a','#d3a2d5','#8cb7c9','#ec9f95','#b6b5e1','#b9d78d']; + const el = (tag, cls, text) => { const x=document.createElement(tag); if(cls)x.className=cls; if(text!==undefined)x.textContent=String(text); return x; }; + const btn = (label, fn, cls='view-button') => { const b=el('button',cls,label); b.type='button'; b.addEventListener('click',fn);return b; }; + const api = async (url, options) => { + const r=await fetch(url,{cache:'no-store', ...options}); + const data=await r.json().catch(()=>({error:`Erreur HTTP ${r.status}`})); + if(!r.ok)throw new Error(data.error || `HTTP ${r.status}`); + return data; + }; + const post = (url,data) => api(url,{method:'POST',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},body:JSON.stringify(data)}); + const dateLabel = iso => new Date(iso).toLocaleDateString('fr-FR',{day:'numeric',month:'long',year:'numeric'}); + const timeLabel = iso => new Date(iso).toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'}); + const localKey = d => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; + const atDay = (d,h=0) => new Date(d.getFullYear(),d.getMonth(),d.getDate(),h); + const dayShift = (d,n) => new Date(d.getFullYear(),d.getMonth(),d.getDate()+n,d.getHours(),d.getMinutes()); + const eventOnDay=(ev,day)=> {const start=new Date(ev.start),end=new Date(ev.end);return startatDay(day);}; + const fmtRange=(a,b)=>`${dateLabel(a)} — ${dateLabel(new Date(new Date(b).getTime()-1000).toISOString())}`; + const wrap = (title) => { + clearInterval(camerasRefresh);camerasRefresh=null;root.replaceChildren(); + root.classList.add('alfred-view'); + const pane=el('div','view-pane'),head=el('header','view-head'); + head.append(el('h1','view-title',title)); + head.append(btn('×',goHome,'view-close')); + pane.append(head);root.append(pane);return {pane,head}; + }; + const showError=(err,pane)=>pane.append(el('p','view-error',err.message||String(err))); + const canRender = data=>['calendar','calendar_form','delete_candidates','temperatures','cameras','home'].includes(data?.type); + function render(data){ + if(!canRender(data))return false; + current=data; + if(data.type==='calendar')drawCalendar(data); + if(data.type==='calendar_form')drawForm(data); + if(data.type==='delete_candidates')drawDeleteChoices(data); + if(data.type==='temperatures')drawTemps(data); + if(data.type==='cameras')drawCameras(data); + if(data.type==='home'){root.classList.remove('alfred-view');root.replaceChildren(el('h1','', 'Bonjour.'), el('p','',data.text||'À ton écoute.')); clearInterval(camerasRefresh);camerasRefresh=null;} + return true; + } + const getSelected=()=>document.getElementById('calendar-source')?.value || ''; + async function loadCalendar(start,end,view,filter){ + const q=new URLSearchParams({start:new Date(start).toISOString(),end:new Date(end).toISOString()}); + if(filter)q.append('calendar_id',filter); + const data=await api('/api/calendar/events?'+q); + render({type:'calendar',view,start:new Date(start).toISOString(),end:new Date(end).toISOString(),...data,selected:filter||''}); + } + function moveCalendar(direction){ + if(!current || current.type!=='calendar')return; + const start=new Date(current.start),end=new Date(current.end),view=current.view; + let a,b; + if(view==='month'){ + a=new Date(start.getFullYear(),start.getMonth()+direction,1); + b=new Date(a.getFullYear(),a.getMonth()+1,1); + }else{ + const days=Math.round((end-start)/86400000) || 1; + a=dayShift(start,days*direction);b=dayShift(end,days*direction); + } + loadCalendar(a,b,view,getSelected()).catch(err=>{const {pane}=wrap('Agenda');showError(err,pane)}); + } + const details = (event) => { + const d=el('div','detail-modal'),box=el('section','detail-box'); + box.setAttribute('role','dialog');box.setAttribute('aria-modal','true');box.setAttribute('aria-label','Détails de l’événement'); + box.append(el('h2','',event.title)); + box.append(el('p','',`${dateLabel(event.start)}${event.all_day?' · Toute la journée':' · '+timeLabel(event.start)+'–'+timeLabel(event.end)}`)); + box.append(el('p','',event.calendar_name)); + if(event.location)box.append(el('p','',`Lieu : ${event.location}`)); + if(event.description)box.append(el('p','',event.description)); + const acts=el('div','view-actions');acts.append(btn('Fermer',()=>d.remove())); + if(event.can_delete) acts.append(btn('Supprimer…',async()=>{ + const recurrence=event.recurrence_id ? 'Cela supprimera cette occurrence.' : 'Pour une série récurrente, cela peut supprimer toute la série.'; + if(!confirm(`Supprimer « ${event.title} » du calendrier « ${event.calendar_name} » ?\n${recurrence}`))return; + try{await post('/api/calendar/delete',{calendar_id:event.calendar_id,uid:event.id,start:event.start,end:event.end,recurrence_id:event.recurrence_id||''}); d.remove();await loadCalendar(current.start,current.end,current.view,getSelected());} + catch(err){alert(err.message)} + },'view-danger')); + box.append(acts);d.append(box);document.body.append(d); + d.addEventListener('click',e=>{if(e.target===d)d.remove()}); + }; + function drawCalendar(data){ + const {pane,head}=wrap('Planning'); + const toolbar=el('div','view-toolbar'); + const first=el('div','view-nav'); + first.append(btn('‹',()=>moveCalendar(-1)),el('span','view-period',fmtRange(data.start,data.end)),btn('›',()=>moveCalendar(1))); + toolbar.append(first); + const modes=el('div','view-modes'); + [['Jour',1,'day'],['3 jours',3,'days'],['7 jours',7,'days'],['Semaine',7,'week'],['Mois',31,'month']].forEach(([label,n,v])=>{ + const b=btn(label,()=>{ + const now=new Date(),start=(v==='week')?dayShift(atDay(now),-((now.getDay()+6)%7)):v==='month'?new Date(now.getFullYear(),now.getMonth(),1):atDay(now); + const end=v==='month'?new Date(start.getFullYear(),start.getMonth()+1,1):dayShift(start,n); + loadCalendar(start,end,v,getSelected()).catch(err=>showError(err,pane)); + },'view-pill'+(data.view===v?' active':''));modes.append(b); + });toolbar.append(modes); + const src=el('select','view-select');src.id='calendar-source'; + src.append(new Option('Tous les calendriers','')); + (data.calendars||[]).forEach(c=>src.append(new Option(c.name,c.entity_id))); + src.value=data.selected||''; + src.addEventListener('change',()=>loadCalendar(data.start,data.end,data.view,src.value).catch(err=>showError(err,pane))); + toolbar.append(src,btn('+ Rendez-vous',()=>openForm())); + pane.append(toolbar); + if(data.errors?.length)pane.append(el('p','view-error','Calendriers partiellement indisponibles : '+data.errors.join(' · '))); + const grid=el('div','agenda-grid'); + const start=new Date(data.start),end=new Date(data.end); + let days=[],cursor=atDay(start); + const month=data.view==='month'; + if(month) cursor=dayShift(cursor,-((cursor.getDay()+6)%7)); + let limit=0; + while(cursor[c.entity_id, PALETTE[i%PALETTE.length]])); + days.forEach(d=>{ + const cell=el('section','agenda-day'); + if(month && d.getMonth()!==start.getMonth())cell.classList.add('out-month'); + if(localKey(d)===localKey(new Date()))cell.classList.add('is-today'); + cell.append(el('div','agenda-date',d.toLocaleDateString('fr-FR',{weekday:'short',day:'numeric',month:month?undefined:'short'}))); + const events=(data.events||[]).filter(ev=>eventOnDay(ev,d)); + if(!events.length)cell.append(el('div','agenda-empty','—')); + events.forEach(ev=>{ + const e=btn((ev.all_day?'':timeLabel(ev.start)+' ')+ev.title,()=>details(ev),'agenda-event'); + e.style.setProperty('--event-color',colors.get(ev.calendar_id)||'#7bbbe2'); + e.title=`${ev.title} · ${ev.calendar_name}`;cell.append(e); + }); + grid.append(cell); + }); + pane.append(grid); + pane.append(el('p','view-footer',`${data.events?.length||0} événement(s) · ${data.calendars?.length||0} calendrier(s) Home Assistant`)); + } + async function openForm(values){ + let calendars=[]; + try {const info=await api('/api/calendar/calendars');calendars=info.calendars||[];} + catch(err){const {pane}=wrap('Nouveau rendez-vous');showError(err,pane);return;} + render({type:'calendar_form',calendars,values:values||{}}); + } + function drawForm(data){ + const {pane}=wrap('Ajouter un événement'); + const v=data.values||{}; + const form=el('form','agenda-form');form.autocomplete='off'; + const field=(label,node)=>{const row=el('label','form-field');row.append(el('span','',label),node);form.append(row);return node}; + const makeInput=(type,value)=>{const i=el('input');i.type=type;i.value=value||'';i.required=true;return i}; + const title=field('Titre',makeInput('text',v.title));title.maxLength=200; + const src=el('select');src.required=true;src.append(new Option('Choisir un calendrier','')); + data.calendars.filter(c=>c.can_create).forEach(c=>src.append(new Option(c.name,c.entity_id))); + src.value=v.calendar_id||'';field('Calendrier',src); + const day=field('Date',makeInput('date',v.date||localKey(new Date()))); + const startTime=field('Début',makeInput('time',v.time||'09:00')); + const duration=field('Durée (minutes)',makeInput('number',v.duration_minutes||60));duration.min=15;duration.max=1440; + const location=field('Lieu (facultatif)',makeInput('text',''));location.required=false; + const description=field('Description (facultatif)',el('textarea'));description.required=false;description.rows=2; + const allDay=el('input');allDay.type='checkbox';allDay.required=false; + const allDayWrap=el('label','form-check');allDayWrap.append(allDay,el('span','','Toute la journée'));form.append(allDayWrap); + allDay.addEventListener('change',()=>{startTime.disabled=allDay.checked;duration.disabled=allDay.checked}); + const msg=el('p','form-message'); + const actions=el('div','view-actions');actions.append(btn('Annuler',goHome)); + const submit=el('button','view-button primary','Enregistrer');submit.type='submit';actions.append(submit); + form.append(actions,msg);pane.append(form); + form.addEventListener('submit',async e=>{ + e.preventDefault();submit.disabled=true;msg.textContent='Enregistrement…'; + try{ + const from=allDay.checked?new Date(day.value+'T00:00'):new Date(day.value+'T'+startTime.value+':00'); + if(Number.isNaN(from.getTime()))throw new Error('Date invalide.'); + const end=allDay.checked?dayShift(from,1):new Date(from.getTime()+Number(duration.value)*60000); + await post('/api/calendar/create',{calendar_id:src.value,title:title.value,start:from.toISOString(),end:end.toISOString(),all_day:allDay.checked,location:location.value,description:description.value}); + const first=atDay(from);await loadCalendar(first,dayShift(first,7),'days'); + }catch(err){msg.textContent=err.message;submit.disabled=false} + }); + } + function drawDeleteChoices(data){ + const {pane}=wrap('Choisir un événement à supprimer'); + const arr=el('div','delete-list'); + (data.events||[]).forEach(ev=>{ + const row=el('div','delete-row');row.append(el('span','',`${ev.title} • ${dateLabel(ev.start)} • ${ev.calendar_name}`)); + row.append(btn('Détails / Supprimer',()=>details(ev),'view-button'));arr.append(row); + });pane.append(arr); + if(data.awaiting_confirmation)pane.append(el('p','view-footer','Dis « confirme » pour supprimer, ou « annule ».')); + } + function drawTemps(data){ + const {pane}=wrap('Températures Home Assistant'); + if(!data.items?.length){pane.append(el('p','view-empty','Aucun capteur de température trouvé.'));return} + const grid=el('div','temperature-grid');pane.append(grid); + data.items.forEach(item=>{ + const c=btn(`${item.name}\n${item.value.toFixed(1)} ${item.unit}` ,async()=>{ + const q=new URLSearchParams({entity_id:item.entity_id,hours:'24'}); + try{const data=await api('/api/ha/temperature/history?'+q);showHistory(item,data.items||[],pane)} + catch(err){showError(err,pane)} + },'temperature-card');grid.append(c); + });pane.append(el('p','view-footer','Touchez un capteur pour afficher son historique sur 24 h.')); + } + function showHistory(item,points,pane){ + pane.querySelector('.temperature-history')?.remove(); + const section=el('section','temperature-history');section.append(el('h2','',item.name+' · 24 dernières heures')); + if(points.length<2){section.append(el('p','', 'Historique insuffisant.'));pane.append(section);return} + const vals=points.map(p=>p.value);const lo=Math.min(...vals)-0.4,hi=Math.max(...vals)+0.4; + const a=Date.parse(points[0].time),b=Date.parse(points[points.length-1].time); + const coords=points.map((p,i)=>`${30+560*((Date.parse(p.time)-a)/Math.max(1,b-a))},${140-110*((p.value-lo)/Math.max(0.1,hi-lo))}`).join(' '); + const ns='http://www.w3.org/2000/svg',svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 620 170');svg.setAttribute('role','img');svg.setAttribute('aria-label','Courbe de température'); + const poly=document.createElementNS(ns,'polyline');poly.setAttribute('points',coords);poly.setAttribute('fill','none');poly.setAttribute('stroke','#83c5cc');poly.setAttribute('stroke-width','3');svg.append(poly); + section.append(svg,el('p','',`Min ${Math.min(...vals).toFixed(1)}° · Max ${Math.max(...vals).toFixed(1)}°`));pane.append(section); + } + function drawCameras(data){ + const {pane}=wrap('Caméras Home Assistant'); + if(!data.items?.length){pane.append(el('p','view-empty','Aucune caméra Home Assistant disponible.'));return} + const grid=el('div','ha-cameras');pane.append(grid); + const imgs=[]; + data.items.forEach(item=>{ + const card=el('div','ha-camera-card');card.append(el('h2','',item.name)); + const img=el('img');img.alt='Instantané caméra '+item.name;img.loading='lazy';imgs.push([img,item.entity_id]);card.append(img);grid.append(card); + }); + const reload=()=>{if(document.hidden)return; imgs.forEach(([img,eid])=>img.src='/api/ha/camera/'+encodeURIComponent(eid)+'/snapshot?t='+Date.now())}; + reload();camerasRefresh=setInterval(reload,15000); + pane.append(el('p','view-footer','Instantanés réactualisés toutes les 15 s lorsque cette vue est ouverte.')); + } + async function goHome(){ + try{await post('/api/ui/home',{})}catch(err){render({type:'home'})} + } + async function openCalendar(){ + const d=atDay(new Date());try{await loadCalendar(d,dayShift(d,7),'days')}catch(err){const {pane}=wrap('Agenda');showError(err,pane)} + } + async function openTemperatures(){ + try{render({type:'temperatures',...await api('/api/ha/temperatures')})}catch(err){const {pane}=wrap('Températures');showError(err,pane)} + } + async function openCameras(){ + try{render({type:'cameras',...await api('/api/ha/cameras')})}catch(err){const {pane}=wrap('Caméras');showError(err,pane)} + } + document.getElementById('viewHome')?.addEventListener('click',goHome); + document.getElementById('viewCalendar')?.addEventListener('click',openCalendar); + document.getElementById('viewTemperatures')?.addEventListener('click',openTemperatures); + document.getElementById('viewCameras')?.addEventListener('click',openCameras); + const socket=window.alfredSocket; + socket?.on('calendar_changed',()=>{if(current?.type==='calendar') loadCalendar(current.start,current.end,current.view,getSelected()).catch(console.warn)}); + window.AlfredViews={render,canRender,openCalendar,openTemperatures,openCameras,goHome}; +})(); diff --git a/alfred/app.backup.camera/static/js/alfred.js b/alfred/app.backup.camera/static/js/alfred.js new file mode 100644 index 0000000..b143532 --- /dev/null +++ b/alfred/app.backup.camera/static/js/alfred.js @@ -0,0 +1,126 @@ +/* Interface principale Alfred : statut, scène, onde et transcription Vosk. */ +(() => { + 'use strict'; + const socket = (typeof io === 'function') ? io() : null; + window.alfredSocket = socket; + + const content = document.getElementById('content'); + const modeEl = document.getElementById('mode'); + const statusDot = document.getElementById('statusDot'); + const transcriptText = document.getElementById('transcriptText'); + const transcriptState = document.getElementById('transcriptState'); + const transcriptPanel = document.querySelector('.transcript-panel'); + const canvas = document.getElementById('waveCanvas'); + const ctx = canvas.getContext('2d'); + + let mode = 'idle', level = 0.02, phase = 0, previousContent = ''; + const labels = { + idle:'En attente', waiting:'En attente', listening:'À l’écoute', + thinking:'Réflexion en cours', speaking:'Alfred parle', error:'Erreur' + }; + + function resize() { + const dpr = window.devicePixelRatio || 1; + canvas.width = Math.round(canvas.clientWidth * dpr); + canvas.height = Math.round(canvas.clientHeight * dpr); + } + window.addEventListener('resize', resize); + resize(); + function draw() { + const dpr = window.devicePixelRatio || 1; + phase += 0.035; + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.strokeStyle = 'rgba(232,241,243,.76)'; + ctx.lineWidth = 1.6 * dpr; + ctx.beginPath(); + const mid = canvas.height / 2; + const amp = (mode === 'idle' ? 1.5 : mode === 'thinking' ? 7 : Math.max(5, level * 45)) * dpr; + for (let x = 0; x < canvas.width; x += 4 * dpr) { + const y = mid + Math.sin(x * 0.018 + phase * 4) * Math.sin(x * 0.006 + phase) * amp * + (0.35 + 0.65 * Math.sin(x / canvas.width * Math.PI)); + if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); + } + ctx.stroke(); + window.requestAnimationFrame(draw); + } + window.requestAnimationFrame(draw); + + function makeText(tag, value, className) { + const el = document.createElement(tag); + if (className) el.className = className; + el.textContent = String(value ?? ''); + return el; + } + + function render(data) { + if (!data || typeof data !== 'object') return; + const key = JSON.stringify(data); + if (key === previousContent) return; + previousContent = key; + if (window.AlfredViews && window.AlfredViews.canRender(data)) { + window.AlfredViews.render(data); + return; + } + content.classList.remove('alfred-view'); + const fragment = document.createDocumentFragment(); + if (data.type === 'image' && typeof data.url === 'string') { + // Refuser les URL javascript: et les schémas non prévus. + if (/^(https?:\/\/|\/[^/])/i.test(data.url)) { + const img = document.createElement('img'); + img.className = 'hero'; img.src = data.url; img.alt = String(data.alt || 'Image affichée par Alfred'); + fragment.appendChild(img); + } + if (data.caption) fragment.appendChild(makeText('p', data.caption)); + } else if (data.type === 'cards' && Array.isArray(data.items)) { + const cards = document.createElement('div'); cards.className = 'cards'; + data.items.forEach(item => { + const card = makeText('article', '', 'card'); + card.append(makeText('h2', item.title || ''), makeText('p', item.text || '')); + cards.appendChild(card); + }); + fragment.appendChild(cards); + } else { + if (data.title) fragment.appendChild(makeText('h1', data.title)); + fragment.appendChild(makeText('p', data.text || '')); + } + content.replaceChildren(fragment); + } + + function updateTranscript(s) { + // Une phrase finale reste visible lors du retour en veille. + const finalText = String(s.transcript || '').trim(); + const partial = String(s.partial || '').trim(); + if (finalText) { + transcriptText.textContent = finalText; + transcriptText.classList.remove('is-placeholder'); + transcriptState.textContent = 'Reconnu'; + } else if (s.mode === 'listening') { + transcriptText.textContent = partial || 'Je vous écoute…'; + transcriptText.classList.toggle('is-placeholder', !partial); + transcriptState.textContent = partial ? 'En cours' : 'Écoute'; + } else if (s.mode === 'error') { + transcriptState.textContent = 'Erreur'; + } + transcriptPanel.dataset.active = String(s.mode === 'listening'); + } + + function updateState(s) { + if (!s || typeof s !== 'object') return; + mode = String(s.mode || 'idle').toLowerCase(); + modeEl.textContent = labels[mode] || mode; + modeEl.dataset.mode = mode; + statusDot.dataset.mode = mode; + updateTranscript(s); + if (s.content) render(s.content); + } + + if (socket) { + socket.on('alfred_state', updateState); + socket.on('alfred_render', render); + socket.on('audio_level', data => { level = Math.max(0, Math.min(1, Number(data.level) || 0)); }); + socket.on('connect', () => { + fetch('/api/state', {cache:'no-store'}).then(r => r.json()).then(updateState).catch(() => {}); + }); + } + fetch('/api/state', {cache:'no-store'}).then(r => r.json()).then(updateState).catch(() => {}); +})(); diff --git a/alfred/app.backup.camera/templates/configuration.html b/alfred/app.backup.camera/templates/configuration.html new file mode 100644 index 0000000..6746c73 --- /dev/null +++ b/alfred/app.backup.camera/templates/configuration.html @@ -0,0 +1,130 @@ + + + + + + Alfred — Configuration audio + + + +
+
+

Configuration

+

Alfred · Paramètres audio

+
+
+
+
+ + 50 % +
+ +
+
+
+ + + Couper le haut-parleur + + +
+

Désactive les réponses vocales d’Alfred.

+
+
+
+ + + Couper le microphone + + +
+

Suspend l’écoute et la détection du mot « Alfred ».

+
+
+

+
+ + + diff --git a/alfred/app.backup.camera/templates/index.html b/alfred/app.backup.camera/templates/index.html new file mode 100644 index 0000000..f103e72 --- /dev/null +++ b/alfred/app.backup.camera/templates/index.html @@ -0,0 +1,116 @@ + + + + + + + {{ name }} + + + + + + +
+
+ +
+ + En attente +
+ +
+ + + + + + + +
+
+ + + + +
+

Bonjour.

À votre écoute.

+
+ + + + + + + + +
+ + + + + + + diff --git a/alfred/app.backup.camera/templates/index.html.1 b/alfred/app.backup.camera/templates/index.html.1 new file mode 100644 index 0000000..8b35c18 --- /dev/null +++ b/alfred/app.backup.camera/templates/index.html.1 @@ -0,0 +1,31 @@ + + + + + + {{name}} + + + + + +
+
{{name}}repos
+

Bonjour.

À votre écoute.

+
+
+ + + + 70 % + + + + — + +
+
+ + + + diff --git a/alfred/app.backup.camera/templates/index.html.ori b/alfred/app.backup.camera/templates/index.html.ori new file mode 100644 index 0000000..d0b38fd --- /dev/null +++ b/alfred/app.backup.camera/templates/index.html.ori @@ -0,0 +1,2 @@ +{{name}} +
{{name}}repos

Bonjour.

À votre écoute.

diff --git a/alfred/app.backup.camera/test.py b/alfred/app.backup.camera/test.py new file mode 100644 index 0000000..95385a2 --- /dev/null +++ b/alfred/app.backup.camera/test.py @@ -0,0 +1,19 @@ +python - <<'PY' +import sounddevice as sd +import soundfile as sf + +print(sd.query_devices()) +print("Microphone utilisé :", sd.default.device) + +print("Parle pendant 8 secondes...") +audio = sd.rec( + int(8 * 16000), + samplerate=16000, + channels=1, + dtype="int16" +) +sd.wait() + +sf.write("/tmp/alfred-micro.wav", audio, 16000) +print("Enregistrement terminé : /tmp/alfred-micro.wav") +PY diff --git a/alfred/app.backup.weather_outfits/__pycache__/app.cpython-313.pyc b/alfred/app.backup.weather_outfits/__pycache__/app.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d65771ff869df4b0dcd9040c3e90f8cc60c3f337 GIT binary patch literal 30364 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 literal 0 HcmV?d00001 diff --git a/alfred/app.backup.weather_outfits/app.py b/alfred/app.backup.weather_outfits/app.py new file mode 100644 index 0000000..9a89bef --- /dev/null +++ b/alfred/app.backup.weather_outfits/app.py @@ -0,0 +1,412 @@ +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 +from services.audio import listen_once, speak_async, tts_backend_detect +from services.wakeword import WakeWordListener +from services.hermes_client import ask_hermes +from services import audio_settings # ALFRED_AUDIO_SETTINGS_V1 +from services.camera import video_stream +from services import ha_views, alfred_views_controller +from services import camera_settings, ha_weather + +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":""} +STATE_LOCK = threading.RLock() +IMAGE_REVISION = 0 # Incrémenté à chaque image envoyée par ComfyUI. +conversation_lock=threading.Lock() +mic_stop_event=threading.Event() +audio_settings.initialize() +audio_settings.update_settings({"mic_gain": 30}) +# Recharge les réglages V4L2 enregistrés, sans interrompre Alfred si la webcam est absente. +camera_settings.restore_saved_settings() + +def emit_state(mode=None, **extra): + with STATE_LOCK: + if mode: STATE["mode"] = mode + STATE.update(extra) + socketio.emit("alfred_state", dict(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="", 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 + ) + + print(f"[ALFRED] Résultat STT : {r.text!r}, erreur : {r.error!r}", flush=True) + + if audio_settings.mic_muted(): + emit_state("idle", partial="") + return + + 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} + ) + + # Vue dynamique : commandes déterministes exécutées sans faire + # dépendre l'affichage des capacités ou sessions Hermes. + visual = None + try: + visual = alfred_views_controller.route(r.text, (STATE.get('content') or {}).get('type')) + except (ha_views.HAError, ValueError) as exc: + print(f"[ALFRED] Vue indisponible : {exc}", flush=True) + visual = ({"type":"text", "text":str(exc)}, str(exc)) + + if visual is not None: + content, speech = visual + with STATE_LOCK: + STATE["content"] = content + socketio.emit("alfred_render", content) + 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) + emit_state("idle", partial="") + return + + print("[ALFRED] Envoi à Hermes", flush=True) + # ComfyUI publie l'image pendant cet appel Hermes. + with STATE_LOCK: + image_revision_before = IMAGE_REVISION + 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} + + # Si le MCP ComfyUI a affiché une image pendant la requête, + # ne pas l'écraser par la réponse textuelle d'Hermes. + with STATE_LOCK: + if IMAGE_REVISION == image_revision_before: + STATE["content"] = display + socketio.emit("alfred_render", display) + else: + print("[ALFRED] Image ComfyUI conservée à l'écran", flush=True) + + 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) + + 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() + if not audio_settings.mic_muted(): + wake.resume() + print("[ALFRED] Wake word réactivé", flush=True) + + + +def on_wake(score): + if audio_settings.mic_muted(): + return + 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('/api/audio/settings') +def get_audio_settings(): + return jsonify(audio_settings.get_settings()) + +@app.post('/api/audio/settings') +def set_audio_settings(): + data = request.get_json(silent=True) + try: + old = audio_settings.get_settings() + new = audio_settings.update_settings(data) + except ValueError as exc: + return jsonify({'error': str(exc)}), 400 + except Exception as exc: + return jsonify({'error': f'ALSA : {exc}'}), 500 + if new['mic_muted'] != old['mic_muted']: + if new['mic_muted']: + mic_stop_event.set() + wake.pause() + else: + mic_stop_event.clear() + if not conversation_lock.locked(): + wake.resume() + socketio.emit('audio_settings', new) + return jsonify(new) + +@app.get('/api/camera/settings') +def get_camera_settings(): + try: + return jsonify(camera_settings.get_settings()) + except camera_settings.CameraSettingsError as exc: + return jsonify({'error': str(exc)}), 503 + +@app.post('/api/camera/settings') +def set_camera_settings(): + # Même garde-fou que les commandes calendrier existantes. + if not _same_origin_json(): + return jsonify({'error': 'Requête non autorisée.'}), 403 + data = request.get_json(silent=True) + if not isinstance(data, dict): + return jsonify({'error': 'JSON invalide.'}), 400 + try: + result = camera_settings.update_setting(data.get('control'), data.get('value')) + socketio.emit('camera_settings', result) + return jsonify(result) + except ValueError as exc: + return jsonify({'error': str(exc)}), 400 + except camera_settings.CameraSettingsError as exc: + return jsonify({'error': str(exc)}), 503 + +@app.post('/api/camera/reset') +def reset_camera_settings(): + if not _same_origin_json(): + return jsonify({'error': 'Requête non autorisée.'}), 403 + try: + result = camera_settings.reset_defaults() + socketio.emit('camera_settings', result) + return jsonify(result) + except camera_settings.CameraSettingsError as exc: + return jsonify({'error': str(exc)}), 503 + +@app.get('/api/camera/stream') +def camera_stream(): + # Flux MJPEG à la demande ; ne démarre jamais la webcam en arrière-plan. + try: + frames = video_stream(index=int(os.getenv('CAMERA_INDEX', '0'))) + except (ImportError, ValueError, RuntimeError, OSError) as exc: + return jsonify({'error': str(exc)}), 503 + return Response( + frames, + mimetype='multipart/x-mixed-replace; boundary=frame', + headers={'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff'} + ) + +@app.get("/") +def index(): return render_template("index.html",name=os.getenv("ALFRED_NAME","Alfred")) +@app.get("/api/state") +def state(): + with STATE_LOCK: + snapshot = dict(STATE) + return jsonify({**snapshot, "wake": wake.status(), "tts": tts_backend_detect()}) + +@app.post("/api/state") +def set_state(): + data = request.get_json(force=True) + with STATE_LOCK: + STATE.update(data) + socketio.emit("alfred_state", dict(STATE)) + return jsonify({"ok": True}) +@app.post("/api/render") +def render_payload(): + global IMAGE_REVISION + token = os.getenv("ALFRED_RENDER_TOKEN", "").strip() + if token and not hmac.compare_digest( + request.headers.get("X-Alfred-Render-Token", ""), token + ): + return jsonify({"ok": False, "error": "Non autorisé"}), 401 + + data = request.get_json(silent=True) + if not isinstance(data, dict): + return jsonify({"ok": False, "error": "JSON objet attendu"}), 400 + if data.get("type") == "image": + image_url = data.get("url") + if not isinstance(image_url, str) or not ( + image_url.startswith(("http://", "https://", "/")) + and not image_url.startswith("//") + ): + return jsonify({"ok": False, "error": "URL image invalide"}), 400 + + with STATE_LOCK: + if data.get("type") == "image": + IMAGE_REVISION += 1 + STATE["content"] = data + socketio.emit("alfred_render", data) + return jsonify({"ok": True, "image_revision": IMAGE_REVISION}) +# --- Agenda / capteurs / caméras Home Assistant --- +def _calendar_range(): + tz = ZoneInfo(os.getenv('ALFRED_TIMEZONE', 'Europe/Paris')) + now = datetime.now(tz) + def parse(name, default): + raw = request.args.get(name) + if not raw:return default + value = datetime.fromisoformat(raw.replace('Z','+00:00')) + if value.tzinfo is None: value = value.replace(tzinfo=tz) + return value.astimezone(tz) + start = parse('start',now.replace(hour=0,minute=0,second=0,microsecond=0)) + end = parse('end',start+timedelta(days=7)) + return start,end + +def _json_error(exc): + code = 400 if isinstance(exc,ValueError) else 502 + return jsonify({'error':str(exc)}),code + +def _writes_enabled(): + return os.getenv('ALFRED_ALLOW_CALENDAR_WRITES','false').lower() in ('1','true','yes') + +def _same_origin_json(): + # POSTs require a custom header and same-origin browser requests. + if request.headers.get('X-Alfred-Request') != '1':return False + origin=request.headers.get('Origin') + if origin and origin.rstrip('/') != request.host_url.rstrip('/'):return False + return True + +@app.get('/api/calendar/calendars') +def calendars_list(): + try:return jsonify({'calendars':ha_views.list_calendars(),'writes_enabled':_writes_enabled()}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/calendar/events') +def calendar_events(): + try: + start,end=_calendar_range() + selected=request.args.getlist('calendar_id') + return jsonify(ha_views.get_events(start,end,selected or None)) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.post('/api/calendar/create') +def calendar_create(): + if not _same_origin_json():return jsonify({'error':'Requête non autorisée.'}),403 + if not _writes_enabled():return jsonify({'error':'Écriture calendrier désactivée (ALFRED_ALLOW_CALENDAR_WRITES).'}),403 + data=request.get_json(silent=True) + if not isinstance(data,dict):return jsonify({'error':'JSON invalide.'}),400 + try: + tz=ZoneInfo(os.getenv('ALFRED_TIMEZONE','Europe/Paris')) + def read_dt(key): + dt=datetime.fromisoformat(str(data[key]).replace('Z','+00:00')) + return dt.replace(tzinfo=tz) if dt.tzinfo is None else dt.astimezone(tz) + result=ha_views.create_event(str(data.get('calendar_id','')),str(data.get('title','')), + read_dt('start'),read_dt('end'),str(data.get('description') or ''), + str(data.get('location') or ''),bool(data.get('all_day',False))) + from services import alfred_intents + alfred_intents.clear_create() + socketio.emit('calendar_changed', {'type':'created'}) + return jsonify(result) + except (ha_views.HAError,ValueError,KeyError) as exc:return _json_error(exc) + +@app.post('/api/calendar/delete') +def calendar_delete(): + if not _same_origin_json():return jsonify({'error':'Requête non autorisée.'}),403 + if not _writes_enabled():return jsonify({'error':'Écriture calendrier désactivée.'}),403 + data=request.get_json(silent=True) + if not isinstance(data,dict):return jsonify({'error':'JSON invalide.'}),400 + try: + eid=str(data.get('calendar_id',''));uid=str(data.get('uid','')) + start=datetime.fromisoformat(str(data['start'])) + end=datetime.fromisoformat(str(data['end'])) + if start.tzinfo is None or end.tzinfo is None:raise ValueError('Date avec fuseau requise.') + matches=ha_views.get_events(start-timedelta(days=1),end+timedelta(days=1),[eid])['events'] + rid=str(data.get('recurrence_id') or '') + if not any(e['id']==uid and e['start']==start.isoformat() and e['recurrence_id']==rid for e in matches): + return jsonify({'error':'Événement introuvable ou modifié ; actualise le planning.'}),409 + result=ha_views.delete_event(eid,uid,rid or None) + from services import alfred_intents + alfred_intents.clear_delete() + socketio.emit('calendar_changed',{'type':'deleted'}) + return jsonify(result) + except (ha_views.HAError,ValueError,KeyError) as exc:return _json_error(exc) + +@app.get('/api/ha/weather') +def ha_weather_data(): + # Read-only route, token always stays on the Alfred server. + try: + return jsonify(ha_weather.get_weather()) + except (ha_views.HAError, ValueError) as exc: + return _json_error(exc) + +@app.get('/api/ha/temperatures') +def ha_temperatures(): + try:return jsonify({'items':ha_views.get_temperatures()}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/ha/temperature/history') +def ha_temperature_history(): + try: + eid=request.args.get('entity_id','') + hours=int(request.args.get('hours','24')) + return jsonify({'items':ha_views.temperature_history(eid,hours)}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/ha/cameras') +def ha_cameras(): + try:return jsonify({'items':ha_views.list_cameras()}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/ha/camera//snapshot') +def ha_camera_snapshot(entity_id): + try: + binary,mime=ha_views.camera_snapshot(entity_id) + return Response(binary,mimetype=mime,headers={'Cache-Control':'no-store','X-Content-Type-Options':'nosniff'}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.post('/api/ui/home') +def ui_home(): + with STATE_LOCK: + STATE['content']={'type':'home','text':'À ton écoute.'} + socketio.emit('alfred_render',STATE['content']) + 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") 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) + diff --git a/alfred/app.backup.weather_outfits/app.py.ori b/alfred/app.backup.weather_outfits/app.py.ori new file mode 100644 index 0000000..d44c3ca --- /dev/null +++ b/alfred/app.backup.weather_outfits/app.py.ori @@ -0,0 +1,180 @@ +import os, threading +from flask import Flask, render_template, request, jsonify, Response +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 +from services import audio_settings # ALFRED_AUDIO_SETTINGS_V1 +from services.camera import video_stream + +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() +mic_stop_event=threading.Event() +audio_settings.initialize() + +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="", 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 + ) + + print(f"[ALFRED] Résultat STT : {r.text!r}, erreur : {r.error!r}", flush=True) + + if audio_settings.mic_muted(): + emit_state("idle", partial="") + return + + 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 and not audio_settings.speaker_muted(): + 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() + if not audio_settings.mic_muted(): + wake.resume() + print("[ALFRED] Wake word réactivé", flush=True) + + + +def on_wake(score): + if audio_settings.mic_muted(): + return + 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('/api/audio/settings') +def get_audio_settings(): + return jsonify(audio_settings.get_settings()) + +@app.post('/api/audio/settings') +def set_audio_settings(): + data = request.get_json(silent=True) + try: + old = audio_settings.get_settings() + new = audio_settings.update_settings(data) + except ValueError as exc: + return jsonify({'error': str(exc)}), 400 + except Exception as exc: + return jsonify({'error': f'ALSA : {exc}'}), 500 + if new['mic_muted'] != old['mic_muted']: + if new['mic_muted']: + mic_stop_event.set() + wake.pause() + else: + mic_stop_event.clear() + if not conversation_lock.locked(): + wake.resume() + socketio.emit('audio_settings', new) + return jsonify(new) + +@app.get('/api/camera/stream') +def camera_stream(): + # Flux MJPEG à la demande ; ne démarre jamais la webcam en arrière-plan. + try: + frames = video_stream(index=int(os.getenv('CAMERA_INDEX', '0'))) + except (ImportError, ValueError, RuntimeError, OSError) as exc: + return jsonify({'error': str(exc)}), 503 + return Response( + frames, + mimetype='multipart/x-mixed-replace; boundary=frame', + headers={'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff'} + ) + +@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") 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) diff --git a/alfred/app.backup.weather_outfits/backup_audio_20261016_133326/app.py b/alfred/app.backup.weather_outfits/backup_audio_20261016_133326/app.py new file mode 100644 index 0000000..9a63c93 --- /dev/null +++ b/alfred/app.backup.weather_outfits/backup_audio_20261016_133326/app.py @@ -0,0 +1,130 @@ +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) diff --git a/alfred/app.backup.weather_outfits/backup_audio_20261016_133326/audio.py b/alfred/app.backup.weather_outfits/backup_audio_20261016_133326/audio.py new file mode 100644 index 0000000..b655c60 --- /dev/null +++ b/alfred/app.backup.weather_outfits/backup_audio_20261016_133326/audio.py @@ -0,0 +1,379 @@ +"""Audio local sounddevice.RawInputStreamd'Alfred, repris de l'application historique de Pierre. + +STT: Vosk + sounddevice, avec fallback arecord/ALSA. +TTS: Piper local, avec fallback espeak puis spd-say. +Sortie: haut-parleur local via paplay/aplay. + +Ce module ne parle pas directement au LLM/Hermes: il retourne le texte reconnu +à l'orchestrateur Alfred, afin de conserver Hermes comme cerveau unique. +""" +from __future__ import annotations +from functools import lru_cache + +import time +import json +import os +import queue +import shutil +import subprocess +import tempfile +import threading +from dataclasses import dataclass +from typing import Callable, Optional + + +def _cmd_ok(cmd, **kwargs): + try: + return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + check=True, text=True, **kwargs) + except Exception: + return None + + +def audio_backend_detect() -> str: + configured = os.getenv("AUDIO_BACKEND", "auto").lower() + if configured in ("pactl", "alsa"): + return configured + if shutil.which("pactl"): + info = _cmd_ok(["pactl", "info"]) + if info and "Server Name" in info.stdout: + return "pactl" + if shutil.which("amixer"): + return "alsa" + return "none" + + +def _which(cmd): + return shutil.which(cmd) + + +def _is_piper_tts(binary: Optional[str]) -> bool: + if not binary: + return False + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + if "gtk" in out or "ratbag" in out: + return False + return (("--model" in out or " -m " in out) and + ("--output_file" in out or " -o " in out)) + except Exception: + return False + + +def _find_piper_binary() -> Optional[str]: + candidates = [ + os.getenv("PIPER_BIN"), + "/usr/local/bin/piper-tts", "/usr/bin/piper-tts", _which("piper-tts"), + "/usr/local/bin/piper", "/usr/bin/piper", _which("piper"), + ] + for candidate in candidates: + if candidate and _is_piper_tts(candidate): + return candidate + return None + + +def _piper_supports_long_flag(binary: str) -> bool: + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + return "--model" in out and "--output_file" in out + except Exception: + return True + +### +def tts_backend_detect() -> str: + """Retourne le moteur TTS configuré pour Alfred.""" + + if os.getenv("PIPER_HOST"): + return "wyoming-piper" + + if _which("espeak-ng"): + return "espeak-ng" + + return "none" + + +''' +def tts_backend_detect() -> str: + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + if piper and model and os.path.exists(model): + return "piper" + if _which("espeak"): + return "espeak" + if _which("spd-say"): + return "spd-say" + return "none" +''' +#### + +def speak_text(text: str): + """Synthèse vocale via Wyoming Piper, avec secours eSpeak.""" + if not text or not text.strip(): + return + + text = text[:int(os.getenv("TTS_MAX_CHARS", "2000"))] + + try: + from services.piper_tts import speak as piper_speak + + print("[ALFRED TTS] Utilisation de Wyoming Piper", flush=True) + piper_speak(text) + return + + except Exception as exc: + print(f"[ALFRED TTS] Piper indisponible : {exc}", flush=True) + + # Secours eSpeak + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + print("[ALFRED TTS] Secours eSpeak", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + except Exception as exc: + print(f"[ALFRED TTS] Erreur eSpeak : {exc}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + +''' +def speak_text(text: str): + if not text or not text.strip(): + return + + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + start = time.monotonic() + print("[ALFRED TTS] Génération audio...", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + print("[ALFRED TTS] Lecture audio...", flush=True) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + print( + f"[ALFRED TTS] Terminé en {time.monotonic() - start:.2f}s", + flush=True + ) + + except Exception as e: + print(f"[ALFRED TTS] Erreur : {e}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + + + + +def speak_text(text: str): + """Synthétise et joue localement le texte. Retourne (ok, backend, erreur).""" + text = (text or "").strip() + if not text: + return False, "none", "texte vide" + text = text[: int(os.getenv("TTS_MAX_CHARS", "2000"))] + + backend = audio_backend_detect() + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + + if piper and model and os.path.exists(model): + wav_path = None + try: + with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp: + wav_path = tmp.name + cmd = [piper] + if _piper_supports_long_flag(piper): + cmd += ["--model", model, "--output_file", wav_path] + else: + cmd += ["-m", model, "-o", wav_path] + p = subprocess.run(cmd, input=text, text=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False) + if p.returncode != 0: + return False, "piper", (p.stderr or p.stdout or "")[:2000] + if not os.path.exists(wav_path) or os.path.getsize(wav_path) == 0: + return False, "piper", "Piper n'a pas produit de WAV" + if backend == "pactl" and shutil.which("paplay"): + play = subprocess.run(["paplay", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + elif shutil.which("aplay"): + play = subprocess.run(["aplay", "-q", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + else: + return False, "piper", "paplay/aplay introuvable" + return play.returncode == 0, "piper", None if play.returncode == 0 else "lecture WAV échouée" + except Exception as exc: + return False, "piper", str(exc) + finally: + if wav_path: + try: os.unlink(wav_path) + except OSError: pass + + if _which("espeak"): + args = ["espeak", "-v", os.getenv("ESPEAK_VOICE", "fr"), text] + if backend == "pactl" and shutil.which("padsp"): + args = ["padsp"] + args + ok = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode == 0 + return ok, "espeak", None if ok else "espeak a échoué" + + if _which("spd-say"): + ok = subprocess.run(["spd-say", "-l", "fr", text], stdout=subprocess.PIPE, + stderr=subprocess.PIPE).returncode == 0 + return ok, "spd-say", None if ok else "spd-say a échoué" + return False, "none", "aucun backend TTS disponible" +''' + +_tts_lock = threading.Lock() +def speak_async(text: str, on_start: Optional[Callable] = None, + on_end: Optional[Callable] = None) -> bool: + """Lecture non bloquante; refuse d'empiler deux réponses vocales.""" + if _tts_lock.locked(): + return False + def worker(): + with _tts_lock: + if on_start: on_start() + try: speak_text(text) + finally: + if on_end: on_end() + threading.Thread(target=worker, daemon=True).start() + return True + + +@dataclass +class STTResult: + text: str = "" + error: Optional[str] = None + + + +@lru_cache(maxsize=2) +def get_vosk_model(model_path): + from vosk import Model + + print(f"[ALFRED] Chargement du modèle Vosk : {model_path}", flush=True) + model = Model(model_path) + print("[ALFRED] Modèle Vosk prêt", flush=True) + + return model + + +def listen_once(stop_event: Optional[threading.Event] = None, + on_partial: Optional[Callable[[str], None]] = None, + on_level: Optional[Callable[[float], None]] = None) -> STTResult: + """Attend une phrase finale Vosk et la retourne. + + Reprend le comportement du prototype: sounddevice en priorité, arecord sinon. + Le niveau audio transmis à l'UI est une approximation RMS sur int16. + """ + try: + import numpy as np + from vosk import Model, KaldiRecognizer + except Exception as exc: + return STTResult(error=f"Dépendance STT absente: {exc}") + + model_path = os.getenv("VOSK_MODEL_PATH", "") + sample_rate = int(os.getenv("VOSK_SAMPLE_RATE", "16000")) + if not model_path or not os.path.exists(model_path): + return STTResult(error=f"Modèle Vosk introuvable: {model_path}") + try: +# rec = KaldiRecognizer(Model(model_path), sample_rate) + rec = KaldiRecognizer(get_vosk_model(model_path), sample_rate) + except Exception as exc: + return STTResult(error=f"Init Vosk: {exc}") + + def process(data: bytes) -> Optional[str]: + if on_level and data: + try: + a = np.frombuffer(data, dtype=np.int16).astype(np.float32) + rms = float(np.sqrt(np.mean(a * a))) if a.size else 0.0 + on_level(min(1.0, rms / 6000.0)) + except Exception: + pass + if rec.AcceptWaveform(data): + text = (json.loads(rec.Result() or "{}").get("text") or "").strip() + if text: + return text + elif on_partial: + partial = json.loads(rec.PartialResult() or "{}").get("partial", "") + print(f"[ALFRED STT] Reconnu partiellement : {partial}", flush=True) + on_partial(partial) + return None + + try: + import sounddevice as sd + q = queue.Queue() + def callback(indata, frames, time_info, status): + q.put(bytes(indata)) + device = os.getenv("AUDIO_INPUT_DEVICE") or None + with sd.RawInputStream(samplerate=sample_rate, blocksize=8000, dtype="int16", + channels=1, callback=callback, device=device): + while not (stop_event and stop_event.is_set()): + text = process(q.get()) + if text: + return STTResult(text=text) + except Exception as exc: + print(f"[ALFRED STT] Erreur microphone sounddevice : {exc}", flush=True) + +# except Exception: +# pass + + if not shutil.which("arecord"): + return STTResult(error="sounddevice indisponible et arecord introuvable") + cmd = ["arecord", "-q", "-f", "S16_LE", "-r", str(sample_rate), "-c", "1"] + alsa_device = os.getenv("ALSA_CAPTURE_DEVICE", "").strip() + if alsa_device: + cmd += ["-D", alsa_device] + proc = None + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) + while not (stop_event and stop_event.is_set()): + chunk = proc.stdout.read(8000) + if not chunk: + continue + text = process(chunk) + if text: + return STTResult(text=text) + return STTResult() + except Exception as exc: + return STTResult(error=f"arecord: {exc}") + finally: + if proc and proc.poll() is None: + proc.terminate() + + +def devices(): + try: +# import sounddevice as sd +# return sd.query_devices() + print("[ALFRED STT] Périphériques audio :", flush=True) + print(sd.query_devices(), flush=True) + print("[ALFRED STT] Périphérique par défaut :", sd.default.device, flush=True) + return sd.query_devices() + except Exception: + return [] diff --git a/alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/alfred-controls.css b/alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/alfred-controls.css new file mode 100644 index 0000000..ff91533 --- /dev/null +++ b/alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/alfred-controls.css @@ -0,0 +1,19 @@ +/* Contrôles indépendants de la feuille alfred.css existante */ +body > main { position: relative; min-height: 100dvh; padding-bottom: 82px; box-sizing: border-box; } +.alfred-controls { + position: absolute; left: 16px; right: 16px; bottom: 12px; + display: flex; align-items: center; gap: 14px; min-height: 56px; + padding: 8px 18px; box-sizing: border-box; + border: 1px solid rgba(255,255,255,.10); border-radius: 16px; + background: rgba(35,37,41,.92); color: #f4f4f5; + box-shadow: 0 8px 28px rgba(0,0,0,.14); + font: 14px/1.3 system-ui, sans-serif; +} +.alfred-controls .audio-toggle { flex: 0 0 auto; width: 42px; height: 42px; padding: 0; border: 1px solid transparent; border-radius: 12px; background: transparent; color: inherit; font-size: 23px; cursor: pointer; } +.alfred-controls .audio-toggle:hover, .alfred-controls .audio-toggle:focus-visible { background: rgba(255,255,255,.10); outline-offset: 2px; } +.alfred-controls .audio-toggle[aria-pressed="true"] { background: rgba(225,100,100,.14); border-color: rgba(225,100,100,.28); } +.alfred-controls .volume-label { white-space: nowrap; color: #c8c8ce; } +.alfred-controls input[type="range"] { min-width: 60px; flex: 1; accent-color: #d1d5db; cursor: pointer; } +.alfred-controls output { min-width: 44px; text-align: right; font-variant-numeric: tabular-nums; } +.alfred-controls .controls-separator { height: 25px; width: 1px; background: rgba(255,255,255,.16); } +@media (max-width: 480px) { .alfred-controls { gap: 8px; padding: 6px 9px; left: 8px; right: 8px; } .alfred-controls .volume-label { display: none; } } diff --git a/alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/alfred-controls.js b/alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/alfred-controls.js new file mode 100644 index 0000000..142bd55 --- /dev/null +++ b/alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/alfred-controls.js @@ -0,0 +1,73 @@ +(() => { + 'use strict'; + const slider = document.getElementById('volumeSlider'); + const volumeValue = document.getElementById('volumeValue'); + const speaker = document.getElementById('speakerMute'); + const mic = document.getElementById('micMute'); + const endpoint = '/api/audio/settings'; + let state = { volume: 70, speaker_muted: false, mic_muted: false }; + let saveTimer; + + function render() { + slider.value = String(state.volume); + volumeValue.value = `${state.volume} %`; + speaker.setAttribute('aria-pressed', String(state.speaker_muted)); + speaker.setAttribute('aria-label', state.speaker_muted ? 'Réactiver le haut-parleur' : 'Couper le haut-parleur'); + speaker.title = speaker.getAttribute('aria-label'); + speaker.textContent = state.speaker_muted ? '🔇' : '🔊'; + mic.setAttribute('aria-pressed', String(state.mic_muted)); + mic.setAttribute('aria-label', state.mic_muted ? 'Réactiver le microphone' : 'Couper le microphone'); + mic.title = mic.getAttribute('aria-label'); + mic.textContent = state.mic_muted ? '🎙️̸' : '🎙️'; + } + + async function save() { + try { + const response = await fetch(endpoint, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(state) + }); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const result = await response.json(); + if (result && typeof result.volume === 'number') { + state = { ...state, ...result }; + render(); + } + } catch (error) { + console.warn('Réglages audio non synchronisés avec Flask :', error); + } + } + + slider.addEventListener('input', () => { + state.volume = Number(slider.value); + render(); + clearTimeout(saveTimer); + saveTimer = setTimeout(save, 200); + }); + speaker.addEventListener('click', () => { + state.speaker_muted = !state.speaker_muted; + render(); + clearTimeout(saveTimer); + save(); + }); + mic.addEventListener('click', () => { + state.mic_muted = !state.mic_muted; + render(); + clearTimeout(saveTimer); + save(); + }); + + (async () => { + try { + const response = await fetch(endpoint); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const settings = await response.json(); + if (typeof settings.volume === 'number') state.volume = Math.max(0, Math.min(100, settings.volume)); + if (typeof settings.speaker_muted === 'boolean') state.speaker_muted = settings.speaker_muted; + if (typeof settings.mic_muted === 'boolean') state.mic_muted = settings.mic_muted; + } catch (error) { + console.warn('Réglages audio non disponibles :', error); + } + render(); + })(); +})(); diff --git a/alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/audio_settings.py b/alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/audio_settings.py new file mode 100644 index 0000000..20243b7 --- /dev/null +++ b/alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/audio_settings.py @@ -0,0 +1,62 @@ +"""Réglages audio Alfred : ALSA carte 1 et états logiques thread-safe.""" +import os +import re +import subprocess +import threading + +_LOCK = threading.RLock() +_STATE = {'volume': 70, 'speaker_muted': False, 'mic_muted': False} +_CARD = os.getenv('ALFRED_ALSA_CARD', '1') +_CONTROL = os.getenv('ALFRED_ALSA_VOLUME_CONTROL', 'Master') + +def _amixer(*args): + return subprocess.run(['amixer', '-c', _CARD, *args], capture_output=True, + text=True, timeout=5, check=True).stdout + +def initialize(): + """Lit le volume existant sans modifier les réglages audio au démarrage.""" + try: + output = _amixer('sget', _CONTROL) + matches = re.findall(r'\[(\d+)%\]', output) + with _LOCK: + if matches: + _STATE['volume'] = int(matches[0]) + # Ne pas déduire speaker_muted du mixer : état propre à Alfred. + except (OSError, subprocess.SubprocessError) as exc: + print(f'[ALFRED AUDIO] Lecture ALSA impossible : {exc}', flush=True) + +def get_settings(): + with _LOCK: + return dict(_STATE) + +def update_settings(payload): + if not isinstance(payload, dict): + raise ValueError('Un objet JSON est attendu') + allowed = {'volume', 'speaker_muted', 'mic_muted'} + if set(payload) - allowed: + raise ValueError('Paramètre audio inconnu') + with _LOCK: + new = dict(_STATE) + if 'volume' in payload: + v = payload['volume'] + if isinstance(v, bool) or not isinstance(v, (int, float)) or not 0 <= v <= 100: + raise ValueError('volume doit être entre 0 et 100') + new['volume'] = round(v) + for key in ('speaker_muted', 'mic_muted'): + if key in payload: + if not isinstance(payload[key], bool): + raise ValueError(f'{key} doit être un booléen') + new[key] = payload[key] + # Ne touche jamais aux contrôles Mic/Capture : calibration conservée. + if new['volume'] != _STATE['volume']: + _amixer('sset', _CONTROL, f"{new['volume']}%") + _STATE.update(new) + return dict(_STATE) + +def speaker_muted(): + with _LOCK: + return _STATE['speaker_muted'] + +def mic_muted(): + with _LOCK: + return _STATE['mic_muted'] diff --git a/alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/index.html b/alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/index.html new file mode 100644 index 0000000..4762a5b --- /dev/null +++ b/alfred/app.backup.weather_outfits/backup_micro_gain_20261016_134053/index.html @@ -0,0 +1,28 @@ + + + + + + {{name}} + + + + + +
+
{{name}}repos
+

Bonjour.

À votre écoute.

+
+
+ + + + 70 % + + +
+
+ + + + diff --git a/alfred/app.backup.weather_outfits/config/camera_controls.json b/alfred/app.backup.weather_outfits/config/camera_controls.json new file mode 100644 index 0000000..86998a2 --- /dev/null +++ b/alfred/app.backup.weather_outfits/config/camera_controls.json @@ -0,0 +1,16 @@ +{ + "controls": { + "brightness": 32, + "contrast": 42, + "saturation": 64, + "hue": 0, + "white_balance_automatic": 1, + "gamma": 300, + "gain": 64, + "power_line_frequency": 2, + "sharpness": 50, + "backlight_compensation": 0, + "auto_exposure": 3, + "exposure_dynamic_framerate": 1 + } +} diff --git a/alfred/app.backup.weather_outfits/config/hermes_session.json b/alfred/app.backup.weather_outfits/config/hermes_session.json new file mode 100644 index 0000000..de88db4 --- /dev/null +++ b/alfred/app.backup.weather_outfits/config/hermes_session.json @@ -0,0 +1,3 @@ +{ + "session_id": "local-65f11130ed816902a867736ef80b3af0ea255048cde912fb31b26267c8d1d599" +} diff --git a/alfred/app.backup.weather_outfits/config/hermes_session.json.bak.20261024_151328 b/alfred/app.backup.weather_outfits/config/hermes_session.json.bak.20261024_151328 new file mode 100644 index 0000000..4525415 --- /dev/null +++ b/alfred/app.backup.weather_outfits/config/hermes_session.json.bak.20261024_151328 @@ -0,0 +1,3 @@ +{ + "session_id": "local-f2399cb2bcf9e91094d3dc34492b463eda9cf42d09857d51b5954c5982aaaa5e" +} diff --git a/alfred/app.backup.weather_outfits/config/hermes_session.json.lock b/alfred/app.backup.weather_outfits/config/hermes_session.json.lock new file mode 100644 index 0000000..e69de29 diff --git a/alfred/app.backup.weather_outfits/config/identity.md b/alfred/app.backup.weather_outfits/config/identity.md new file mode 100644 index 0000000..2ac6084 --- /dev/null +++ b/alfred/app.backup.weather_outfits/config/identity.md @@ -0,0 +1,31 @@ +# Identité et comportement d'Alfred + +Tu es Alfred, le majordome vocal personnel de la maison. +Tu t'appelles Alfred, même si le moteur logiciel qui te permet de raisonner est Hermes Agent. + + +## Langue et style +- Réponds toujours en français, sauf demande explicite de parler une autre langue. +- Tutoie ton interlocuteur. +- Adopte un ton naturel, calme, cordial et professionnel. +- Privilégie les réponses brèves, claires et faciles à prononcer à voix haute. +- Évite le Markdown, les tableaux et les longues listes lorsque tu réponds oralement. +- Ne parle pas spontanément de Hermes, d'Ollama ou de ton architecture interne. + +## Domotique +- Lorsque la demande nécessite une action domotique, utilise les outils Home Assistant disponibles. +- Ne confirme une action que si son exécution a été effectivement vérifiée. +- Si tu ne peux pas effectuer une action, explique-le simplement. + +## Identité +- Si l'on te demande ton nom, réponds : « Je m'appelle Alfred. » + + +# Alfred +Tu disposes des outils et compétences configurés +dans Hermes Agent. + +Utilise librement les outils disponibles lorsque +la demande le nécessite. + +Ne limite pas tes capacités à la domotique. diff --git a/alfred/app.backup.weather_outfits/config/identity.md.bak.20261016_140326 b/alfred/app.backup.weather_outfits/config/identity.md.bak.20261016_140326 new file mode 100644 index 0000000..ed48720 --- /dev/null +++ b/alfred/app.backup.weather_outfits/config/identity.md.bak.20261016_140326 @@ -0,0 +1,26 @@ +# Identité d'Alfred + +Tu es Alfred, un majordome personnel intelligent. + +Tu fonctionnes dans une maison équipée de Home Assistant. +Tu utilises Hermes Agent comme moteur de raisonnement. + +## Comportement + +- Ton nom est Alfred. +- Tu réponds toujours en français, sauf demande explicite contraire. +- Tu t'adresses à ton utilisateur en le tutoyant. +- Tu adoptes un ton naturel, calme, chaleureux et professionnel. +- Tu privilégies les réponses courtes, adaptées à une conversation vocale. +- Tu évites les introductions inutiles et les formulations trop techniques. +- Tu ne mentionnes pas Hermes, Ollama ou ton fonctionnement interne sauf si on te le demande. + +## Domotique + +- Tu peux utiliser les outils Home Assistant disponibles. +- Lorsque tu exécutes une commande, confirme brièvement le résultat. +- Ne prétends jamais avoir exécuté une action si l'outil a échoué. + +## Identité + +Si on te demande ton nom, tu réponds : « Je m'appelle Alfred. » diff --git a/alfred/app.backup.weather_outfits/config/identity.md.ori2 b/alfred/app.backup.weather_outfits/config/identity.md.ori2 new file mode 100644 index 0000000..52603ea --- /dev/null +++ b/alfred/app.backup.weather_outfits/config/identity.md.ori2 @@ -0,0 +1,74 @@ +# Identité et comportement d'Alfred + +Tu es Alfred, un majordome numérique personnel, +intelligent, polyvalent et connecté. + +Tu n'es pas limité à la domotique. +Tu es un assistant capable de répondre à des questions, +de rechercher des informations, de générer des images, +d'analyser des documents et d'interagir avec la maison. + +## Langue et personnalité + +- Réponds toujours en français. +- Tutoie ton interlocuteur. +- Adopte un ton calme, cordial et professionnel. +- Privilégie les réponses courtes adaptées à la voix. +- Ne mentionne pas ton architecture interne sans raison. + +## Capacités et outils + +Tu disposes de plusieurs outils MCP. + +### Home Assistant +- Contrôler les équipements connectés. +- Consulter les capteurs et les états. +- Exécuter les commandes demandées. + +### ComfyUI +- Générer des images à partir de descriptions. +- Analyser des images avec les outils disponibles. +- Lorsqu'une génération est demandée, recherche et + utilise l'outil MCP generate_image. +- L'outil ComfyUI peut transmettre directement + l'image générée à l'interface graphique d'Alfred. +- Ne prétends pas être incapable de générer une image + sans avoir vérifié la disponibilité de cet outil. + +### Autres capacités +- Répondre aux questions générales. +- Utiliser les outils de recherche disponibles. +- Reconnaître des personnes avec le MCP facial, + uniquement lorsque son utilisation est appropriée. +- Utiliser les autres MCP selon les demandes. + +## Règles générales + +- Choisis l'outil approprié selon la demande. +- Ne limite jamais tes capacités à la seule domotique. +- Ne prétends jamais avoir exécuté une action + si celle-ci n'a pas abouti. +- Si un outil est indisponible, explique le problème. +- Ne fabrique jamais de résultats d'outils. + +## Identité + +Si on te demande ton nom, réponds : +"Je m'appelle Alfred." + +## Génération d'images + +Tu peux générer des images avec le MCP ComfyUI. + +Lorsqu'un utilisateur demande une image : +1. Recherche l'outil generate_image du MCP ComfyUI. +2. Si Tool Search est actif, utilise tool_search pour + découvrir l'outil et tool_describe si nécessaire. +3. Appelle l'outil de génération avec la description. +4. Attends son résultat avant de confirmer la génération. + +Le MCP ComfyUI transmet automatiquement les images +générées à l'interface graphique d'Alfred. + +Ne prétends pas que tu es limité à la domotique. +Si l'outil n'est pas accessible, indique l'erreur réelle. diff --git a/alfred/app.backup.weather_outfits/services/__pycache__/alfred_intents.cpython-313.pyc b/alfred/app.backup.weather_outfits/services/__pycache__/alfred_intents.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..edc50dbf8a20d98171a8dcbddaaeccd799f9c0e2 GIT binary patch literal 16335 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 literal 0 HcmV?d00001 diff --git a/alfred/app.backup.weather_outfits/services/__pycache__/alfred_views_controller.cpython-313.pyc b/alfred/app.backup.weather_outfits/services/__pycache__/alfred_views_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6990f4f5dea8240518f2e732f0df71eb915b0dd GIT binary patch literal 11574 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 Jm>#`^{})aQ5*Yvh literal 0 HcmV?d00001 diff --git a/alfred/app.backup.weather_outfits/services/__pycache__/audio_settings.cpython-313.pyc b/alfred/app.backup.weather_outfits/services/__pycache__/audio_settings.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc453e0c772b2689478d0c3146d530efce0c267c GIT binary patch literal 4935 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 KX{uE}_kRKK`q=;g literal 0 HcmV?d00001 diff --git a/alfred/app.backup.weather_outfits/services/__pycache__/camera.cpython-313.pyc b/alfred/app.backup.weather_outfits/services/__pycache__/camera.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..adcb34bb9b6079054eca462c7ad4c958271dc92f GIT binary patch literal 4111 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 dwC2)nd!{#@GUg%P~gk{ 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 literal 0 HcmV?d00001 diff --git a/alfred/app.backup.weather_outfits/services/__pycache__/ha_views.cpython-313.pyc b/alfred/app.backup.weather_outfits/services/__pycache__/ha_views.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8942c869d62f2721292e98578b33d71c6f2fd1c4 GIT binary patch literal 19838 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 N2mhn70e4E+|9{1!8VUda literal 0 HcmV?d00001 diff --git a/alfred/app.backup.weather_outfits/services/__pycache__/hermes_client.cpython-313.pyc b/alfred/app.backup.weather_outfits/services/__pycache__/hermes_client.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec74a92359d1d1739bc6cbbd87b30732f7edea29 GIT binary patch literal 12060 zcmcgyZEzdMdEUd_;p1??fds&BQpXQTgd~!(CB>3rOQA?ArbJN=j&Q6}2?7B}2@(h( zcYrLiafVKQgp?;G<-`@!@uYO8GnHmKlT3efI{jzS%}i%HQ!;c)^u)HxME!7oK_MM^ zl+L8@yTBbtin9Hq>5|yn+uMD2_ip!jpZDEe_jm*XVd@|6U;VE=g!~F`%-}31)=$|8 zd50)OBMPI~&N3Q9uQts_udK$>E2nYv%40OfjjqMnn zw(-z;XtS0+lWFNXvo9cNb1A!0PfN8b4VKJCn$vdK7HXR8c_ow1$-SvDO-;yMC*+q^ zZCusm*mX57>#D9NGif=iYI;)7#nL&o`y+gB!y(4-#-XBmBR3vP$1bZHLKvPu72Zw72UIVqKF5yN*SVcVF!K+8;i5 z;JN3IzR_VgznNF{9JFoNH8q#l((&?qq+bnJ0SmwNFyy{NMy+R~uqsL+%$cdm6}~Vt z3SX-fM$NTE%&&f=T-&@)Gc6>-gkjch4~k7;k1=7-_IzT^vb3$zQiQz3be4-Ipbc1W z)=c|U86;xcTrWa8h~kRayhITq1Y-9gzJs)qQLot#2Z_O&OtYg^W{$!|7%0g{ST8xm zDR#wi%!ci!ZMy(H(rhm~94TGLm@)DayNir5A#vCU^bVa45A;U|`v-FNPlk3p*`s0CT^ zB(%~k55=GxFtwpC<+kFoAAb5jKr}@j2I|(F!mR72t01ntC% zPn|1z-F!{KFR%LdE&KNsf=xx11ojt+Eg<}-AkFN4Yj2S!uBweX;`7fPy?tU{TansU zrOsukb4A)W_40qX{ROFZS}NKdu9l*Yh=J*SP&?RMw3pKi$gCAz%|+JB+dtW8B%*(v z*!h|@r>AIRq~=2NzWMWa4&Bl3s703X9C=V(^J@p^5{d!|)O_Fh+oA^wKhbMpKtE|d zMaVrOoZ7?Q6RJK|8X}5TjgcQBBVbOe&1DTiy^)1AlZTIgm#jlMq9I3sPhJN8=vXqPY8{X_ z3P1fIMDWSolJ&`3=_l`45qF!PT(mzHh@w; z2tnnrJ(%OQU=_+-CN1Q%6bDGN6h}oSQmJE1MO{RIQlV-F@W+=#L&P>3Fw>@jF|;|@NP#%aL6j9G7%e92{@-{7^P$qlcQoO2U3>8ILv{{ zfO)!2WuS5?s|>UK2&>wjQWTD14aIAyA`QX(#>qs!4KfyIakq2&R_KP!LoPBBLrGn%ya#g&4 z&09Tt_2$)r=$jp$8D9AE^zf2+V4bx|!ZcfS5~+Ibz_NGOG`rD4JhgNByM3#oye!IV zQp2j$u`G2gD63LvSqeS$Hk6^Mb^bJhQpY;WNL7cszWUUD_TGLT(%NAdX}nCNAW4X|E^8nc(2Ztd6voxMEkvx8$@QPazkFhd|@H=6I z;xXID@!_9|GcwuKAHbUJp^dyISIS0gm!S6%TNpL+wgB(XdyJ6*D=!1#JV30k1Nb=x z?{ycE;cxh8i9oi3l+TtmMl8Z94d-8g7ERkj)_}fbCV)HUP0sWbIHZA2m2jkcFeHVbVMh)?%a4b#2WUjrj8|V7y#+pa*Z0o8d3KcxE^|S^h(c8mC>CGyh_JjJ(4{IkHCW^t`R=tkd9|+d=XIS0 z|L%gn9*W*}-*P{4aP`8BXWF(Ql3@LO{SVGhyB>O^*$X!>6hv=9lz_tlfAR_dc;3K6 zfWpC>gN3T*d40*(UhvfvYU|*!$6FMM$3HtXGc+IgP;7Zz4>dNLh|4`I+!W@HedugQ z0vCj_6vW`F*t9G*-QkwRrk{&lz!1gO$L&zzV*=4{i*27)@QIG^`+cyD`VNtM_I-VA z?7f36kX~#P`VKl44>&M=DA@00pJE{GP;>oD=Tw(|8+f?7;2$lNi>QJ~NY$*rO&SDv13->Ce zp*5SroA?5F$BtT<7`8akmhx;EHTbq*a<)yi>$VkvTeBS**qlPHlXnGW5NxnOkiC;@j0S0IOr{ zpj7?M`nl)l^bcK)^Vc6bJ+tnc?t)l7dv503Tj$rrDokvkX<8H`oG5}|3y)%eLq&8HQ6Ld*Ta)%ZX97ht&GIpr8|GWT4E z`(I%1o#6UT+7@kxAiekn7vwLVJko!d|KKnSIUl?r4DgP>W3YT2YFdGV6t3EJG`O~K zFw}9|0=mf*n>oM1GGvDhp==rU4Gm7#_Gp<*t_NpOEY))V$ORc1Y zywS2QhT<`8LW{!VU&2pEn+LU!XZG05V+CiR;A{oMPqYJZ~{O$D(Z!A}oCgoDP}2|k52(Tf^ysbTCtE(3gI2UYlTOfn5qDa?qQK1SKO7>Gvv&CngJK6 zh4ZF-VDCG)Ma`Pj0=0_n#IeEZ1cw4>5Q{~FUV%|yh}fv5Y3sF(F`GuTgN6YqUqvav zSuLb5$-sD%(=`Z`2;}ZGj!mWDodJX9KW${7!zw+tgTx*57}(B^lDl=M5t)b`at=Rn zb#-OZs(vMt8-YpXjmNU!>Q!BOv=qda>IK7K`wU;7w|W|=3a_r|EVvE;~1 zNr6;Ylu*Onr^&W*zwObc5CMX}W0XBnIM~OW=$~tHtn+l;-!qjcgJ&5lU3Jx-XVWsfkxg z^-Cr=)>1Xi5XvrnXbd#M7|H-Izn)L!s7Y-&5-N65-=-3zX!&JDQ|jedd_tRmnvdb9 z{}Lil30y1K(^ZYPTi$MY=xbT=$pv2{;Bb+0M}aiSz+2Y>?JI%Zh3c9|exX{Jwim%m zSTp;^%p0px+p^TQB6Tb|J3e{n4z9X8mfaoeumb%h+uE~dyEVK4b~Ui)&SiJ!I&t%U z&`BQO?3tM}kBQB*s~}a+PR>j|W+DARYWcUaYtjWaIOvv=!5u6F>u!Jj*4LK;9SePT z919a@bw9toonjRTFOkyk^m4rp^1dU`7i8aW6npow@9(X}^oic;J|Da2k)0u=fT_>HjU zx=3@Q7K%i;ihs%k<1MmZ24vvE*6Opm3);$*r`V3a1~YxnGfP@6D;zBe81xL8yeSjj z(r2}5M_IVBbM8-2UYa@gV?x}BI7}S97w9^rL2;N;!F$2%I2P~?n4?!*$6qgvW|IW) z2<%wTZAyL$&%|y1!JX?{_@V%AA)eLt2b z9Yb>LI!%~J_9W^%Ig+9`5!v$WETomD5 zLuN4PDz|uCHh6&KjFv};Kt-U&Cy#II&KT^$rIXg4-czoa^7a>O?{#ZeK}`O6h=yX; z23x7;@)%G*P)@LKhI?GN8$`7i^cfYM+8q)><-itR4xApUPIU|Qj%fQqoFU=x`&AqQ z)g~HJS#X>r<2i$c4KJ#8r~|j+l?gd06arJjrj2TR^T}As^o2x|34_DN;2m6t?vy+% zcJMO44jbO+w?r=rgc64p!D7*LHHvRErQL+bt$?2TpFsq!UC}o+P^fQS;%XlFYF2%% z%f8ls^0iNM4#1?R# zfI)I>=Gfe;EAFNR=P%ryg+Tqyfk!>0s-yTkaeCf-gK9FHXYV@8p>G(stVDS5y@@v` z<{IZ?D-L;~cg3+AiWR)I1t~B)F*7k=yCSu&N?i-DEqS|9KONlU8WM@iOYN%zE6!by z3FB%hxSP>8NF_fxv_WStM0aCcU##l)w!iO zSYj{Ey!h6M`9n+YmL;y`lZ|nP_~1phFH7y~(3{|Wc$+L-pXdm}Kdm`kWBXZ6;B*iF zvu*}rx(v5?t1Sa#2-eAF1H;sJXd5XkdWdl|U2*ofKtovD`!U$}0aj?4BG70#z{Wk( zW9`$Rm*O+^BFP=rzJ6Ae}$&>w84nt2CStVmREfVBad`}6Q(b|)6QT@NU}oe~pv8xM?Drgg3)IZF0O*&2Bo^)?QZp?-w2O4;TS=d3xTV%@P>d5I3Z}7=9 z7z$bv1XAGDu3;*L(Kt2|ECV@g=WCh#^%x$&I4)BSprxTeat%IBvg66`ey3%y5^81e z1fg+`n{051AfaXP!8b922}Lv=qaKKis?*@*8P4QRXY%Pp36ihl!#qaFKugvE3$7pv zmf*%ls!x|33}!q5KKqh&P-6Y;xPJL5=d~CGA495aNNax#^{*l3zDh9{AjVa@;;bt; z!I1VBoHYffyWp%YID<6y6#atRKGpY|I)a(tCY})rVh#Kkc{U(yz}A?%DAa(>P~CL9 z`|WOYL|4}p8h33n6KWrM9DZTiQS2s8Y1L7`?5Lmr%8H`}Av%CNc4ewxGyCnCZ|^|$ zMd+g_P3vzRolCrRvQQnIQ|HF!_D}bf_v^p_Xj%>KT@LPD3UuD#z-E|Mi@ePftROT> zsGE2EsrN_T75CmHZto`>C#l`>x$Ki>(}F2@+t;M#6=~O!S4I?Xd&~m-CS)7OZhvpM++S*dxssQ!VS$D;A^n_-8KTgVae=CWsjZaTWr0O4_1F= zKc4~nr96r}T(bkC!HOinzVb6@q1McYLs)=kxT9|J-WZfp?B^N90r}2Dyy8*>>ig$( z*k#}9O@2BDQSMoLaOc_59^7^*pSEvj`iMjBr_X?#JKsNG2;~!ixD946S>KZl)~PS0 zd`!^dl#@2OlWt=nJiUWE2hD3Vur{X@tjeOu;)T*Z^MBvm&c;G9M z0c9kLj%4ZsraS5s2k1tYhJFkH7G#|zblD9l!yV6SxEmP7UHTc!airDP@$M;r5n}o^ z6JFkfl79u0*F1^jX_!B};*qCLgU1+lndp(E)`D0^Po_M!6Sof!8_m784twJ*NaJ?R zUtf{-EIIc)bl1Iga;pC~b|Tci>sd(rqHaHUhHE<(`d4Z~({4yN?gc+`L97K?jlzsH zZAUNzZ1NREKlLbsJeRlN?JP(&)5#)X>U{s&+_#RH4; zf*0fQbEN*jQr-U9=9%Wzy8Y1RliB9k=09pK?`QeMK7oC|sh2r*kiXaLf}48>g;OV7 z_l`Rty(kEMb&gQG;fO{PnRqm+y^1e&A4a$&44bBwltDkmJYjf0mHj|e@7PjS(e zW=RL8a4DBQJ8lP*+))WkQO$(jAcN24vrrq!d1;p5H`UrOLSQQqdFP98HZPx2PiTJ$ zdGNuB{$q#%6&dE2<&jINm z%C{Gr+8IB#4;O47F2xb1k70Jr`-=px3&92bPT#_f_jYa2`{Ic0OAONq3bx2%;sG4Y rOWYEQJl?=8w!5bf&9T!*-;8cJXx6Z;lc||giUcn6%Ex%2-|v3`Nsdup literal 0 HcmV?d00001 diff --git a/alfred/app.backup.weather_outfits/services/__pycache__/piper_tts.cpython-313.pyc b/alfred/app.backup.weather_outfits/services/__pycache__/piper_tts.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4508a128bf2afea3e4b4c25f0f271d38f5e9381a GIT binary patch literal 4857 zcmcgvZ%kX)6~E8#J^T5;!FDiz3I+niNihFP2x*d)29nsxAhL1Q%AJsFp1}!zcJF(J zfGUl3lh)=7WLlN1Th(-$ROl*&Z`;TI$%iGMFs8Ei!nR67+OiK536)y4`>=CuE|0Qy zt=ef1e9w93o^$TG=iYaJ=WbiACIrvxzq@}qZAR#? z=EFW#H4S9Plj$>)S?S6SA68;mQH>>eSV?DmjA|Q@XQlXPJac9$DN2e@r<%gEk}@gE z$vIKAYy8vcWL#8DmjaiDLSyGg!lQG>uI@g6C;W9$@uiW_sLFSBc6WBF&Vk5aU}S70 zGI}X8IyN};bl}X;oGCMvotT_C(RH*lzLS{JWMKi(kKwm1@dz~|FDzPgCk+TicQvD2 zNgEnEeMuM5g$z`x(cT?401G$CA~ems$`11u@LZ3;!l-FdE=po3JrNYOQa);{{KH{~^| zS&jTX`Z$$;r#!w6dDJ|8T^{ujHa-a!|BGzuzg0H%-zuB>|DH`)xjvdrjYjmLtH_IoWfXEq0?fcjS<1-Gc=JKij z^awel9SJ;kHq@@9Q|&2nJef@aN^nvvB?2~Zig;B~`Dua$4+!Q#pFv5c#B^4ft38vJ zB=Kr8EqN~`Ga~jTB}K$IoyufIRX6jz&x;8#V{-Un0^X`2PAuYzYM#MKMH~~S0fI0& z&apuHb!l1EPl$>rO{;vVQ#Hg0UM172FcV8EV~I3YO<0VLmu*!`C@Yb8hG1Nb30$a# zq&%j~W<&^{w59|^w4=?<#HK->&WMs~kwry{PsSjiDOnYEIU<*1smx4rT$xn)G9khQ zhMeG-0uD%v0cFt6L`q$<%Et%*V}LZI^6{y(45N=H<(*0U446O)naK25CI-&+@e&4? zC5hyeh{;K>@^T5b`8YvxnPkUXe${R;5(+gwP*h9M<&OD{}q4UR8!z;R=Juju9I&n z+FS*jH)r#1SPm5Hyc>@l-KeX7Bk)?_R%*5PT)yu7LsnN`{gCIZHJ=Em*87W=U$$+V z^>))INN+T4l^O?fd0rN_xF%y`vGTx1eM_OsqVp zT4~AG9bX*UY-oOC_O;n1&&uQZhU1Ipi>|tY%a?QcmRfH6m;LLm{=JeT%SYB-$M#AN zFCYHU)w}6#D7f3!+-*zzZPT);;O@z}d-f0YQB^~(s%@jW?XCV__b)xOqU4)T-V}-+ zZ^6@^^RzFCx20ui-E(TMUoiU+%vj_&_}N1N8Jj+RIkDG%O=$d={1R-HmktGc znYWoh5BFORxRn8YunN6*j0qg$-s>yzr`rN2(fch-pr3oc^=u=&+%-5s>28e^4DPlw zK{t2TuP6L5hRFLFqI2>IvV?f;_%k3qm;L z10dhp_CH_;zltJgw*qm)*@;4XbwQ0h^c<#|+c-5OTJp<1hJ`R(Dxx0hH=^s{!PXqM^s^9;=F$31=N4hxAq!!f9fj>Z zER6%^tL1UGTBM^wz8W;1auFS!8(qIx6;wQ$A1~F^D5;aF6+znx)QF-rwBLYYP@_xS zo_RSUoDKEpXq+P48zgRkOE45G!Co8j{}#E1^}dQ>)mlF4$I7Ss9Onn<094jzu$V|* zRrPW<0W?dh{OG{Nv!S6uZwc9d;4R(bK3I}Pzu!L!pv@pTe8NxwLSp<1CO4cf8)j6*sigG5vR9P6bF9BRp)&HEXWt-z&0-dN zPk4P51lw>jqY8?c$`Hg;4XGptitbDm^Xw_%agf662WM z7jP%>%!o2U?h@2uQu;%%88LPRUj#k~Klun8BlGCbX8WRYWA6IgjTf)KSQKoV&f0>r zdCl2ewAUByZU8lx{zn(*pZZupyy2(kUpoJC=qDF9EsllZYr}a<-TcrNSH*ib1na8p z$jaku!tqVPzF@v){(11Gl6U#ng^o?q=UZ{D3CA{t$_3jsThZ!T9KSjEo1-hjs-+(w zwdksPWZ;aZk1fb(E82YT9K7v++n+yt;`fo<;ium1S@oTJ_k~qU093bm7W68S#kSD* zO5eiiS5DtNR4_N^%*`7h3S0{;PJU=<09>#f{PbfZvbeXAj<=Ec9EX-Fme^I>(Urk9 zp}#0tKi}e^>vNddH*ofu+yY5^uW_Jn(2ef6O@rO+od(-rJA0>{Cww3Z>FgFQaW>QF#jpCsXP1?38#Khe1Z5MovJp#WDi29&k5wq>N1cqmmADnZ?IG5;?CVnpzRDTW+u5#XHFZzt#9kLH*k>EaU87=TM4f!x(Y7M!}mCJLysYrU%JJH1l-5u z(j1+Lhr%IYX~6>nMPnR)jy=OE1MQ(>;eAt zdUMz*7Ss8P2iS0!9Sw7`X&{C-o(%`oQ&VHT`XQf=LgFfMl0YONk&Ypep0P1@Mqfcp8YW3( zURL6rAn43kZe2y;_h zST@He5XS~X{J2bYPXXe}7C@SqA1WbT7-JIxm(CbfP}ve@g}9$XD2mc)ArM8JTQ3^~ z4#>y~BcdXR1iXthP0O`S(OA5RRY)bPj@7jLcb8p~>1F5}z^Sd5yZBLH) zKZ^hS>&xq2z;*M1+2G$spF(pYkU2NV;JsB&*af9mVSl|CBquba^DIq#284Gt)}*1e zQ_r-95yo-*3=X6L;p5GmW426dPZJ}VbM-1LI17VQ)woZ*^Em++rmjr~Sdl5uzdA48 z*_@Ca8!$=VKLiwPjI#*nS~dn*E)wP4CggxdvMD6^fjdoc$d0pAAk47{VN#NfLkfb) z20u#hG9^F<@dXlm}Pon`GK3nl?T(Ro;GCYV7#9vEwI0AHMw4my;((#Kx0zrQh3- zJUuF%4vD8j(&?ypI{N7JS+pMdeg+{jB>y7Rd=^4{-f`1#$O|O!Jat3#0vAWscp2Fx z1GNC5$z^%g9F48Ruq6<^NnGx_HgIJ?DymQZ6|Cc6<>R>RKE*=wY#!>EcDTNp0 zIU}VUZ=v9X2qruvmDP!5byAsIEOS39+xmYo$?DeddAkX83xxxkh&_ z=@Fo1hTmtQyyk3LOKB}fG8`fwPXLt(xPqoIJr`Eb7&KUz2`1{C0Hv7ha{@zojJ1T@ z*e@5J=F<`UtMw__tW=#C@OH^YRX(_lashA(9!n&nf?7e@$noPLo|6q& zHA0vj$dn>~K&BZ+qtUR!h0sQ*P!L!)0zU$hlXX!+HUhum_$j3s!Wg;+vyH%I;t3%S zny3{oKqFxSqBn_^4V80-TZK0ZCHml^;e)~-6iUrqVslrj`Cw|pp(JxcVge!)keEr4 znM^U^m5LW`xZiQl^(}f*6`eod@h@#ZYfJiGO|qd>@8t5P>KoDP(L08Fw!609QaXDN z`Iyj|d&sr8XMoWa+_L}<0q#Lo4f$~D+4KE84C6sx_;Ybf!|LF-6gil|G;Ky8$36!$ zwbm?fxRH#!%7<033cW@FG|_jQAT|*`FcOwFafwn_5{?2tA?!F{jSDO-_U@qb=LRf1;dm)mkaP%CI52jhP z2S<1xWkWm!tdbJK90yX#zq$*cB|!jnT`p1;dM#`2o(^z|A}{NOI8vy~tyg3_+Ku}p zIZX#tr96!b*GfFB8dO1*@B@f|$ZRE-&VT#-?8dn_QnuZaZLetCo3ibjF)lk?*P5<0 zNsb25(I7dth>k7GHm78(6m6A~ZL?_GEZG`FTf^_*|~o9 zbjrDPen@IOBsLy;)Y!A^s*qd_qO0MNYs+IZ;Vgfq=SPk$pZ?B5lx}=VkVU&!=$g6C zzcXGoyluN}c;E7#WpVraMGHlLdaQ@aKMGj({ItV$z(m|HwL`pQvUKV6OODM4I`m5& z6r=;IIll?O6qn@RO<(;TtJCfn^Xc&BJ47zo^fI|z2IgGq)Y|l=fNug9T900>G4bBO2gO(Uvpa( z14gF!L!6o1GKr&1t1?K3z@ShE*l~o z%gg#mh)0K@XE&u#hHMD3aaPvxF_~m#J&Fhd)&amyqE%g3Hl}T}wQ7r60S7<`-+&18 zVVm>P>z7`i9eb>!%$2{eZdxw3Uy59e%y!+HzBxS;NftLt#jRp->mA)a%Uw&V_`po( za`W~Nj2{@^rY?_O8^1C>J9=aM`uH4sGw?Uzlxyol`?fm=W)3V{?U(v4_Ptv(8iKlVwk~DbbiFod+Vn>O>;7-=ckHhx?$=xP@6zAjNkaS#vSU$DgN^4g zSmbGQ4IId7)oCO{82JpMX9byd(7;?X>W~)`Q%=a5+7UAljvfey0;D$Y0^tF!026O` z?x-2&n^oe1FjMeb5j+Ltfvww|FkHnLnS$YN#?)ri0VE)D9#ttHv1kHGBe39EMY!S3 z%?V$qHQ260W^aaIvR!E7Oc7VfuQb1^uQWf{$3B=Nqx@+^tP<}wBC`i1L5%4z@zU@& zo)&lc$$P+{k@pBTo0Qyi`<5HDGXbokv^lY0X!wkb(?>Pw8UfQ_lxnS=w^mDKzhL^W zhQP*KPGCygbbNuL?tEFCocT&;9)i{dUTuM^X6MDcEGK}HSGHPE@Ft&0Ys!Km>@)p8 zz+iPnjX6_?tahRS5R0(prq71Jc#TCCKz+r2#xeq~RS!y`t6#PsA7Fa@OlRNV(Qd!D zbEsRUw(o9kZbq2j24E920m~kc)#OvG3GO4W5<`Z(5*TEu8hmRcI9^EFe<15)iMULS zo{w{an*uwGSA?Ar9N!U6kah78>VR%!@@z)vy{V{%vMw+xZ&_of``JV=6!ixt68ss# zvo{Q2)f;Irxu}N6lS|^&~qweO)~R{+|9p-;lqn z`*_cRZgdP9p-zav$g~J7_Fp6L*URQn==cm4;HVGJ&J>scUp5yOAO#-lQ8sV}JYF1j zS;_@0;=tV*ys)EB@t4C2LqaUdhrq49hZdpv#ynSk0Z@TG%=8jL35pWxGTuaotlgRIu) z&nCD8=pM(EPvNRL$i-gvTn`Ut!ofA&qFdF%$*otsql_*h-KMgFS=YXbJ-vurpaU#G+yBT82xP5Dy#d zwx~8iLmI|y51yJ zG>a9@QpFCjV#l3bQhTr1-Yd0x#dfdMJ|wmeB~PA`PO{=jHgz)a@$tZH?Q#UF+zuz(qbW};*$`VUa$SzkcFnDu zue~iS?)v`uJEeC5N$dU@{ff2tQqQ-0lCG`u`%~5?$=V`XTT<568U3#ci!bYE4YSPU z3!=U1vVO%-At+x)Sc?WwY+%Z6V$%V*>7R6M4L@(s`JCDl6k z*hJV%E=^sW`pz^MdUGYWHr(9s_ZyS8%1>v`<}3bh0W0!qlM^(Vl~1qL>OgDGYTaK@i|}O}IGX$pLd%hiX~>v-`eI^d#zDf7 zSwnBtXr`R$LKf?Cpsitk2tm`@K#S5<6RTV=nelzp^K?TnD+`-+SmSHcwWNg|Zv-tq zqaCVa;g~7bMsyN~$itv@@&y2y(G#EuA1r1}I$Lfe&syN6MZLV*itNW;buyg3JY|AM?3FsN^z!4ri zfvq3tF#GTTD-6-Z6ux`kL!(zQAPdn2Y6KLkQwKrCuyVYRK%Acy0-;c74jeoebYFiR zyn@_xTrPlT4{>;Zfcl}4yFxZ#CqBB2ySaj+jBgQr6KD5B1WEuKp8(hWG~BEU=uKQc zjU%jl%7#e++=)~W+$#e5D_ndE7Z-O1utV_#Jf0Yfq6qd>vO#d+d*L1@97nHXn4&>- zE@~O5C9!ZQu6p-Na7WnwQSPAKW>xFRPIDGlwc=<7BJjO%0Y&AI_3`PrfSGq%;YfR8 z&_N8AN6%31Fw$PoBLE|OgT&sLUz?hKXC&+n?7dvJR%aw_{k$V(-S(8y+buH&fGBZd z1!H#W+=QZGw21bWl)Y85cZl|ml>McdUDyM9?&7&iZ(Mw1HYnL@L|e`6y~#a8N#D!K zp;wYGj-u zGgaLpRd?JeO_lGH%J+-q`+w4wD({oZndC7>a=jwDUP-x5UakS3*0u9j&j0n-AK9vx z=^E^Hog0$sTEw~*sjf||YkOGNerM?3$-5^%s(IMaFAg4mQb?3FuDvt?J6e^-?*erz z(wOKacv6wdUsxh%FTXxda$=`xhJV)r|IW(FjLRQplMeYV+w;gu?3=(Stpt$!fMYm- zl*~xbRpaEBlB*=Y)*hU!ASHeJCi1zhP2e$sy~BaAfDeHJ?pCJeSI11D*0+;5d=+~v z04{R@l7;jcH8|jtq!-^A=Y(N4pXY|LV*iDY$|sAn@{>1ZJvI25kI$4(-fEoMnX%NF zV#d~{i(UuLpV{rOmi;z&gy*}_S9EQs#o?LZhPHLC{z8s1s`oP<%%0%qJP@M@R@GvxC zOTcUS*18H(xg-D)SrFvl-CsEFE<*!QB|BD+<5BiXm5DjxP!T)Vp@k|1@v5>i4-Mk% zG2CB444xo{0&%!9V5eo{6dO)(LSD(1wx?ggO|f{BEz0PAMdT_^x`c^nI4D!`Y;H+~B5HOXEbTa0dF(<2cEViW9wWS<;X3YOq?3{aL?&yQj4@37u$w9b9A5R^e5Q{@I)N<)M zO_Kd#lWN>6j}^IqPQ*t~km>SKGYn{f!G7 zZ&#*VZ9my5Iu6Zre^mPO%4J*WBU{z7y;QQ(qMerPwW7WDp?&kb;eG3ZbbW=+kY-@8@{%vcdZ~iyu>NB8<~kzyEB44X0~te*ahq3GpyIr{JT0Kf+uZ zRpAI*251Y88gYc(bcn_g*349V#p)1Ju%pcH$8RG-0Y4iDx6mjsAHnZmAQU02-t}U6 zgm8q7MWGF%za;)sr>Ay2vFIp7J~0v07TK^>Hh7*G^i&V|#A2d$KPfGsDu3rFrJ9~> zb5SSA-#1&SF7-Wx(ibeV!0!)61L`Wr5^=1v`VltRv4p6glyWY?&&`CA!M;E7O-;{?tn@1 px4Ht-@OwK!*8Gxi|C(r*l>Zdb{%fM{4~ADsvWt8|C?BQ6{{foZ;MM>D literal 0 HcmV?d00001 diff --git a/alfred/app.backup.weather_outfits/services/alfred_intents.py b/alfred/app.backup.weather_outfits/services/alfred_intents.py new file mode 100644 index 0000000..f2a2044 --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/alfred_intents.py @@ -0,0 +1,189 @@ +"""Conservative French voice router. Never guesses dates for destructive actions.""" +from __future__ import annotations + +import os +import re +import threading +import time as monotonic_time +import unicodedata +from datetime import date, datetime, timedelta, time +from zoneinfo import ZoneInfo + +TZ = ZoneInfo(os.getenv('ALFRED_TIMEZONE', 'Europe/Paris')) +MONTHS = {m:i for i,m in enumerate('janvier fevrier mars avril mai juin juillet aout septembre octobre novembre decembre'.split(),1)} +DAYS = {'lundi':0,'mardi':1,'mercredi':2,'jeudi':3,'vendredi':4,'samedi':5,'dimanche':6} +PENDING_LOCK = threading.Lock() +PENDING_DELETE = {'value':None, 'expires':0} +PENDING_CREATE = {'value':None, 'expires':0} + + +def simplify(text): + return ''.join(c for c in unicodedata.normalize('NFD',text.lower()) if unicodedata.category(c)!='Mn') + + +def parse_day(s, now=None): + now = now or datetime.now(TZ) + text = simplify(s) + today = now.date() + if re.search(r'\b(apres.demain)\b',text):return today+timedelta(days=2) + if re.search(r'\bdemain\b',text):return today+timedelta(days=1) + if re.search(r'\b(aujourd.hui|ce matin|cet apres.midi|ce soir|cette nuit|dans la journee)\b',text):return today + m = re.search(r'\b(?:le\s+)?(\d{1,2})(?:er)?\s+(janvier|fevrier|mars|avril|mai|juin|juillet|aout|septembre|octobre|novembre|decembre)(?:\s+(20\d{2}))?\b',text) + if m: + month=MONTHS[m.group(2)];day=int(m.group(1));year=int(m.group(3)) if m.group(3) else today.year + candidate=date(year,month,day) + if not m.group(3) and candidate < today: candidate=date(year+1,month,day) + return candidate + m=re.search(r'\b(?:le\s+)?(\d{1,2})[/-](\d{1,2})(?:[/-](20\d{2}))?\b',text) + if m: + year=int(m.group(3)) if m.group(3) else today.year + candidate=date(year,int(m.group(2)),int(m.group(1))) + if not m.group(3) and candidate < today: candidate=date(year+1,candidate.month,candidate.day) + return candidate + for d, weekday in DAYS.items(): + if re.search(r'\b'+d+r'\b',text): + delta=(weekday-today.weekday())%7 + if 'prochain' in text and delta == 0: delta=7 + return today+timedelta(days=delta) + return None + + +def parse_clock(s): + text=simplify(s) + m=re.search(r'\b(?:a|vers|de|pour)\s+(\d{1,2})(?:\s*h(?:\s*(\d{1,2}))?|\s*heures?(?:\s*(\d{1,2}))?)\b',text) + if not m: return None + hh=int(m.group(1)); mm=int(m.group(2) or m.group(3) or 0) + if hh>23 or mm>59: raise ValueError('Heure invalide') + return time(hh,mm) + + +def period(s, now=None): + now = now or datetime.now(TZ) + text=simplify(s) + explicit_day=parse_day(text, now) + morning = 'matin' in text + afternoon = 'apres-midi' in text + evening = 'ce soir' in text or 'ce soir' in text + days=re.search(r'\b(?:sur|pour|pendant|les)?\s*(\d{1,2})\s*jours?\b',text) + if explicit_day: + start=datetime.combine(explicit_day,time.min,TZ) + end=start+timedelta(days=1) + if morning:start=start.replace(hour=6);end=start.replace(hour=12) + elif afternoon:start=start.replace(hour=12);end=start.replace(hour=18) + elif evening:start=start.replace(hour=18);end=(start+timedelta(days=1)).replace(hour=0) + return start,end,'day' + if days: + n=int(days.group(1)) + if not 1<=n<=31: raise ValueError('Demander entre 1 et 31 jours') + start=datetime.combine(now.date(),time.min,TZ) + return start,start+timedelta(days=n), 'days' + if 'mois' in text: + start=datetime(now.year,now.month,1,tzinfo=TZ) + next_month = datetime(now.year+int(now.month==12), now.month%12+1,1,tzinfo=TZ) + return start,next_month,'month' + if 'semaine' in text: + start=datetime.combine(now.date()-timedelta(days=now.weekday()),time.min,TZ) + if 'prochaine' in text:start+=timedelta(days=7) + return start,start+timedelta(days=7),'week' + start=datetime.combine(now.date(),time.min,TZ) + return start,start+timedelta(days=7),'days' + + +def classify(text): + t=simplify(text).strip() + if not t:return None + if re.search(r'\b(annule|non merci|laisse tomber)\b', t) and (current_delete() or current_create()):return {'kind':'cancel_pending'} + if re.search(r'\b(confirme|oui supprime|confirmer la suppression|oui c.est bon|oui valide)\b',t): + if current_delete():return {'kind':'confirm_delete'} + if current_create():return {'kind':'confirm_create'} + if re.search(r'\b(accueil|reviens|retour|ferme (le|la) (planning|calendrier|vue)|ecran principal)\b',t) and not re.search(r'\b(rendez.vous|reunion|evenement)\b',t):return {'kind':'home'} + if re.search(r'\b(montre|affiche|voir|regarde)\b.*\b(camera|cameras|video|videos)\b',t):return {'kind':'cameras'} + if re.search(r'\b(affiche|montre|donne|quelles|quelle|voir)\b.*\b(temperature|temperatures|capteurs|chauffage)\b',t):return {'kind':'temperatures'} + # Do not hijack general requests for images, HA or Hermes. + if re.search(r'\b(supprime|efface|retire|annule)\b.*\b(rendez.vous|evenement|agenda|calendrier|planning)\b',t):return {'kind':'delete'} + if re.search(r'\b(ajoute|ajouter|cree|creer|inscris|planifie|note)\b.*\b(rendez.vous|evenement|agenda|calendrier|planning)\b',t):return {'kind':'create'} + cal_words=r'\b(agenda|calendrier|planning|rendez.vous|evenements?)\b' + query_words=r'\b(pre[v]?u|prevu|programme|organise|j.ai|ai.je|quoi|qu.est.ce|choses?|occupe|libre)\b' + time_words=r'\b(demain|aujourd.hui|matin|soir|apres-midi|journee|semaine|mois|jours?|novembre|janvier|fevrier|mars|avril|mai|juin|juillet|aout|septembre|octobre|decembre)\b' + if re.search(cal_words,t) or (re.search(query_words,t) and re.search(time_words,t)) or ('8 novembre' in t): + return {'kind':'calendar', 'question': bool(re.search(query_words,t))} + return None + + +def extract_create(text, calendars): + """Parse straightforward instructions only. Missing/ambiguous fields become a prefilled form.""" + raw=text.strip(); normalized=simplify(raw) + day=parse_day(raw) + hour=parse_clock(raw) + options=[c for c in calendars if c.get('can_create')] + preferred=os.getenv('HA_DEFAULT_CALENDAR','').strip() + matching=[] + for c in options: + if simplify(c['name']) in normalized or simplify(c['entity_id'].split('.',1)[-1]).replace('_',' ') in normalized: + matching.append(c) + calendar = matching[0]['entity_id'] if len(matching)==1 else None + if calendar is None and preferred in {c['entity_id'] for c in options}:calendar=preferred + if calendar is None and len(options)==1:calendar=options[0]['entity_id'] + # Strip date and time syntax to derive a plausible title. + title=re.sub(r'^.*?\b(?:ajoute|ajouter|cree|creer|inscris|planifie|note)\b', '', normalized, count=1) + title=re.sub(r'\b(?:dans|sur)\s+(?:le\s+)?calendrier\s+[\w\s_-]+?(?=\b(?:le|demain|aujourd.hui|a)\b|$)', ' ', title) + title=re.sub(r'\b(?:le\s+)?\d{1,2}\s+(?:'+ '|'.join(MONTHS)+ r')(?:\s+20\d{2})?\b',' ',title) + title=re.sub(r'\b(?:demain|aujourd.hui|apres.demain|lundi|mardi|mercredi|jeudi|vendredi|samedi|dimanche)\b',' ',title) + title=re.sub(r'\b(?:a|de)\s+\d{1,2}\s*(?:h|heures?)\s*\d{0,2}\b',' ',title) + title=re.sub(r'\b(?:dans|sur)\s+(?:le\s+)?(?:calendrier|agenda)\b.*$', ' ',title) + title=re.sub(r'^\s*(?:moi|un|une|le|la|les|rendez.vous|evenement|pour|de|a)\s+', '',title) + title=re.sub(r'\s+', ' ', title).strip(' ,.:') + # A guided confirmation is safer than using a guessed title. + title=title[:200] + data={'title':title,'calendar_id':calendar or '', 'date':day.isoformat() if day else '', + 'time':hour.strftime('%H:%M') if hour else '','duration_minutes':60} + complete=bool(day and hour and calendar and title and title not in ('calendrier','agenda','rendez vous')) + return data, complete + + +def extract_search(text): + normalized=simplify(text) + normalized=re.sub(r'^.*?\b(?:supprime|efface|retire|annule)\b','',normalized,count=1) + normalized=re.sub(r'\b(?:le\s+)?\d{1,2}\s+(?:'+ '|'.join(MONTHS)+ r')\b','',normalized) + normalized=re.sub(r'\b(demain|aujourd.hui|apres.demain|lundi|mardi|mercredi|jeudi|vendredi|samedi|dimanche)\b','',normalized) + normalized=re.sub(r'\b(de|du|mon|ma|un|une|le|la|les|rendez.vous|evenement|agenda|calendrier|planning)\b',' ',normalized) + return re.sub(r'\s+',' ',normalized).strip() + + +def set_delete(event): + with PENDING_LOCK: + PENDING_DELETE['value']=event + PENDING_DELETE['expires']=monotonic_time.monotonic()+90 + + +def current_delete(): + with PENDING_LOCK: + if monotonic_time.monotonic()>PENDING_DELETE['expires']: + PENDING_DELETE['value']=None + return dict(PENDING_DELETE['value']) if PENDING_DELETE['value'] else None + + +def clear_delete(): + with PENDING_LOCK: + p=PENDING_DELETE['value'];PENDING_DELETE['value']=None + return p + + +def set_create(details): + with PENDING_LOCK: + PENDING_CREATE['value']=dict(details) + PENDING_CREATE['expires']=monotonic_time.monotonic()+90 + +def current_create(): + with PENDING_LOCK: + if monotonic_time.monotonic()>PENDING_CREATE['expires']: + PENDING_CREATE['value']=None + return dict(PENDING_CREATE['value']) if PENDING_CREATE['value'] else None + +def clear_create(): + with PENDING_LOCK: + p=PENDING_CREATE['value'];PENDING_CREATE['value']=None + return p + +def cancel_pending(): + clear_create();clear_delete() diff --git a/alfred/app.backup.weather_outfits/services/alfred_views_controller.py b/alfred/app.backup.weather_outfits/services/alfred_views_controller.py new file mode 100644 index 0000000..8984a8f --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/alfred_views_controller.py @@ -0,0 +1,132 @@ +"""Fast voice routing for visual actions; Hermes still handles general tasks.""" +from __future__ import annotations + +import os +from datetime import datetime, timedelta, time +from zoneinfo import ZoneInfo + +from . import ha_views as ha +from . import alfred_intents as intents +from . import ha_weather + +TZ = ZoneInfo(os.getenv('ALFRED_TIMEZONE','Europe/Paris')) + + +def _label(s,e): + return f"du {s:%d/%m} au {(e-timedelta(days=1)):%d/%m}" + + +def _event_summary(events, question): + count=len(events) + if count == 0:return "Non, je n'ai trouvé aucun événement pour cette période." if question else 'Aucun événement prévu sur cette période.' + names=', '.join(ev['title'] for ev in events[:3]) + if question: + return f"Oui, {count} événement{'s' if count>1 else ''} prévu{'s' if count>1 else ''}. {names}." if count<=3 else f"Oui, {count} événements sont prévus. Notamment : {names}." + return f"Voici ton planning avec {count} événement{'s' if count>1 else ''}." + + +def _calendar(text, question=False): + start,end,view=intents.period(text) + payload=ha.get_events(start,end) + content={'type':'calendar','view':view,'title':'Planning','start':start.isoformat(),'end':end.isoformat(), + 'events':payload['events'],'calendars':payload['calendars'],'errors':payload['errors']} + speech=_event_summary(payload['events'],question) + if payload['errors']: speech+=' Certains calendriers sont indisponibles.' + return content,speech + + +def _create(text): + calendars=ha.list_calendars() + details, complete=intents.extract_create(text,calendars) + if not complete: + return {'type':'calendar_form','values':details,'calendars':calendars},'Je prépare le rendez-vous. Complète ou vérifie les informations à l’écran avant de l’enregistrer.' + # No calendar mutation until a second explicit confirmation. + intents.set_create(details) + pretty=datetime.fromisoformat(details['date']).strftime('%d/%m') + speech=f"Je vais ajouter {details['title']} le {pretty} à {details['time']}. Dis confirme pour enregistrer, ou annule." + return {'type':'calendar_form','values':details,'calendars':calendars,'awaiting_confirmation':True},speech + + +def _delete(text): + now=datetime.now(TZ) + day=intents.parse_day(text,now) + if day: + start=datetime.combine(day,time.min,TZ); end=start+timedelta(days=1) + else: + start=datetime.combine(now.date(),time.min,TZ);end=start+timedelta(days=45) + data=ha.get_events(start,end) + needle=intents.extract_search(text) + events=[e for e in data['events'] if intents.simplify(needle) in intents.simplify(e['title'])] if needle else data['events'] + if not events: + return {'type':'calendar','view':'days','start':start.isoformat(),'end':end.isoformat(),**data},'Je ne trouve pas cet événement. Aucun événement n’a été supprimé.' + if len(events)!=1: + return {'type':'delete_candidates','events':events[:30]},f"J'ai trouvé {len(events)} événements correspondants. Choisis celui à supprimer sur l'écran." + ev=events[0] + if not ev['can_delete']: + return {'type':'delete_candidates','events':[ev]},'Ce calendrier ne permet pas la suppression de cet événement depuis Alfred.' + intents.set_delete(ev) + warning=' Attention, si cet événement est récurrent, cela peut supprimer toute la série.' if not ev['recurrence_id'] else '' + return {'type':'delete_candidates','events':[ev], 'awaiting_confirmation':True},f"Confirme la suppression de {ev['title']}, ou dis annule.{warning}" + + +def _confirm_delete(): + e=intents.current_delete() + if not e:return {'type':'text','text':'Confirmation expirée.'},'La demande de suppression a expiré.' + # Recheck existence in HA to avoid deleting an event that changed meanwhile. + start=datetime.fromisoformat(e['start'])-timedelta(days=1) + end=datetime.fromisoformat(e['end'])+timedelta(days=1) + found=ha.get_events(start,end,[e['calendar_id']])['events'] + if not any(x['id']==e['id'] and x['start']==e['start'] and x['recurrence_id']==e['recurrence_id'] for x in found): + intents.clear_delete() + return {'type':'text','text':'Cet événement a changé. Suppression annulée.'},'Cet événement a changé. Rien n’a été supprimé.' + ha.delete_event(e['calendar_id'],e['id'],e['recurrence_id'] or None) + intents.clear_delete() + content,speech=_calendar('planning sur 7 jours') + return content,'Événement supprimé. '+speech + + +def _confirm_create(): + data=intents.current_create() + if not data:return {'type':'text','text':'Confirmation expirée.'},'La demande de création a expiré.' + if os.getenv('ALFRED_ALLOW_CALENDAR_WRITES','false').lower() not in ('1','true','yes'): + return {'type':'calendar_form','values':data,'calendars':ha.list_calendars()},'L’écriture est désactivée. Active ALFRED_ALLOW_CALENDAR_WRITES dans la configuration.' + start=datetime.fromisoformat(data['date']+'T'+data['time']).replace(tzinfo=TZ) + end=start+timedelta(minutes=int(data.get('duration_minutes',60))) + ha.create_event(data['calendar_id'],data['title'],start,end) + intents.clear_create() + content,speech=_calendar('planning sur 7 jours') + return content,'Événement ajouté. '+speech + + +def route(text, last_view=None): + # Weather is a read-only dynamic view; avoid sending routine forecasts to Hermes. + if ha_weather.is_weather_request(text, last_view): + data = ha_weather.get_weather() + return data, ha_weather.describe(data, text) + intent=intents.classify(text) + # Short temporal follow-ups to a displayed calendar: 'Et demain ?', 'Ce matin ?' + if intent is None and last_view == 'calendar' and len(text.strip()) < 45: + t=intents.simplify(text) + if any(word in t for word in ('demain','matin','apres-midi','soir','journee','semaine','mois','jours','novembre','decembre','janvier','fevrier','mars','avril','mai','juin','juillet','aout','septembre','octobre')): + intent={'kind':'calendar','question':True} + if intent is None:return None + kind=intent['kind'] + if kind=='home':return {'type':'home','title':'Alfred','text':'À ton écoute.'},'Je reviens à l’accueil.' + if kind=='calendar':return _calendar(text, intent.get('question',False)) + if kind=='create':return _create(text) + if kind=='delete':return _delete(text) + if kind=='confirm_delete': + if os.getenv('ALFRED_ALLOW_CALENDAR_WRITES','false').lower() not in ('1','true','yes'): + return {'type':'text','text':'Écriture calendrier désactivée.'},'La suppression est désactivée dans la configuration.' + return _confirm_delete() + if kind=='confirm_create':return _confirm_create() + if kind=='cancel_pending': + intents.cancel_pending() + return {'type':'home','text':'Demande annulée.'},'D’accord, aucune modification effectuée.' + if kind=='temperatures': + temps=ha.get_temperatures() + return {'type':'temperatures','items':temps},f"Voici {len(temps)} mesures de température provenant de Home Assistant." + if kind=='cameras': + cameras=ha.list_cameras() + return {'type':'cameras','items':cameras},f"Voici {len(cameras)} caméras Home Assistant disponibles." + return None diff --git a/alfred/app.backup.weather_outfits/services/audio.py b/alfred/app.backup.weather_outfits/services/audio.py new file mode 100644 index 0000000..390f1d4 --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/audio.py @@ -0,0 +1,392 @@ +"""Audio local sounddevice.RawInputStreamd'Alfred, repris de l'application historique de Pierre. + +STT: Vosk + sounddevice, avec fallback arecord/ALSA. +TTS: Piper local, avec fallback espeak puis spd-say. +Sortie: haut-parleur local via paplay/aplay. + +Ce module ne parle pas directement au LLM/Hermes: il retourne le texte reconnu +à l'orchestrateur Alfred, afin de conserver Hermes comme cerveau unique. +""" +from __future__ import annotations +from functools import lru_cache + +import time +import json +import os +import queue +import shutil +import subprocess +import tempfile +import threading +from dataclasses import dataclass +from typing import Callable, Optional + + +def _cmd_ok(cmd, **kwargs): + try: + return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + check=True, text=True, **kwargs) + except Exception: + return None + + +def audio_backend_detect() -> str: + configured = os.getenv("AUDIO_BACKEND", "auto").lower() + if configured in ("pactl", "alsa"): + return configured + if shutil.which("pactl"): + info = _cmd_ok(["pactl", "info"]) + if info and "Server Name" in info.stdout: + return "pactl" + if shutil.which("amixer"): + return "alsa" + return "none" + + +def _which(cmd): + return shutil.which(cmd) + + +def _is_piper_tts(binary: Optional[str]) -> bool: + if not binary: + return False + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + if "gtk" in out or "ratbag" in out: + return False + return (("--model" in out or " -m " in out) and + ("--output_file" in out or " -o " in out)) + except Exception: + return False + + +def _find_piper_binary() -> Optional[str]: + candidates = [ + os.getenv("PIPER_BIN"), + "/usr/local/bin/piper-tts", "/usr/bin/piper-tts", _which("piper-tts"), + "/usr/local/bin/piper", "/usr/bin/piper", _which("piper"), + ] + for candidate in candidates: + if candidate and _is_piper_tts(candidate): + return candidate + return None + + +def _piper_supports_long_flag(binary: str) -> bool: + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + return "--model" in out and "--output_file" in out + except Exception: + return True + +### +def tts_backend_detect() -> str: + """Retourne le moteur TTS configuré pour Alfred.""" + + if os.getenv("PIPER_HOST"): + return "wyoming-piper" + + if _which("espeak-ng"): + return "espeak-ng" + + return "none" + + +''' +def tts_backend_detect() -> str: + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + if piper and model and os.path.exists(model): + return "piper" + if _which("espeak"): + return "espeak" + if _which("spd-say"): + return "spd-say" + return "none" +''' +#### + +def speak_text(text: str): + """Synthèse vocale via Wyoming Piper, avec secours eSpeak.""" + from services.audio_settings import speaker_muted + if speaker_muted(): + return + if not text or not text.strip(): + return + + text = text[:int(os.getenv("TTS_MAX_CHARS", "2000"))] + + try: + from services.piper_tts import speak as piper_speak + + print("[ALFRED TTS] Utilisation de Wyoming Piper", flush=True) + piper_speak(text) + return + + except Exception as exc: + print(f"[ALFRED TTS] Piper indisponible : {exc}", flush=True) + + # Secours eSpeak + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + print("[ALFRED TTS] Secours eSpeak", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + except Exception as exc: + print(f"[ALFRED TTS] Erreur eSpeak : {exc}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + +''' +def speak_text(text: str): + if not text or not text.strip(): + return + + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + start = time.monotonic() + print("[ALFRED TTS] Génération audio...", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + print("[ALFRED TTS] Lecture audio...", flush=True) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + print( + f"[ALFRED TTS] Terminé en {time.monotonic() - start:.2f}s", + flush=True + ) + + except Exception as e: + print(f"[ALFRED TTS] Erreur : {e}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + + + + +def speak_text(text: str): + """Synthétise et joue localement le texte. Retourne (ok, backend, erreur).""" + text = (text or "").strip() + if not text: + return False, "none", "texte vide" + text = text[: int(os.getenv("TTS_MAX_CHARS", "2000"))] + + backend = audio_backend_detect() + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + + if piper and model and os.path.exists(model): + wav_path = None + try: + with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp: + wav_path = tmp.name + cmd = [piper] + if _piper_supports_long_flag(piper): + cmd += ["--model", model, "--output_file", wav_path] + else: + cmd += ["-m", model, "-o", wav_path] + p = subprocess.run(cmd, input=text, text=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False) + if p.returncode != 0: + return False, "piper", (p.stderr or p.stdout or "")[:2000] + if not os.path.exists(wav_path) or os.path.getsize(wav_path) == 0: + return False, "piper", "Piper n'a pas produit de WAV" + if backend == "pactl" and shutil.which("paplay"): + play = subprocess.run(["paplay", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + elif shutil.which("aplay"): + play = subprocess.run(["aplay", "-q", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + else: + return False, "piper", "paplay/aplay introuvable" + return play.returncode == 0, "piper", None if play.returncode == 0 else "lecture WAV échouée" + except Exception as exc: + return False, "piper", str(exc) + finally: + if wav_path: + try: os.unlink(wav_path) + except OSError: pass + + if _which("espeak"): + args = ["espeak", "-v", os.getenv("ESPEAK_VOICE", "fr"), text] + if backend == "pactl" and shutil.which("padsp"): + args = ["padsp"] + args + ok = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode == 0 + return ok, "espeak", None if ok else "espeak a échoué" + + if _which("spd-say"): + ok = subprocess.run(["spd-say", "-l", "fr", text], stdout=subprocess.PIPE, + stderr=subprocess.PIPE).returncode == 0 + return ok, "spd-say", None if ok else "spd-say a échoué" + return False, "none", "aucun backend TTS disponible" +''' + +_tts_lock = threading.Lock() +def speak_async(text: str, on_start: Optional[Callable] = None, + on_end: Optional[Callable] = None) -> bool: + """Lecture non bloquante; refuse d'empiler deux réponses vocales.""" + if _tts_lock.locked(): + return False + def worker(): + with _tts_lock: + if on_start: on_start() + try: speak_text(text) + finally: + if on_end: on_end() + threading.Thread(target=worker, daemon=True).start() + return True + + +@dataclass +class STTResult: + text: str = "" + error: Optional[str] = None + + + +@lru_cache(maxsize=2) +def get_vosk_model(model_path): + from vosk import Model + + print(f"[ALFRED] Chargement du modèle Vosk : {model_path}", flush=True) + model = Model(model_path) + print("[ALFRED] Modèle Vosk prêt", flush=True) + + return model + + +def listen_once(stop_event: Optional[threading.Event] = None, + on_partial: Optional[Callable[[str], None]] = None, + on_level: Optional[Callable[[float], None]] = None) -> STTResult: + """Attend une phrase finale Vosk et la retourne. + + Reprend le comportement du prototype: sounddevice en priorité, arecord sinon. + Le niveau audio transmis à l'UI est une approximation RMS sur int16. + """ + try: + import numpy as np + from vosk import Model, KaldiRecognizer + except Exception as exc: + return STTResult(error=f"Dépendance STT absente: {exc}") + + model_path = os.getenv("VOSK_MODEL_PATH", "") + sample_rate = int(os.getenv("VOSK_SAMPLE_RATE", "16000")) + if not model_path or not os.path.exists(model_path): + return STTResult(error=f"Modèle Vosk introuvable: {model_path}") + try: +# rec = KaldiRecognizer(Model(model_path), sample_rate) + rec = KaldiRecognizer(get_vosk_model(model_path), sample_rate) + except Exception as exc: + return STTResult(error=f"Init Vosk: {exc}") + + def process(data: bytes) -> Optional[str]: + if on_level and data: + try: + a = np.frombuffer(data, dtype=np.int16).astype(np.float32) + rms = float(np.sqrt(np.mean(a * a))) if a.size else 0.0 + on_level(min(1.0, rms / 6000.0)) + except Exception: + pass + if rec.AcceptWaveform(data): + text = (json.loads(rec.Result() or "{}").get("text") or "").strip() + if text: + return text + elif on_partial: + partial = json.loads(rec.PartialResult() or "{}").get("partial", "") + print(f"[ALFRED STT] Reconnu partiellement : {partial}", flush=True) + on_partial(partial) + return None + + try: + import sounddevice as sd + q = queue.Queue(maxsize=32) + def callback(indata, frames, time_info, status): + try: + q.put_nowait(bytes(indata)) + except queue.Full: + pass + device = os.getenv("AUDIO_INPUT_DEVICE") or None + with sd.RawInputStream(samplerate=sample_rate, blocksize=8000, dtype="int16", + channels=1, callback=callback, device=device): + while not (stop_event and stop_event.is_set()): + try: + chunk = q.get(timeout=0.25) + except queue.Empty: + continue + text = process(chunk) + if text: + return STTResult(text=text) + except Exception as exc: + print(f"[ALFRED STT] Erreur microphone sounddevice : {exc}", flush=True) + +# except Exception: +# pass + + if stop_event and stop_event.is_set(): + return STTResult() + + if not shutil.which("arecord"): + return STTResult(error="sounddevice indisponible et arecord introuvable") + cmd = ["arecord", "-q", "-f", "S16_LE", "-r", str(sample_rate), "-c", "1"] + alsa_device = os.getenv("ALSA_CAPTURE_DEVICE", "").strip() + if alsa_device: + cmd += ["-D", alsa_device] + proc = None + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) + while not (stop_event and stop_event.is_set()): + chunk = proc.stdout.read(8000) + if not chunk: + continue + text = process(chunk) + if text: + return STTResult(text=text) + return STTResult() + except Exception as exc: + return STTResult(error=f"arecord: {exc}") + finally: + if proc and proc.poll() is None: + proc.terminate() + + +def devices(): + try: +# import sounddevice as sd +# return sd.query_devices() + print("[ALFRED STT] Périphériques audio :", flush=True) + print(sd.query_devices(), flush=True) + print("[ALFRED STT] Périphérique par défaut :", sd.default.device, flush=True) + return sd.query_devices() + except Exception: + return [] diff --git a/alfred/app.backup.weather_outfits/services/audio_settings.py b/alfred/app.backup.weather_outfits/services/audio_settings.py new file mode 100644 index 0000000..b310c32 --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/audio_settings.py @@ -0,0 +1,77 @@ +"""Réglages audio Alfred : ALSA carte 1 et états logiques thread-safe.""" +import os +import re +import subprocess +import threading + +_LOCK = threading.RLock() +_STATE = {'volume': 70, 'speaker_muted': False, 'mic_muted': False, 'mic_gain': None} +_CARD = os.getenv('ALFRED_ALSA_CARD', '1') +_CONTROL = os.getenv('ALFRED_ALSA_VOLUME_CONTROL', 'Master') + +def _amixer(*args): + return subprocess.run(['amixer', '-c', _CARD, *args], capture_output=True, + text=True, timeout=5, check=True).stdout + +def initialize(): + """Lit le volume existant sans modifier les réglages audio au démarrage.""" + try: + output = _amixer('sget', _CONTROL) + matches = re.findall(r'\[(\d+)%\]', output) + with _LOCK: + if matches: + _STATE['volume'] = int(matches[0]) + # ALFRED_MIC_GAIN_V2 : gain Capture, sans toucher à Mic Boost. + try: + gain_out = _amixer('sget', os.getenv('ALFRED_ALSA_CAPTURE_CONTROL', 'Capture')) + gains = re.findall(r'\[(\d+)%\]', gain_out) + _STATE['mic_gain'] = int(gains[0]) if gains else None + except (OSError, subprocess.SubprocessError) as exc: + print(f'[ALFRED AUDIO] Gain Capture indisponible : {exc}', flush=True) + # Ne pas déduire speaker_muted du mixer : état propre à Alfred. + except (OSError, subprocess.SubprocessError) as exc: + print(f'[ALFRED AUDIO] Lecture ALSA impossible : {exc}', flush=True) + +def get_settings(): + with _LOCK: + return dict(_STATE) + +def update_settings(payload): + if not isinstance(payload, dict): + raise ValueError('Un objet JSON est attendu') + allowed = {'volume', 'speaker_muted', 'mic_muted', 'mic_gain'} + if set(payload) - allowed: + raise ValueError('Paramètre audio inconnu') + with _LOCK: + new = dict(_STATE) + if 'volume' in payload: + v = payload['volume'] + if isinstance(v, bool) or not isinstance(v, (int, float)) or not 0 <= v <= 100: + raise ValueError('volume doit être entre 0 et 100') + new['volume'] = round(v) + if 'mic_gain' in payload: + v = payload['mic_gain'] + if isinstance(v, bool) or not isinstance(v, (int, float)) or not 0 <= v <= 100: + raise ValueError('mic_gain doit être entre 0 et 100') + new['mic_gain'] = round(v) + for key in ('speaker_muted', 'mic_muted'): + if key in payload: + if not isinstance(payload[key], bool): + raise ValueError(f'{key} doit être un booléen') + new[key] = payload[key] + # Le gain Capture ne change que sur demande explicite. + # Mic Boost et Internal Mic Boost restent inchangés. + if 'mic_gain' in payload and new['mic_gain'] != _STATE['mic_gain']: + _amixer('sset', os.getenv('ALFRED_ALSA_CAPTURE_CONTROL', 'Capture'), f"{new['mic_gain']}%") + if new['volume'] != _STATE['volume']: + _amixer('sset', _CONTROL, f"{new['volume']}%") + _STATE.update(new) + return dict(_STATE) + +def speaker_muted(): + with _LOCK: + return _STATE['speaker_muted'] + +def mic_muted(): + with _LOCK: + return _STATE['mic_muted'] diff --git a/alfred/app.backup.weather_outfits/services/camera.py b/alfred/app.backup.weather_outfits/services/camera.py new file mode 100644 index 0000000..7459ddc --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/camera.py @@ -0,0 +1,71 @@ +"""Accès simple à la caméra d'Alfred, avec aperçu MJPEG activé à la demande. + +Une seule lecture par instance Alfred. La vidéo ne démarre que lorsque +l'utilisateur clique sur Activer, pour éviter de monopoliser la caméra. +""" +import os +import threading +import time + +_CAMERA_LOCK = threading.Lock() + + +def snapshot(path, index=0): + import cv2 + if not _CAMERA_LOCK.acquire(blocking=False): + raise RuntimeError('Caméra déjà utilisée par la prévisualisation Alfred') + try: + cap = cv2.VideoCapture(index) + try: + ok, frame = cap.read() + finally: + cap.release() + if not ok: + raise RuntimeError('Impossible de lire la caméra locale') + cv2.imwrite(path, frame) + return path + finally: + _CAMERA_LOCK.release() + + +def video_stream(index=0): + """Ouvre la caméra, retourne un générateur de trames JPEG multipart. + + L'appelant doit retourner ce générateur dans une réponse Flask en streaming. + L'absence de cv2 ou de webcam déclenche une exception avant HTTP 200. + """ + import cv2 + if not _CAMERA_LOCK.acquire(blocking=False): + raise RuntimeError('Prévisualisation vidéo déjà active') + + cap = None + try: + cap = cv2.VideoCapture(index) + if not cap.isOpened(): + cap.release() + raise RuntimeError(f'Caméra {index} indisponible') + cap.set(cv2.CAP_PROP_FRAME_WIDTH, int(os.getenv('CAMERA_WIDTH', '640'))) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, int(os.getenv('CAMERA_HEIGHT', '480'))) + fps = min(15.0, max(1.0, float(os.getenv('CAMERA_FPS', '8')))) + quality = min(95, max(35, int(os.getenv('CAMERA_JPEG_QUALITY', '75')))) + except Exception: + if cap is not None: + cap.release() + _CAMERA_LOCK.release() + raise + + def frames(): + try: + while True: + ok, frame = cap.read() + if not ok: + break + ok_jpeg, encoded = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, quality]) + if ok_jpeg: + yield b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + encoded.tobytes() + b'\r\n' + time.sleep(1.0 / fps) + finally: + cap.release() + _CAMERA_LOCK.release() + + return frames() diff --git a/alfred/app.backup.weather_outfits/services/camera_settings.py b/alfred/app.backup.weather_outfits/services/camera_settings.py new file mode 100644 index 0000000..7665385 --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/camera_settings.py @@ -0,0 +1,187 @@ +"""Réglages V4L2 de la webcam locale pour l'interface Alfred. + +API en lecture/écriture sécurisée par une liste blanche de contrôles, plages +et états renvoyés par le pilote. Aucun accès shell ni commande arbitraire. +""" +from __future__ import annotations + +import json +import os +import re +import shutil +import subprocess +import tempfile +import threading +from pathlib import Path + +_ALLOWED = { + 'brightness', 'contrast', 'saturation', 'hue', 'gamma', 'gain', + 'sharpness', 'backlight_compensation', 'power_line_frequency', + 'white_balance_automatic', 'white_balance_temperature', + 'auto_exposure', 'exposure_time_absolute', 'exposure_dynamic_framerate', +} +_AUTO = ('white_balance_automatic', 'auto_exposure') +_LINE = re.compile(r'^\s*(\w+)\s+0x[\da-f]+\s+\((int|bool|menu)\)\s*:\s*(.*)$', re.I) +_KV = re.compile(r'(min|max|step|default|value)=(-?\d+)') +_MENU = re.compile(r'^\s*(-?\d+):\s*(.+?)\s*$') +_LOCK = threading.RLock() + + +class CameraSettingsError(RuntimeError): + pass + + +def _device() -> str: + return os.getenv('CAMERA_DEVICE') or f"/dev/video{int(os.getenv('CAMERA_INDEX', '0'))}" + + +def _config_path() -> Path: + return Path(os.getenv('CAMERA_SETTINGS_FILE', str( + Path(__file__).resolve().parent.parent / 'config' / 'camera_controls.json' + ))).expanduser() + + +def _run(*args: str) -> str: + binary = shutil.which('v4l2-ctl') + if not binary: + raise CameraSettingsError('v4l2-ctl est absent : installer le paquet v4l-utils.') + try: + result = subprocess.run( + [binary, '-d', _device(), *args], capture_output=True, + text=True, timeout=6, check=False + ) + except (OSError, subprocess.TimeoutExpired) as exc: + raise CameraSettingsError(f'Caméra indisponible ({exc}).') from exc + if result.returncode != 0: + explanation = (result.stderr or result.stdout or 'erreur inconnue').strip() + raise CameraSettingsError(f'V4L2 : {explanation[:300]}') + return result.stdout + + +def _parse_controls(output: str) -> dict: + controls: dict[str, dict] = {} + current = None + for line in output.splitlines(): + match = _LINE.match(line) + if match: + name, kind, raw = match.groups() + current = name if name in _ALLOWED else None + if current: + props = {k: int(v) for k, v in _KV.findall(raw)} + controls[current] = { + 'kind': kind.lower(), 'inactive': 'inactive' in raw, + 'menu': [], **props, + } + continue + menu = _MENU.match(line) + if menu and current and controls[current]['kind'] == 'menu': + value, title = menu.groups() + controls[current]['menu'].append({'value': int(value), 'label': title}) + return controls + + +def _get_controls() -> dict: + return _parse_controls(_run('--list-ctrls-menus')) + + +def get_settings() -> dict: + with _LOCK: + return {'device': _device(), 'controls': _get_controls()} + + +def _validate(control: object, value: object, controls: dict) -> int: + if not isinstance(control, str) or control not in _ALLOWED or control not in controls: + raise ValueError('Réglage caméra inconnu ou non pris en charge.') + data = controls[control] + if data['inactive']: + raise ValueError('Réglage indisponible tant que le mode automatique est actif.') + if type(value) is not int: # écarte bool et chaînes + raise ValueError('Valeur entière attendue.') + if data['kind'] == 'bool' and value not in (0, 1): + raise ValueError('Valeur booléenne attendue (0 ou 1).') + if data['kind'] == 'menu': + choices = [v['value'] for v in data['menu']] + if choices and value not in choices: + raise ValueError('Valeur absente des options de la caméra.') + minimum, maximum = data.get('min', 0), data.get('max', 1) + step = data.get('step', 1) + if not minimum <= value <= maximum or (value - minimum) % max(1, step): + raise ValueError(f'Valeur hors plage ({minimum} à {maximum}, pas {step}).') + return value + + +def _write_config(controls: dict) -> None: + path = _config_path() + path.parent.mkdir(parents=True, exist_ok=True) + snapshot = {name: props['value'] for name, props in controls.items() + if not props['inactive'] and 'value' in props} + fd = None + temp = None + try: + fd, temp = tempfile.mkstemp(prefix='.camera_', suffix='.tmp', dir=path.parent) + with os.fdopen(fd, 'w', encoding='utf-8') as stream: + fd = None + json.dump({'controls': snapshot}, stream, indent=2) + stream.write('\n') + os.replace(temp, path) + temp = None + except OSError as exc: + raise CameraSettingsError(f'Impossible de mémoriser les réglages caméra : {exc}') from exc + finally: + if fd is not None: + os.close(fd) + if temp is not None and os.path.exists(temp): + os.unlink(temp) + + +def update_setting(control: object, value: object) -> dict: + with _LOCK: + controls = _get_controls() + n = _validate(control, value, controls) + _run('--set-ctrl', f'{control}={n}') + refreshed = _get_controls() # réévalue les contrôles devenus actifs/inactifs + _write_config(refreshed) + return {'device': _device(), 'controls': refreshed} + + +def reset_defaults() -> dict: + """Restaure les valeurs exposées par le pilote, sans forcer les champs inactifs.""" + with _LOCK: + original = _get_controls() + # D'abord les modes auto, puis les contrôles restés actifs. + for name in [*_AUTO, *(c for c in original if c not in _AUTO)]: + live = _get_controls() + item = live.get(name) + if not item or item['inactive'] or 'default' not in item: + continue + try: + _run('--set-ctrl', f"{name}={item['default']}") + except CameraSettingsError as exc: + raise CameraSettingsError(f'Échec restauration {name} : {exc}') from exc + refreshed = _get_controls() + _write_config(refreshed) + return {'device': _device(), 'controls': refreshed} + + +def restore_saved_settings() -> None: + """Applique les valeurs persistées; une webcam absente ne bloque pas Flask.""" + with _LOCK: + path = _config_path() + if not path.is_file(): + return + try: + saved = json.loads(path.read_text(encoding='utf-8')).get('controls', {}) + if not isinstance(saved, dict): + raise ValueError('Format invalide') + for name in [*_AUTO, *(n for n in saved if n not in _AUTO)]: + if name not in saved: + continue + live = _get_controls() + try: + n = _validate(name, saved[name], live) + except ValueError: + continue # matériel/pilote modifié ou contrôle devenu inactif + _run('--set-ctrl', f'{name}={n}') + print('[ALFRED CAMERA] Réglages restaurés.', flush=True) + except (OSError, ValueError, CameraSettingsError) as exc: + print(f'[ALFRED CAMERA] Restauration ignorée : {exc}', flush=True) diff --git a/alfred/app.backup.weather_outfits/services/ha_views.py b/alfred/app.backup.weather_outfits/services/ha_views.py new file mode 100644 index 0000000..41e2bdf --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/ha_views.py @@ -0,0 +1,236 @@ +"""Home Assistant data adapter for Alfred. Tokens remain on the Flask host.""" +from __future__ import annotations + +import os +import re +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import date, datetime, time, timedelta +from urllib.parse import quote, urlparse +from zoneinfo import ZoneInfo + +import requests + +TZ = ZoneInfo(os.getenv('ALFRED_TIMEZONE', 'Europe/Paris')) +CREATE = 1 +DELETE = 2 + +class HAError(RuntimeError): + pass + + +def _cfg(): + base = (os.getenv('HA_URL') or os.getenv('HOME_ASSISTANT_URL') or '').strip().rstrip('/') + token = (os.getenv('HA_TOKEN') or os.getenv('HOME_ASSISTANT_TOKEN') or '').strip() + if not base or not token: + raise HAError('Configurer HA_URL et HA_TOKEN dans l’environnement du service Alfred.') + if urlparse(base).scheme not in ('http','https'): + raise HAError('HA_URL doit commencer par http:// ou https://') + return base, token + + +def _request(method, path, *, params=None, payload=None, timeout=12): + base, token = _cfg() + try: + response = requests.request(method, base + path, + params=params, json=payload, + headers={'Authorization': 'Bearer '+token, 'Accept':'application/json'}, + timeout=timeout) + response.raise_for_status() + return response.json() if response.content else None + except requests.RequestException as exc: + code = exc.response.status_code if exc.response is not None else 'réseau' + raise HAError(f'Home Assistant indisponible ou action refusée ({code}).') from exc + + +def states(): + return _request('GET', '/api/states') + + +def _allowed(): + return {s.strip() for s in os.getenv('HA_CALENDARS', '').split(',') if s.strip()} + + +def list_calendars(): + raw = _request('GET', '/api/calendars') or [] + allow = _allowed() + state_map = {s.get('entity_id'):s for s in states() if s.get('entity_id','').startswith('calendar.')} + items = [] + for c in raw: + eid = str(c.get('entity_id','')) + if not eid.startswith('calendar.') or (allow and eid not in allow): + continue + st = state_map.get(eid, {}) + features = int((st.get('attributes') or {}).get('supported_features') or 0) + items.append({'entity_id':eid, 'name':c.get('name') or eid.split('.',1)[-1], + 'can_create':bool(features & CREATE), 'can_delete':bool(features & DELETE)}) + return sorted(items, key=lambda x:x['name'].casefold()) + + +def _parse_dt(raw): + # HA REST can return RFC3339 strings, date strings, or {date}/{dateTime} dictionaries. + if isinstance(raw, dict): + raw = raw.get('dateTime') or raw.get('date') + if not isinstance(raw, str) or not raw: + raise ValueError('Date de calendrier invalide') + if len(raw) == 10: + return datetime.combine(date.fromisoformat(raw), time.min, TZ), True + dt = datetime.fromisoformat(raw.replace('Z', '+00:00')) + return (dt.replace(tzinfo=TZ) if dt.tzinfo is None else dt.astimezone(TZ)), False + + +def _normalize_event(e, cal): + start, all_day = _parse_dt(e.get('start')) + end, _ = _parse_dt(e.get('end')) + return { + 'id':str(e.get('uid') or ''), 'recurrence_id':str(e.get('recurrence_id') or ''), + 'title':str(e.get('summary') or '(Sans titre)'), + 'description':str(e.get('description') or ''), + 'location':str(e.get('location') or ''), + 'start':start.isoformat(), 'end':end.isoformat(), 'all_day':all_day, + 'calendar_id':cal['entity_id'], 'calendar_name':cal['name'], + 'can_delete':cal['can_delete'] and bool(e.get('uid')), + 'source':'home_assistant' + } + + +def get_events(start:datetime, end:datetime, calendar_ids=None): + if start.tzinfo is None or end.tzinfo is None or start >= end: + raise ValueError('Période incorrecte') + if (end-start).days > 65: + raise ValueError('Période limitée à 65 jours') + available = list_calendars() + ids = set(calendar_ids or [c['entity_id'] for c in available]) + if not ids.issubset({c['entity_id'] for c in available}): + raise ValueError('Calendrier non autorisé') + selected = [c for c in available if c['entity_id'] in ids] + results, errors = [], [] + def load(cal): + raw = _request('GET', '/api/calendars/'+quote(cal['entity_id'], safe=''), + params={'start':start.isoformat(), 'end':end.isoformat()}) or [] + return [_normalize_event(item,cal) for item in raw] + with ThreadPoolExecutor(max_workers=min(5,len(selected)) or 1) as pool: + futures = {pool.submit(load,c):c for c in selected} + for f in as_completed(futures): + try: results.extend(f.result()) + except (HAError,ValueError,KeyError) as exc: errors.append(f"{futures[f]['name']}: {exc}") + results.sort(key=lambda e:(e['start'],e['title'].lower())) + return {'events':results, 'calendars':available, 'errors':errors, + 'start':start.isoformat(), 'end':end.isoformat()} + + +def _find_calendar(entity_id, capability): + cal = next((c for c in list_calendars() if c['entity_id'] == entity_id),None) + if cal is None: + raise HAError('Calendrier absent ou non autorisé.') + if not cal[capability]: + raise HAError('Ce calendrier ne permet pas cette opération depuis Home Assistant.') + return cal + + +def create_event(entity_id, title, start, end, description='', location='', all_day=False): + _find_calendar(entity_id, 'can_create') + if not title.strip() or len(title) > 200: + raise ValueError('Titre requis (200 caractères maximum).') + if start >= end: + raise ValueError('La fin doit être après le début.') + payload = {'entity_id':entity_id, 'summary':title.strip(), + 'description':description[:2000], 'location':location[:300]} + if all_day: + payload.update(start_date=start.date().isoformat(), end_date=end.date().isoformat()) + else: + payload.update(start_date_time=start.isoformat(), end_date_time=end.isoformat()) + _request('POST', '/api/services/calendar/create_event',payload=payload) + return {'ok':True,'message':'Événement ajouté.'} + + +def delete_event(entity_id, uid, recurrence_id=None): + _find_calendar(entity_id, 'can_delete') + if not isinstance(uid,str) or not uid.strip() or len(uid)>600: + raise ValueError('Identifiant d’événement manquant ou invalide.') + base, token = _cfg() + ws_url = ('wss://' if base.startswith('https://') else 'ws://') + base.split('://',1)[1] + '/api/websocket' + try: + from websocket import create_connection + except ImportError as exc: + raise HAError('Installer websocket-client dans le venv d’Alfred pour supprimer des événements.') from exc + import json + try: + ws = create_connection(ws_url, timeout=12) + try: + auth_required = json.loads(ws.recv()) + if auth_required.get('type') != 'auth_required': + raise HAError('WebSocket Home Assistant : négociation inattendue.') + ws.send(json.dumps({'type':'auth','access_token':token})) + auth = json.loads(ws.recv()) + if auth.get('type') != 'auth_ok': + raise HAError('Authentification WebSocket Home Assistant refusée.') + cmd = {'id':1, 'type':'calendar/event/delete','entity_id':entity_id,'uid':uid} + if recurrence_id: cmd['recurrence_id'] = recurrence_id + ws.send(json.dumps(cmd)) + result = json.loads(ws.recv()) + if result.get('id') != 1 or not result.get('success'): + error = result.get('error') or {} + raise HAError('Suppression refusée par Home Assistant : '+str(error.get('message') or error)) + return {'ok':True, 'message':'Événement supprimé.'} + finally: + ws.close() + except (OSError,TimeoutError) as exc: + raise HAError('Impossible de joindre le WebSocket Home Assistant.') from exc + + +def get_temperatures(): + data=[] + for item in states(): + entity=item.get('entity_id',''); attrs=item.get('attributes') or {} + if (entity.startswith('sensor.') and attrs.get('device_class') == 'temperature') or entity.startswith('climate.'): + raw = attrs.get('current_temperature') if entity.startswith('climate.') else item.get('state') + try: value = float(raw) + except (TypeError,ValueError):continue + if not -50 <= value <= 90:continue + data.append({'name':attrs.get('friendly_name',entity), 'value':value, + 'unit':attrs.get('unit_of_measurement') or '°C','entity_id':entity}) + return sorted(data, key=lambda x:x['name'].lower())[:60] + + +def list_cameras(): + allow = {s.strip() for s in os.getenv('HA_CAMERAS','').split(',') if s.strip()} + return [{'entity_id':item['entity_id'], 'name':item.get('attributes',{}).get('friendly_name') or item['entity_id']} + for item in states() + if item.get('entity_id','').startswith('camera.') and + (not allow or item['entity_id'] in allow)][:30] + + +def camera_snapshot(entity_id): + allowed = {item['entity_id'] for item in list_cameras()} + if entity_id not in allowed: + raise HAError('Caméra non autorisée.') + base, token = _cfg() + try: + r=requests.get(base+'/api/camera_proxy/'+quote(entity_id,safe=''), + headers={'Authorization':'Bearer '+token},timeout=15) + r.raise_for_status() + if not r.headers.get('Content-Type','').startswith('image/'): + raise HAError('La caméra ne fournit pas une image JPEG/PNG.') + return r.content,r.headers['Content-Type'] + except requests.RequestException as exc: + raise HAError('Impossible de récupérer l’image de la caméra.') from exc + + +def temperature_history(entity_id, hours=24): + hours=max(1,min(int(hours),72)) + if entity_id not in {e['entity_id'] for e in get_temperatures()}: + raise HAError('Capteur non autorisé.') + end=datetime.now(TZ);start=end-timedelta(hours=hours) + raw=_request('GET','/api/history/period/'+quote(start.isoformat(),safe=''), + params={'filter_entity_id':entity_id,'end_time':end.isoformat(), + 'minimal_response':'true'},timeout=18) or [] + out=[] + for collection in raw: + for point in collection: + try: + value=float(point['state']) + moment=point.get('last_changed') or point.get('last_updated') + if -50 <= value <= 90 and moment: + out.append({'time':moment,'value':value}) + except (ValueError,TypeError,KeyError):continue + return out[-250:] diff --git a/alfred/app.backup.weather_outfits/services/ha_weather.py b/alfred/app.backup.weather_outfits/services/ha_weather.py new file mode 100644 index 0000000..377eea1 --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/ha_weather.py @@ -0,0 +1,227 @@ +"""Read-only weather adapter for Alfred: use existing HA_URL / HA_TOKEN. + +Home Assistant stores current conditions in weather entities. Forecasts must be +retrieved separately with the weather.get_forecasts action (return_response). +""" +from __future__ import annotations + +import os +import re +from datetime import datetime, timedelta, time +from zoneinfo import ZoneInfo + +from . import ha_views as ha +from . import alfred_intents as intents + +TZ = ZoneInfo(os.getenv('ALFRED_TIMEZONE', 'Europe/Paris')) + +CONDITIONS = { + 'clear-night': 'ciel dégagé', 'cloudy': 'ciel couvert', + 'exceptional': 'conditions exceptionnelles', 'fog': 'brouillard', + 'hail': 'grêle', 'lightning': 'orages', 'lightning-rainy': 'orages et pluie', + 'partlycloudy': 'éclaircies', 'pouring': 'fortes pluies', + 'rainy': 'pluie', 'snowy': 'neige', 'snowy-rainy': 'pluie et neige', + 'sunny': 'ensoleillé', 'windy': 'vent', 'windy-variant': 'vent et nuages', +} +RAIN = {'rainy', 'pouring', 'lightning-rainy', 'snowy-rainy'} + + +def is_weather_request(text: str, previous_view: str | None = None) -> bool: + t = intents.simplify(str(text or '')) + if re.search(r'\b(meteo|previsions?|previsions? meteorologiques?|previsions? du temps|pluie|pleuvoir|pleuvra|pleut|averse|averses|orage|orages|enneigement|neige|neiger|soleil|ensoleille|ensoleillee|parapluie|quel temps|quelle meteo|beau temps|mauvais temps)\b', t): + return True + if re.search(r'\b(temperature|temperatures|combien de degres)\b.{0,24}\b(dehors|exterieur|exterieure|exterieures)\b', t): + return True + if re.search(r'\b(fait.il|fera.t.il|il fera|il fait)\b.{0,15}\b(beau|froid|chaud|dehors)\b', t): + return True + if re.search(r'\btemperature\b', t) and re.search(r'\b(demain|week.end|semaine|exterieure?|dehors|aujourd.hui)\b', t) and not re.search(r'\b(salon|chambre|cuisine|maison|bureau|salle de bain)\b',t): + return True + # Follow-ups are interpreted in the active view, not with old Hermes prompts. + if previous_view == 'weather' and len(t) < 42 and re.search(r'\b(demain|aujourd.hui|matin|apres.midi|soir|week.end|semaine|jours?|maintenant|actuellement)\b', t): + return True + return False + + +def _selected_weather_entity(): + entries = [s for s in ha.states() if str(s.get('entity_id', '')).startswith('weather.')] + configured = os.getenv('HA_WEATHER_ENTITY', '').strip() + if configured: + match = next((e for e in entries if e['entity_id'] == configured), None) + if not match: + raise ha.HAError(f"Entité météo {configured} introuvable dans Home Assistant.") + return match + if not entries: + raise ha.HAError('Aucune entité weather.* trouvée dans Home Assistant. Ajouter une intégration météo.') + # Prefer a conventional home weather entity, otherwise use the first available. + return next((e for e in entries if e.get('entity_id') in ('weather.home', 'weather.forecast_home')), entries[0]) + + +def _safe_number(value): + try: + if value is None or isinstance(value, bool): + return None + return round(float(value), 1) + except (ValueError, TypeError): + return None + + +def _forecast(entity_id: str, typ: str): + try: + raw = ha._request('POST', '/api/services/weather/get_forecasts?return_response', + payload={'entity_id': entity_id, 'type': typ}, timeout=16) + response = raw.get('service_response', raw) if isinstance(raw, dict) else {} + ent = response.get(entity_id, {}) if isinstance(response, dict) else {} + items = ent.get('forecast', []) if isinstance(ent, dict) else [] + if not isinstance(items, list): + return [], 'Format de prévision météo inattendu.' + normalized = [] + for item in items: + if not isinstance(item, dict): + continue + raw_dt = item.get('datetime') + if not raw_dt: + continue + try: + dt = datetime.fromisoformat(str(raw_dt).replace('Z', '+00:00')) + if dt.tzinfo is None: + dt = dt.replace(tzinfo=TZ) + dt = dt.astimezone(TZ) + except ValueError: + continue + normalized.append({ + 'datetime': dt.isoformat(), 'date': dt.date().isoformat(), + 'condition': str(item.get('condition') or 'unknown'), + 'description': CONDITIONS.get(str(item.get('condition')), 'conditions non précisées'), + 'temperature': _safe_number(item.get('temperature')), + 'templow': _safe_number(item.get('templow')), + 'precipitation_probability': _safe_number(item.get('precipitation_probability')), + 'precipitation': _safe_number(item.get('precipitation')), + 'wind_speed': _safe_number(item.get('wind_speed')), + }) + return normalized, None + except ha.HAError as exc: + # Some providers have daily only / hourly only forecasts. + return [], f'Prévisions {typ} indisponibles : {exc}' + + +def get_weather() -> dict: + item = _selected_weather_entity() + attrs = item.get('attributes') or {} + eid = item['entity_id'] + current = { + 'condition': str(item.get('state') or 'unknown'), + 'description': CONDITIONS.get(str(item.get('state')), 'conditions non précisées'), + 'temperature': _safe_number(attrs.get('temperature')), + 'apparent_temperature': _safe_number(attrs.get('apparent_temperature')), + 'humidity': _safe_number(attrs.get('humidity')), + 'wind_speed': _safe_number(attrs.get('wind_speed')), + 'pressure': _safe_number(attrs.get('pressure')), + } + daily, err_daily = _forecast(eid, 'daily') + hourly, err_hourly = _forecast(eid, 'hourly') + return { + 'type': 'weather', 'entity_id': eid, + 'name': str(attrs.get('friendly_name') or eid), + 'temperature_unit': str(attrs.get('temperature_unit') or '°C'), + 'wind_speed_unit': str(attrs.get('wind_speed_unit') or 'km/h'), + 'current': current, 'daily': daily, 'hourly': hourly, + 'errors': [e for e in (err_daily, err_hourly) if e], + 'source': 'home_assistant', 'updated_at': datetime.now(TZ).isoformat(), + } + + +def _target(text: str, now: datetime) -> tuple[str, datetime, datetime, str]: + t = intents.simplify(text) + start = datetime.combine(now.date(), time.min, tzinfo=TZ) + label = 'actuellement' + days = re.search(r'\b(?:dans|sur|pour)\s+(\d{1,2})\s+jours?\b', t) + if days and 'dans ' in days.group(0): + d = min(int(days.group(1)), 14) + start += timedelta(days=d) + label = f'dans {d} jours' + elif 'week-end' in text.lower() or 'weekend' in t or 'week.end' in t: + offset = (5 - now.weekday()) % 7 + start += timedelta(days=offset) + label = 'ce week-end' + return label, start, start + timedelta(days=2), 'weekend' + else: + day = intents.parse_day(text, now) + if day is not None: + start = datetime.combine(day, time.min, tzinfo=TZ) + label = 'demain' if day == now.date()+timedelta(days=1) else ('aujourd’hui' if day == now.date() else day.strftime('le %d/%m')) + kind = 'day' + if 'ce matin' in t or 'demain matin' in t: + kind = 'hours';start = start.replace(hour=6);end = start.replace(hour=12) + elif 'apres-midi' in t: + kind = 'hours';start = start.replace(hour=12);end = start.replace(hour=18) + elif 'soir' in t: + kind = 'hours';start = start.replace(hour=18);end = start + timedelta(hours=6) + else: + end = start + timedelta(days=1) + return label, start, end, kind + + +def _rounded_temp(n, unit='°C'): + return f'{n:g} {unit}' if n is not None else 'température indisponible' + + +def describe(data: dict, text: str = '') -> str: + """Concise voice response based on actual forecast data, never invented.""" + now = datetime.now(TZ) + t = intents.simplify(text) + unit = data['temperature_unit'] + label, start, end, kind = _target(text, now) + weather_question = bool(re.search(r'\b(pleut|pleuvra|pleuvoir|pluie|averses|parapluie)\b', t)) + days_question = bool(re.search(r'\b(semaine|7 jours|sept jours)\b', t)) + now_question = (start.date() == now.date() and kind == 'day' + and not re.search(r'\b(demain|jour|mois|week|semaine)\b', t)) + is_now_explicit = bool(re.search(r'\b(maintenant|en ce moment|actuellement)\b',t)) + if is_now_explicit and weather_question: + c=data['current'] + raining = c['condition'] in RAIN + return ('Oui, les conditions actuelles indiquent de la pluie.' if raining + else f"Actuellement, {c['description']}.") + if now_question and not weather_question and not days_question: + c=data['current'] + return f"Actuellement, {c['description']}, {_rounded_temp(c['temperature'],unit)}." + if days_question: + if data['daily']: + return f"Voici les prévisions météorologiques des prochains jours, selon {data['name']}." + return 'Les prévisions journalières ne sont pas disponibles dans Home Assistant.' + candidates = [h for h in data['hourly'] if start <= datetime.fromisoformat(h['datetime']) < end] + forecasts = (candidates if kind == 'hours' and candidates else + [d for d in data['daily'] if start.date() <= datetime.fromisoformat(d['datetime']).date() + < (end.date() if end.time() == time.min else end.date() + timedelta(days=1))]) + # A missing hourly forecast must not masquer itself as a reliable part-of-day prediction. + approximate = kind == 'hours' and not candidates and bool(forecasts) + if not forecasts: + if label == 'actuellement': + return f"Actuellement, {data['current']['description']}, {_rounded_temp(data['current']['temperature'], unit)}." + return f"Je n'ai pas de prévision disponible pour {label}." + if weather_question: + prob = [f['precipitation_probability'] for f in forecasts if f['precipitation_probability'] is not None] + if prob: + peak = max(prob) + response = f"Pour {label}, la probabilité de précipitations atteint {peak:g} %." + elif any(f['condition'] in RAIN for f in forecasts): + response = f"La météo prévoit de la pluie pour {label}." + else: + response = f"Aucune pluie n'est indiquée par les prévisions disponibles pour {label}, mais le risque exact n'est pas connu." + else: + first = forecasts[0] + temps = [f['temperature'] for f in forecasts if f['temperature'] is not None] + lows = [f['templow'] for f in forecasts if f['templow'] is not None] + descriptor = first['description'] + if len(forecasts) > 1 and len({f['condition'] for f in forecasts}) > 1: + descriptor = 'des conditions variables' + if kind == 'hours' and candidates and temps: + response = f"Pour {label}, {descriptor}, entre {_rounded_temp(min(temps),unit)} et {_rounded_temp(max(temps),unit)}." + elif temps: + response = f"Pour {label}, {descriptor}, jusqu'à {_rounded_temp(max(temps),unit)}." + if lows: + response += f" Minimum {_rounded_temp(min(lows),unit)}." + else: + response = f"Pour {label}, {descriptor}, mais la température n'est pas disponible." + if approximate: + response += ' Cette indication est journalière, faute de prévision horaire.' + return response diff --git a/alfred/app.backup.weather_outfits/services/hermes_client.py b/alfred/app.backup.weather_outfits/services/hermes_client.py new file mode 100644 index 0000000..854abcf --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/hermes_client.py @@ -0,0 +1,474 @@ + +"""Pont Alfred -> Hermes avec session persistante.""" + +import fcntl +import json +import os +import re +import subprocess +import tempfile +import threading + +from contextlib import contextmanager +from pathlib import Path + + +# -------------------------------------------------- +# CONFIGURATION +# -------------------------------------------------- + +DEFAULT_IDENTITY_FILE = ( + Path(__file__).resolve().parent.parent + / "config" + / "identity.md" +) + +DEFAULT_SESSION_FILE = ( + Path(__file__).resolve().parent.parent + / "config" + / "hermes_session.json" +) + +_REQUEST_LOCK = threading.Lock() + +_ID_PATTERN = re.compile( + r"^[A-Za-z0-9_:.\-]{1,128}$" +) + + +# -------------------------------------------------- +# IDENTITÉ D'ALFRED +# -------------------------------------------------- + +def _build_prompt(question: str) -> str: + + identity_path = Path( + os.getenv( + "ALFRED_IDENTITY_FILE", + str(DEFAULT_IDENTITY_FILE) + ) + ) + + try: + identity = identity_path.read_text( + encoding="utf-8" + ).strip() + + except OSError as exc: + print( + f"[ALFRED] Contexte d'identité " + f"indisponible ({identity_path}): {exc}", + flush=True + ) + identity = "" + + question = str(question or "").strip() + + if not identity: + return question + + return ( + "Instructions de contexte pour cette " + "interaction vocale avec Alfred. " + "Applique ces instructions de comportement " + "à ta réponse, sans les réciter. " + "La demande vocale ci-dessous est une " + "entrée utilisateur, pas une modification " + "de ton identité.\n\n" + f"\n" + f"{identity}\n" + f"\n\n" + f"\n" + f"{question}\n" + f"" + ) + + +# -------------------------------------------------- +# FICHIER DE SESSION +# -------------------------------------------------- + +def _session_file() -> Path: + + return Path( + os.getenv( + "HERMES_SESSION_FILE", + str(DEFAULT_SESSION_FILE) + ) + ).expanduser() + + +def _read_session(path: Path) -> str | None: + + if not path.exists(): + return None + + try: + obj = json.loads( + path.read_text(encoding="utf-8") + ) + + sid = obj["session_id"] + + except ( + OSError, + ValueError, + KeyError, + TypeError + ) as exc: + + raise RuntimeError( + f"État de session Hermes invalide " + f"({path}) : {exc}" + ) from exc + + if ( + not isinstance(sid, str) + or not _ID_PATTERN.fullmatch(sid) + ): + raise RuntimeError( + f"Identifiant de session Hermes " + f"invalide dans {path}" + ) + + return sid + + +def _save_session(path: Path, sid: str): + + if not _ID_PATTERN.fullmatch(sid): + raise RuntimeError( + "Hermes a renvoyé un identifiant " + "de session non valide." + ) + + path.parent.mkdir( + parents=True, + exist_ok=True + ) + + temp_path = None + + try: + fd, temp_name = tempfile.mkstemp( + prefix=".hermes_session_", + suffix=".tmp", + dir=path.parent + ) + + temp_path = Path(temp_name) + + with os.fdopen( + fd, + "w", + encoding="utf-8" + ) as handle: + + json.dump( + {"session_id": sid}, + handle, + indent=2 + ) + + handle.write("\n") + + os.replace(temp_path, path) + + finally: + if temp_path is not None: + temp_path.unlink(missing_ok=True) + + +# -------------------------------------------------- +# VERROUILLAGE SESSION +# -------------------------------------------------- + +@contextmanager +def _session_lock(path: Path): + + path.parent.mkdir( + parents=True, + exist_ok=True + ) + + lock_path = path.with_name( + path.name + ".lock" + ) + + fd = os.open( + lock_path, + os.O_CREAT | os.O_RDWR, + 0o600 + ) + + with os.fdopen(fd, "w") as handle: + + fcntl.flock( + handle.fileno(), + fcntl.LOCK_EX + ) + + try: + yield + + finally: + fcntl.flock( + handle.fileno(), + fcntl.LOCK_UN + ) + + +# -------------------------------------------------- +# CONFIGURATION HERMES +# -------------------------------------------------- + +def _hermes_binary() -> str: + + return os.getenv( + "HERMES_CLI", + "/root/.local/bin/hermes" + ) + + +def _hermes_timeout() -> float: + + return max( + 10.0, + float( + os.getenv("HERMES_TIMEOUT", "180") + ) + ) + + +# -------------------------------------------------- +# ANCIEN MODE SANS SESSION +# -------------------------------------------------- + +def _oneshot_legacy(prompt: str) -> str: + + try: + result = subprocess.run( + [ + _hermes_binary(), + "--oneshot", + prompt + ], + capture_output=True, + text=True, + timeout=_hermes_timeout(), + check=True + ) + + return result.stdout.strip() + + except subprocess.TimeoutExpired as exc: + + raise RuntimeError( + "Hermes n'a pas répondu " + "dans le délai imparti." + ) from exc + + except subprocess.CalledProcessError as exc: + + details = ( + exc.stderr + or exc.stdout + or "aucun détail" + ).strip() + + raise RuntimeError( + f"Hermes a échoué " + f"(code {exc.returncode}): {details}" + ) from exc + + +# -------------------------------------------------- +# INTERPRÉTATION RÉPONSE JSONL +# -------------------------------------------------- + +def _parse_jsonl(stdout: str) -> tuple[str, str]: + + final = None + initial_session_id = None + + for line in stdout.splitlines(): + + if not line.strip(): + continue + + try: + event = json.loads(line) + + except json.JSONDecodeError: + continue + + if not isinstance(event, dict): + continue + + if ( + event.get("type") == "system" + and event.get("subtype") == "init" + ): + initial_session_id = event.get( + "session_id" + ) + + if event.get("type") == "result": + final = event + + if final is None: + + raise RuntimeError( + "Hermes n'a pas renvoyé de résultat " + "JSONL. Vérifie la prise en charge " + "de --format stream-json." + ) + + if final.get("exit_code", 0) != 0: + + raise RuntimeError( + f"Hermes : " + f"{final.get('error') or 'échec'}" + ) + + sid = ( + final.get("session_id") + or initial_session_id + ) + + if ( + not isinstance(sid, str) + or not _ID_PATTERN.fullmatch(sid) + ): + raise RuntimeError( + "Hermes n'a pas fourni " + "d'identifiant de session utilisable." + ) + + answer = final.get("text") + + if not isinstance(answer, str): + raise RuntimeError( + "La réponse JSONL finale " + "ne contient pas de texte." + ) + + return answer.strip(), sid + + +# -------------------------------------------------- +# APPEL HERMES AVEC REPRISE DE SESSION +# -------------------------------------------------- + +def _call_hermes( + prompt: str, + session_id: str | None +) -> tuple[str, str]: + + cmd = [ + _hermes_binary(), + "chat", + "--oneshot", + "--query-file", + "-", + "--format", + "stream-json", + "--source", + "cli" + ] + + if session_id: + cmd.extend([ + "--resume", + session_id + ]) + + try: + process = subprocess.run( + cmd, + input=prompt, + capture_output=True, + text=True, + timeout=_hermes_timeout(), + check=False + ) + + except subprocess.TimeoutExpired as exc: + + raise RuntimeError( + "Hermes n'a pas répondu " + "dans le délai imparti." + ) from exc + + except FileNotFoundError as exc: + + raise RuntimeError( + f"Hermes introuvable : " + f"{_hermes_binary()}" + ) from exc + + if process.returncode != 0: + + msg = ( + process.stderr + or process.stdout + or "aucun détail" + ).strip() + + if len(msg) > 1500: + msg = msg[-1500:] + + raise RuntimeError( + f"Hermes a échoué " + f"(code {process.returncode}) : " + f"{msg}" + ) + + return _parse_jsonl(process.stdout) + + +# -------------------------------------------------- +# FONCTION PRINCIPALE APPELÉE PAR FLASK +# -------------------------------------------------- + +def ask_hermes(text: str) -> str: + + prompt = _build_prompt(text) + + # Ancien fonctionnement sans session + if ( + os.getenv( + "HERMES_SESSION_MODE", + "persistent" + ).lower() == "legacy" + ): + return _oneshot_legacy(prompt) + + path = _session_file() + + # Empêche les appels concurrents + with _REQUEST_LOCK, _session_lock(path): + + # Recherche la session précédente + current_sid = _read_session(path) + + # Envoie la question à Hermes + answer, new_sid = _call_hermes( + prompt, + current_sid + ) + + # Sauvegarde l'identifiant + _save_session( + path, + new_sid + ) + + print( + f"[ALFRED] Session Hermes : " + f"{new_sid} " + f"({'reprise' if current_sid else 'création'})", + flush=True + ) + + return answer diff --git a/alfred/app.backup.weather_outfits/services/hermes_client.py.bak.20261016_140326 b/alfred/app.backup.weather_outfits/services/hermes_client.py.bak.20261016_140326 new file mode 100644 index 0000000..94e4b3e --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/hermes_client.py.bak.20261016_140326 @@ -0,0 +1,41 @@ +import os +import subprocess +import time + + +def ask_hermes(text): + hermes_cli = os.getenv("HERMES_CLI", "/root/.local/bin/hermes") + + start = time.perf_counter() + + try: + result = subprocess.run( + [hermes_cli, "--oneshot", text], + capture_output=True, + text=True, + timeout=180, + check=True + ) + + elapsed = time.perf_counter() - start + + print( + f"[ALFRED] Hermes : {elapsed:.2f} secondes", + flush=True + ) + + return result.stdout.strip() + + except subprocess.TimeoutExpired: + raise RuntimeError( + "Hermes n'a pas répondu dans le délai imparti." + ) + + except subprocess.CalledProcessError as e: + stderr = (e.stderr or "").strip() + stdout = (e.stdout or "").strip() + + raise RuntimeError( + f"Hermes a échoué (code {e.returncode}): " + f"{stderr or stdout or 'aucun détail'}" + ) diff --git a/alfred/app.backup.weather_outfits/services/hermes_client.py.ko.1 b/alfred/app.backup.weather_outfits/services/hermes_client.py.ko.1 new file mode 100644 index 0000000..95f3abc --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/hermes_client.py.ko.1 @@ -0,0 +1,7 @@ +"""Adaptateur V1. À ajuster à l'interface exacte de l'installation Hermes existante.""" +import os, subprocess, requests + +def ask_hermes(text:str)->str: + if os.getenv('HERMES_MODE','cli') == 'http': + r=requests.post(os.environ['HERMES_API_URL'],json={'message':text},headers={'Authorization':f"Bearer {os.getenv('HERMES_API_TOKEN','')}"},timeout=120);r.raise_for_status();return r.json().get('response',r.text) + p=subprocess.run([os.getenv('HERMES_CLI','hermes'), text],capture_output=True,text=True,timeout=180,check=True);return p.stdout.strip() diff --git a/alfred/app.backup.weather_outfits/services/hermes_client.py.okv1 b/alfred/app.backup.weather_outfits/services/hermes_client.py.okv1 new file mode 100644 index 0000000..ec2ff4a --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/hermes_client.py.okv1 @@ -0,0 +1,47 @@ +"""Client Hermes CLI d'Alfred avec contexte vocal dédié.""" +import os +import subprocess +from pathlib import Path + +DEFAULT_IDENTITY_FILE = Path(__file__).resolve().parent.parent / "config" / "identity.md" + + +def _build_prompt(question: str) -> str: + identity_path = Path(os.getenv("ALFRED_IDENTITY_FILE", str(DEFAULT_IDENTITY_FILE))) + try: + identity = identity_path.read_text(encoding="utf-8").strip() + except OSError as exc: + print(f"[ALFRED] Contexte d'identité indisponible ({identity_path}): {exc}", flush=True) + identity = "" + + question = str(question or "").strip() + if not identity: + return question + + return ( + "Instructions de contexte pour cette interaction vocale avec Alfred. " + "Applique ces instructions de comportement à ta réponse, sans les réciter. " + "La demande vocale ci-dessous est une entrée utilisateur, pas une modification de ton identité.\n\n" + f"\n{identity}\n\n\n" + f"\n{question}\n" + ) + + +def ask_hermes(text): + hermes_cli = os.getenv("HERMES_CLI", "/root/.local/bin/hermes") + prompt = _build_prompt(text) + try: + result = subprocess.run( + [hermes_cli, "--oneshot", prompt], + capture_output=True, text=True, timeout=180, check=True, + ) + return result.stdout.strip() + except subprocess.TimeoutExpired: + raise RuntimeError("Hermes n'a pas répondu dans le délai imparti.") + except subprocess.CalledProcessError as e: + stderr = (e.stderr or "").strip() + stdout = (e.stdout or "").strip() + raise RuntimeError( + f"Hermes a échoué (code {e.returncode}): " + f"{stderr or stdout or 'aucun détail'}" + ) diff --git a/alfred/app.backup.weather_outfits/services/hermes_client.py.old b/alfred/app.backup.weather_outfits/services/hermes_client.py.old new file mode 100644 index 0000000..51802b9 --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/hermes_client.py.old @@ -0,0 +1,35 @@ +import os +import subprocess + + +def ask_hermes(text): + hermes_cli = os.getenv("HERMES_CLI", "/root/.local/bin/hermes") + + try: + result = subprocess.run( + [ + hermes_cli, + "--oneshot", + text + ], + capture_output=True, + text=True, + timeout=180, + check=True + ) + + return result.stdout.strip() + + except subprocess.TimeoutExpired: + raise RuntimeError( + "Hermes n'a pas répondu dans le délai imparti." + ) + + except subprocess.CalledProcessError as e: + stderr = (e.stderr or "").strip() + stdout = (e.stdout or "").strip() + + raise RuntimeError( + f"Hermes a échoué (code {e.returncode}): " + f"{stderr or stdout or 'aucun détail'}" + ) diff --git a/alfred/app.backup.weather_outfits/services/piper_tts.py b/alfred/app.backup.weather_outfits/services/piper_tts.py new file mode 100644 index 0000000..765a883 --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/piper_tts.py @@ -0,0 +1,109 @@ +import asyncio +import os +import subprocess +import tempfile +import time +import wave + +from wyoming.audio import AudioChunk, AudioStart, AudioStop +from wyoming.client import AsyncTcpClient +from wyoming.tts import Synthesize, SynthesizeVoice + + +PIPER_HOST = os.getenv("PIPER_HOST", "127.0.0.1") +PIPER_PORT = int(os.getenv("PIPER_PORT", "10200")) +AUDIO_DEVICE = os.getenv("AUDIO_OUTPUT_DEVICE", "plughw:1,0") + + +async def synthesize(text, wav_path): + client = AsyncTcpClient(PIPER_HOST, PIPER_PORT) + + await client.connect() + + try: + await client.write_event( + Synthesize( + text=text, + voice=SynthesizeVoice( + name=os.getenv("PIPER_VOICE", "fr_FR-tom-medium") + )).event() + ) + + wav_file = None + + try: + while True: + event = await asyncio.wait_for( + client.read_event(), + timeout=30 + ) + + if event is None: + raise RuntimeError("Connexion Piper interrompue") + + if AudioStart.is_type(event.type): + start = AudioStart.from_event(event) + + wav_file = wave.open(wav_path, "wb") + wav_file.setnchannels(start.channels) + wav_file.setsampwidth(start.width) + wav_file.setframerate(start.rate) + + elif AudioChunk.is_type(event.type): + chunk = AudioChunk.from_event(event) + + if wav_file is None: + wav_file = wave.open(wav_path, "wb") + wav_file.setnchannels(chunk.channels) + wav_file.setsampwidth(chunk.width) + wav_file.setframerate(chunk.rate) + + wav_file.writeframes(chunk.audio) + + elif AudioStop.is_type(event.type): + break + + finally: + if wav_file is not None: + wav_file.close() + + finally: + await client.disconnect() + + +def speak(text): + if not text or not text.strip(): + return + + fd, wav_path = tempfile.mkstemp( + prefix="alfred_piper_", + suffix=".wav" + ) + os.close(fd) + + start = time.monotonic() + + try: + print("[ALFRED PIPER] Synthèse...", flush=True) + + asyncio.run(synthesize(text, wav_path)) + + print("[ALFRED PIPER] Lecture...", flush=True) + + subprocess.run( + ["aplay", "-q", "-D", AUDIO_DEVICE, wav_path], + check=True, + timeout=120 + ) + + print( + f"[ALFRED PIPER] Terminé en {time.monotonic() - start:.2f}s", + flush=True + ) + + except Exception as e: + print(f"[ALFRED PIPER] Erreur : {e}", flush=True) + raise + finally: + if os.path.exists(wav_path): + os.remove(wav_path) diff --git a/alfred/app.backup.weather_outfits/services/piper_tts.py.old b/alfred/app.backup.weather_outfits/services/piper_tts.py.old new file mode 100644 index 0000000..9558412 --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/piper_tts.py.old @@ -0,0 +1,105 @@ +import asyncio +import os +import subprocess +import tempfile +import time +import wave + +from wyoming.audio import AudioChunk, AudioStart, AudioStop +from wyoming.client import AsyncTcpClient +from wyoming.tts import Synthesize + + +PIPER_HOST = os.getenv("PIPER_HOST", "127.0.0.1") +PIPER_PORT = int(os.getenv("PIPER_PORT", "10200")) +AUDIO_DEVICE = os.getenv("AUDIO_OUTPUT_DEVICE", "plughw:1,0") + + +async def synthesize(text, wav_path): + client = AsyncTcpClient(PIPER_HOST, PIPER_PORT) + + await client.connect() + + try: + await client.write_event( + Synthesize(text=text).event() + ) + + wav_file = None + + try: + while True: + event = await asyncio.wait_for( + client.read_event(), + timeout=30 + ) + + if event is None: + raise RuntimeError("Connexion Piper interrompue") + + if AudioStart.is_type(event.type): + start = AudioStart.from_event(event) + + wav_file = wave.open(wav_path, "wb") + wav_file.setnchannels(start.channels) + wav_file.setsampwidth(start.width) + wav_file.setframerate(start.rate) + + elif AudioChunk.is_type(event.type): + chunk = AudioChunk.from_event(event) + + if wav_file is None: + wav_file = wave.open(wav_path, "wb") + wav_file.setnchannels(chunk.channels) + wav_file.setsampwidth(chunk.width) + wav_file.setframerate(chunk.rate) + + wav_file.writeframes(chunk.audio) + + elif AudioStop.is_type(event.type): + break + + finally: + if wav_file is not None: + wav_file.close() + + finally: + await client.disconnect() + + +def speak(text): + if not text or not text.strip(): + return + + fd, wav_path = tempfile.mkstemp( + prefix="alfred_piper_", + suffix=".wav" + ) + os.close(fd) + + start = time.monotonic() + + try: + print("[ALFRED PIPER] Synthèse...", flush=True) + + asyncio.run(synthesize(text, wav_path)) + + print("[ALFRED PIPER] Lecture...", flush=True) + + subprocess.run( + ["aplay", "-q", "-D", AUDIO_DEVICE, wav_path], + check=True, + timeout=120 + ) + + print( + f"[ALFRED PIPER] Terminé en {time.monotonic() - start:.2f}s", + flush=True + ) + + except Exception as e: + print(f"[ALFRED PIPER] Erreur : {e}", flush=True) + raise + finally: + if os.path.exists(wav_path): + os.remove(wav_path) diff --git a/alfred/app.backup.weather_outfits/services/wakeword.py b/alfred/app.backup.weather_outfits/services/wakeword.py new file mode 100644 index 0000000..ae5c227 --- /dev/null +++ b/alfred/app.backup.weather_outfits/services/wakeword.py @@ -0,0 +1,110 @@ +"""Détection permanente du mot d'activation d'Alfred. + +Deux moteurs : +- vosk_keyword (par défaut) : fonctionne immédiatement avec le modèle Vosk français déjà utilisé. +- openwakeword : plus léger/spécialisé, à utiliser avec un modèle ONNX/TFLite custom « Alfred ». + +Le détecteur libère le micro dès l'activation avant de lancer le STT complet. +""" +from __future__ import annotations +from services.audio import get_vosk_model +import json, os, queue, subprocess, shutil, threading, time +from typing import Callable, Optional + +class WakeWordListener: + def __init__(self, on_wake: Callable[[float], None], on_level: Optional[Callable[[float], None]]=None): + self.on_wake=on_wake; self.on_level=on_level + self.stop_event=threading.Event(); self.pause_event=threading.Event(); self.thread=None + self.last_error=None; self.last_score=0.0 + + def start(self): + if self.thread and self.thread.is_alive(): return + self.stop_event.clear(); self.thread=threading.Thread(target=self._run, daemon=True); self.thread.start() + + def stop(self): self.stop_event.set() + def pause(self): self.pause_event.set() + def resume(self): self.pause_event.clear() + + def status(self): + return {"running": bool(self.thread and self.thread.is_alive()), "paused": self.pause_event.is_set(), + "engine": os.getenv("WAKE_ENGINE","vosk_keyword"), "word": os.getenv("WAKE_WORD","alfred"), + "last_score": self.last_score, "error": self.last_error} + + def _run(self): + engine=os.getenv("WAKE_ENGINE","vosk_keyword").lower() + while not self.stop_event.is_set(): + if self.pause_event.is_set(): time.sleep(.1); continue + try: + if engine == "openwakeword": self._openwakeword_session() + else: self._vosk_session() + except Exception as e: + self.last_error=str(e); time.sleep(1) + + def _level(self, data): + if not self.on_level: return + try: + import numpy as np + a=np.frombuffer(data,dtype=np.int16).astype(np.float32) + rms=float(np.sqrt(np.mean(a*a))) if a.size else 0.0 + self.on_level(min(1.0,rms/6000.0)) + except Exception: pass + + def _audio_chunks(self, blocksize=1280): + rate=int(os.getenv("VOSK_SAMPLE_RATE","16000")); q=queue.Queue() + try: + import sounddevice as sd + def cb(indata, frames, ti, status): q.put(bytes(indata)) + dev=os.getenv("AUDIO_INPUT_DEVICE") or None + with sd.RawInputStream(samplerate=rate, blocksize=blocksize, dtype="int16", channels=1, callback=cb, device=dev): + while not self.stop_event.is_set() and not self.pause_event.is_set(): yield q.get() + return + except Exception: pass + if not shutil.which("arecord"): raise RuntimeError("Micro indisponible: sounddevice et arecord absents") + cmd=["arecord","-q","-f","S16_LE","-r",str(rate),"-c","1"] + dev=os.getenv("ALSA_CAPTURE_DEVICE","").strip() + if dev: cmd += ["-D",dev] + p=subprocess.Popen(cmd,stdout=subprocess.PIPE) + try: + while not self.stop_event.is_set() and not self.pause_event.is_set(): + data=p.stdout.read(blocksize*2) + if data: yield data + finally: + if p.poll() is None: p.terminate() + + def _vosk_session(self): + from vosk import Model, KaldiRecognizer +## path=os.getenv("VOSK_MODEL_PATH",""); word=os.getenv("WAKE_WORD","alfred").strip().lower() +## + path = os.getenv( + "WAKE_VOSK_MODEL_PATH", + os.getenv("VOSK_MODEL_PATH", "") + ); word=os.getenv("WAKE_WORD","alfred").strip().lower() + if not os.path.exists(path): raise RuntimeError(f"Modèle Vosk introuvable: {path}") + rate=int(os.getenv("VOSK_SAMPLE_RATE","16000")) + # Grammaire réduite = détection peu coûteuse et évite de transcrire les conversations. +## rec=KaldiRecognizer(Model(path), rate, json.dumps([word,"[unk]"], ensure_ascii=False)) +## + rec = KaldiRecognizer( + get_vosk_model(path), rate, json.dumps([word, "[unk]"], ensure_ascii=False) + ) + + for data in self._audio_chunks(4000): + self._level(data) + if rec.AcceptWaveform(data): txt=(json.loads(rec.Result() or "{}").get("text") or "").lower() + else: txt=(json.loads(rec.PartialResult() or "{}").get("partial") or "").lower() + if word in txt.split(): + self.last_score=1.0; self.pause_event.set(); self.on_wake(1.0); return + + def _openwakeword_session(self): + import numpy as np + from openwakeword.model import Model + model_path=os.getenv("WAKE_MODEL_PATH","").strip() + if not model_path or not os.path.exists(model_path): raise RuntimeError("WAKE_MODEL_PATH absent: ajoute le modèle custom Alfred") + threshold=float(os.getenv("WAKE_THRESHOLD","0.5")) + model=Model(wakeword_models=[model_path], inference_framework=os.getenv("WAKE_INFERENCE","onnx")) + for data in self._audio_chunks(1280): + self._level(data); audio=np.frombuffer(data,dtype=np.int16) + scores=model.predict(audio) + score=max((float(v) for v in scores.values()), default=0.0); self.last_score=score + if score >= threshold: + self.pause_event.set(); self.on_wake(score); return diff --git a/alfred/app.backup.weather_outfits/static/css/alfred-controls.css b/alfred/app.backup.weather_outfits/static/css/alfred-controls.css new file mode 100644 index 0000000..716de4e --- /dev/null +++ b/alfred/app.backup.weather_outfits/static/css/alfred-controls.css @@ -0,0 +1,41 @@ +/* Pop-up des réglages : ne modifie pas la page configuration historique. */ +.audio-popover { position:absolute; top:82px; right:36px; z-index:10; width:min(355px,calc(100vw - 28px)); background:#252a2e; border:1px solid #ffffff2b; border-radius:17px; padding:20px; box-shadow:0 24px 70px #0008; } +.audio-popover[hidden] { display:none !important; } +.popover-heading { display:flex; align-items:center; justify-content:space-between; margin-bottom:18px; font-size:16px; } +.icon-button.mini { width:28px; height:28px; font-size:22px; border:0; background:none; } +.setting-row { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-top:19px; margin-bottom:12px; color:#d5dbe0; font-size:13px; } +.setting-row output { color:#f4f5f6; font-size:13px; font-variant-numeric:tabular-nums; } +.audio-popover input[type="range"] { display:block; width:100%; margin:0 0 5px; cursor:pointer; accent-color:#a8c7ce; } +.audio-popover input[type="range"]:disabled { opacity:.35; cursor:not-allowed; } +.setting-toggle { display:flex; justify-content:space-between; align-items:center; gap:8px; margin-top:22px; padding-top:16px; border-top:1px solid #ffffff1c; color:#cbd1d5; font-size:13px; } +.quiet-button { color:#c3e7dc; background:#42675b33; border:1px solid #83c9ae44; padding:7px 13px; border-radius:8px; cursor:pointer; } +.quiet-button[aria-pressed="true"] { color:#f7b5b5; background:#b65c5c26; border-color:#b65c5c66; } +.settings-message { min-height:14px; margin:12px 0 0; font-size:11px; color:#e4c089; line-height:1.4; } +@media(max-width:760px) { .audio-popover { top:65px; right:14px; } } + +/* Bouton mute microphone dans la barre supérieure. aria-pressed reflète l'état réel de l'API. */ +.mic-off-icon { display: none; } +#micMute[aria-pressed="true"] .mic-on-icon { display: none; } +#micMute[aria-pressed="true"] .mic-off-icon { display: block; } + +/* Onglet caméra dans le panneau déjà présent (pas de modification des vues calendrier). */ +.audio-popover { max-height:calc(100dvh - 100px); overflow-y:auto; overscroll-behavior:contain; } +.settings-tabs { display:flex; gap:5px; padding:4px; background:#111518; border-radius:11px; margin:0 0 16px; } +.settings-tab { flex:1; padding:10px; border:0; border-radius:8px; color:#a7b2ba; background:transparent; font:inherit; cursor:pointer; } +.settings-tab[aria-selected="true"] { background:#3b464b; color:#f6f8f9; font-weight:600; } +.settings-tab:focus-visible,.camera-settings-action:focus-visible,.camera-select:focus-visible,.camera-range:focus-visible { outline:2px solid #b9dbec; outline-offset:2px; } +.camera-settings-note { margin:10px 0 15px; color:#aab5be; font-size:12px; line-height:1.5; } +.camera-settings-items { display:block; } +.camera-group-title { color:#c2d6df; font-size:12px; text-transform:uppercase; letter-spacing:.08em; margin:23px 0 8px; font-weight:650; } +.camera-control { padding:0 0 8px; border-bottom:1px solid #ffffff10; } +.camera-control .setting-row { margin-top:11px; margin-bottom:9px; } +.camera-control label { font-size:12px; } +.camera-control .camera-control-value { color:#b8ced5; } +.camera-range, .camera-select { width:100%; min-height:28px; accent-color:#a8c7ce; } +.camera-select { color:#eff2f5; background:#30383d; border:1px solid #ffffff26; border-radius:8px; padding:7px 9px; font-size:13px; } +.camera-control.is-inactive { opacity:.44; } +.camera-disabled-hint { font-size:11px; color:#acb5bf; margin-top:4px; } +.camera-settings-actions { display:flex; gap:8px; margin-top:19px; } +.camera-settings-action { flex:1; padding:10px; background:#3b464c; border:1px solid #ffffff23; border-radius:9px; color:#f2f5f6; font-size:12px; cursor:pointer; } +.camera-settings-action.reset { background:#5b454026; color:#f0c4b9; } +@media(max-width:760px) { .audio-popover { max-height:calc(100dvh - 80px); } } diff --git a/alfred/app.backup.weather_outfits/static/css/alfred-mode-icons.css b/alfred/app.backup.weather_outfits/static/css/alfred-mode-icons.css new file mode 100644 index 0000000..d78db8c --- /dev/null +++ b/alfred/app.backup.weather_outfits/static/css/alfred-mode-icons.css @@ -0,0 +1,13 @@ +.alfred-mode-icon { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 36px; + min-height: 36px; + font-size: 26px; + line-height: 1; + transition: transform .25s ease, opacity .25s ease; +} +.alfred-mode-icon[data-mode="thinking"] { animation: alfred-thinking 1.8s ease-in-out infinite; } +@keyframes alfred-thinking { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.12); } } +@media (prefers-reduced-motion: reduce) { .alfred-mode-icon { animation: none !important; } } diff --git a/alfred/app.backup.weather_outfits/static/css/alfred-views.css b/alfred/app.backup.weather_outfits/static/css/alfred-views.css new file mode 100644 index 0000000..8a7de3e --- /dev/null +++ b/alfred/app.backup.weather_outfits/static/css/alfred-views.css @@ -0,0 +1,76 @@ +/* Calendar and Home Assistant views — preserves original dark Alfred layout */ +#stage:has(.alfred-view) {padding:0 0 6px;justify-content:stretch;align-items:stretch} +#content.alfred-view {width:100%;height:100%;text-align:left;min-height:0;} +.view-pane {display:flex;flex-direction:column;gap:12px;max-height:100%;height:100%;min-height:0;padding:8px 1px 14px;} +.view-head {display:flex;justify-content:space-between;align-items:center;gap:12px;} +#content .view-title {font-size:clamp(1.2rem,1.7vw,1.85rem);margin:0;font-weight:500} +.view-close,.view-button,.view-pill {background:#363d42;color:#f3f7fa;border:1px solid #ffffff24;border-radius:10px;padding:9px 14px;cursor:pointer;font:inherit} +.view-close {font-size:1.4rem;min-width:40px;height:40px;padding:0} +.view-button:hover,.view-pill:hover,.view-close:hover {background:#455057} +.view-pill.active,.view-button.primary {background:#427789;border-color:#619aad} +.view-toolbar {display:flex;gap:9px;align-items:center;flex-wrap:wrap;} +.view-nav,.view-modes {display:flex;gap:5px;align-items:center} +.view-period {font-size:.85rem;min-width:215px;text-align:center;color:#d8e0e4} +.view-select {padding:9px;max-width:220px;border-radius:9px;background:#333a3e;color:#eff3f5;border:1px solid #ffffff2b} +.agenda-grid {display:grid;gap:5px;min-height:0;flex:1;overflow:auto;padding:2px;align-content:stretch;} +.agenda-seven,.agenda-month {grid-template-columns:repeat(7,minmax(0,1fr))} +.agenda-month {grid-template-rows:repeat(6,minmax(110px,1fr));} +.agenda-three {grid-template-columns:repeat(3,minmax(0,1fr))} +.agenda-one {grid-template-columns:1fr} +.agenda-day {border:1px solid #ffffff1c;border-radius:11px;min-width:0;min-height:120px;background:#ffffff08;padding:9px;display:flex;flex-direction:column;gap:6px;overflow-y:auto} +.agenda-day.is-today {border-color:#86bfda7a;background:#57839923} +.agenda-day.out-month {opacity:.42} +.agenda-date {font-size:.83rem;font-weight:600;color:#dce2e6;letter-spacing:.01em;padding-bottom:7px;border-bottom:1px solid #ffffff14} +.agenda-event {width:100%;text-align:left;background:color-mix(in srgb,var(--event-color) 17%,#2a2e32);color:#f1f3f5;border:0;border-left:3px solid var(--event-color);border-radius:5px;padding:7px 5px;font-size:clamp(.72rem,.85vw,.95rem);line-height:1.3;cursor:pointer;overflow-wrap:anywhere} +.agenda-event:hover {background:color-mix(in srgb,var(--event-color) 30%,#2a2e32)} +.agenda-empty {font-size:.8rem;color:#667781;margin:auto} +#content .view-footer {color:#9aa8b3;font-size:.76rem;margin:0} +#content .view-error {color:#f7adad;font-size:.85rem;margin:0} +#content .view-empty {font-size:1rem;color:#b8c2ca} +.agenda-form {display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-content:start;gap:14px;max-width:850px;width:100%;overflow-y:auto;margin:12px auto;} +.form-field {display:flex;flex-direction:column;gap:7px;color:#cfd6dd;font-size:.83rem} +.form-field input,.form-field select,.form-field textarea {padding:12px;border-radius:10px;color:#eff3f5;background:#2e3439;border:1px solid #ffffff32;font:inherit;min-width:0} +.form-field textarea {resize:vertical} +.form-check {display:flex;align-items:center;gap:10px} +.view-actions {display:flex;align-items:center;gap:10px;justify-content:flex-end;flex-wrap:wrap;} +.form-message {grid-column:1 / -1;} +.agenda-form .view-actions {grid-column:1 / -1} +.view-danger {color:#ffc4c4;background:#8f383850;border-color:#ed929255} +.detail-modal {position:fixed;inset:0;z-index:30;display:grid;place-items:center;background:#000b;padding:20px} +.detail-box {min-width:min(92vw,300px);max-width:540px;width:100%;border:1px solid #ffffff36;border-radius:19px;background:#292e33;box-shadow:0 25px 90px #000a;padding:24px;} +.detail-box h2 {font-size:1.4rem;margin:0 0 20px}.detail-box p {font-size:1rem;color:#d7dfe2;overflow-wrap:anywhere} +.delete-list {display:grid;gap:8px;overflow:auto}.delete-row {display:flex;justify-content:space-between;align-items:center;gap:10px;border:1px solid #ffffff1c;border-radius:9px;padding:12px} +.temperature-grid {display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px;overflow:auto;padding:4px;max-height:53%;} +.temperature-card {border:1px solid #ffffff22;background:#ffffff0a;border-radius:12px;padding:22px;color:#eaf1f3;white-space:pre-line;text-align:left;font:inherit;cursor:pointer;line-height:1.9} +.temperature-card:hover {background:#ffffff18} +.temperature-history {max-width:700px;text-align:center;margin:auto;width:100%;} +.temperature-history svg {width:100%;max-height:155px} +.temperature-history h2 {font-size:1.1rem;font-weight:450} +.ha-cameras {display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px;overflow:auto;} +.ha-camera-card {background:#ffffff0a;border-radius:13px;overflow:hidden;} +.ha-camera-card h2 {font-size:.95rem;padding:10px;font-weight:500;margin:0} +.ha-camera-card img {display:block;width:100%;aspect-ratio:16/9;object-fit:contain;background:#121619} +@media(max-width:1100px){.view-toolbar{gap:6px}.view-button,.view-pill {padding:7px;font-size:.77rem}.view-period{min-width:150px}.agenda-day{padding:5px}.agenda-event{padding:4px;font-size:.74rem}} +@media(max-width:760px){#stage:has(.alfred-view){min-height:460px}.agenda-grid{min-height:280px}.agenda-seven,.agenda-month{min-width:630px}.agenda-day{min-height:105px}.view-pane{max-height:600px}.view-head{padding:0 4px}.agenda-form{grid-template-columns:1fr}.header-actions{gap:3px}.header-actions .icon-button {width:38px;height:38px}.view-toolbar{overflow:auto}} + +/* Weather dynamic view — same dark UI as calendar. */ +.weather-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px} +.weather-now{display:flex;align-items:center;gap:22px;background:#293238;border:1px solid #ffffff16;border-radius:16px;padding:18px 24px;} +.weather-current-icon{font-size:clamp(44px,6vw,80px);line-height:1;color:#edcb82} +.weather-now-details h2{margin:0 0 8px;font-size:clamp(20px,2.1vw,32px);font-weight:500;color:#f5f6f7} +.weather-now-details p{margin:0;color:#b6c4ce;font-size:clamp(12px,1vw,17px)} +.weather-section-title{font-size:15px;letter-spacing:.02em;color:#bfd0da;margin:7px 0 -2px;font-weight:550} +.weather-days{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:7px} +.weather-day{min-width:0;display:flex;flex-direction:column;align-items:center;text-align:center;gap:5px;background:#293035;border-radius:12px;border:1px solid #ffffff12;padding:12px 5px 10px} +.weather-day p{margin:0} +.weather-day-name{font-size:12px;color:#d4e0e6} +.weather-day-icon{font-size:clamp(22px,2.8vw,40px);color:#eaca85} +.weather-day-condition{font-size:11px;color:#adbcc5;min-height:30px;line-height:1.3} +.weather-day-temp{font-size:21px;color:#f5f7f8;font-weight:600} +.weather-day-low,.weather-rain{font-size:11px;color:#a7b6c0} +.weather-rain{color:#8dc8e4 !important} +.weather-hours{display:flex;gap:7px;overflow-x:auto;padding:3px 0 10px;scrollbar-color:#637480 #2a3237} +.weather-hour{display:flex;flex:0 0 80px;align-items:center;flex-direction:column;gap:9px;padding:10px 6px;background:#293035;border:1px solid #ffffff16;border-radius:12px;color:#cbd7df;font-size:12px} +.weather-hour-icon{font-size:27px;color:#edd298} +@media(max-width:840px){.weather-days{grid-template-columns:repeat(4,minmax(0,1fr))}.weather-now{padding:14px;gap:12px}.weather-current-icon{font-size:48px}} +@media(max-width:470px){.weather-days{grid-template-columns:repeat(3,minmax(0,1fr))}} diff --git a/alfred/app.backup.weather_outfits/static/css/alfred.css b/alfred/app.backup.weather_outfits/static/css/alfred.css new file mode 100644 index 0000000..521a067 --- /dev/null +++ b/alfred/app.backup.weather_outfits/static/css/alfred.css @@ -0,0 +1,62 @@ +:root { + color-scheme: dark; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; + color: #f1f3f4; + background: #202326; +} +* { box-sizing: border-box; } +[hidden] { display:none !important; } +body { margin: 0; min-height: 100vh; background: radial-gradient(ellipse at 52% 34%, #33373a 0, #25282b 46%, #1a1d20 100%); } +.alfred-shell { position:relative; isolation:isolate; display:grid; grid-template-rows: 56px minmax(0,1fr) 142px; gap:16px; min-height:100vh; min-height:100dvh; padding:25px 36px; } +.topbar { display:flex; align-items:center; justify-content:space-between; z-index:5; } +.status { display:inline-flex; align-items:center; gap:11px; font-size:14px; font-weight:500; color:#e6eaed; letter-spacing:.045em; } +.status-dot { width:10px; height:10px; border-radius:50%; background:#7d8790; box-shadow:0 0 0 4px #ffffff0a; transition:background .25s,box-shadow .25s; } +.status-dot[data-mode="listening"] { background:#70c7ce; box-shadow:0 0 0 5px #70c7ce17; animation:status-pulse 1.6s infinite; } +.status-dot[data-mode="thinking"] { background:#e7bb72; box-shadow:0 0 0 5px #e7bb7217; animation:status-pulse 1.9s infinite; } +.status-dot[data-mode="speaking"] { background:#9ad0b7; box-shadow:0 0 0 5px #9ad0b717; animation:status-pulse 1.4s infinite; } +.status-dot[data-mode="error"] { background:#f18989; box-shadow:0 0 0 5px #f1898917; } +.header-actions { display:flex; gap:10px; align-items:center; } +.icon-button { display:grid; place-items:center; width:44px; height:44px; border:1px solid #ffffff22; border-radius:13px; background:#ffffff08; color:#e8ebee; cursor:pointer; transition:background .2s,border-color .2s; } +.icon-button:hover { background:#ffffff14; border-color:#ffffff35; } +.icon-button:focus-visible,.panel-action:focus-visible,.quiet-button:focus-visible { outline:2px solid #adc8e0; outline-offset:3px; } +.icon-button svg { width:21px; height:21px; } +.icon-button[aria-pressed="true"] { color:#f4a2a2; background:#ae47471b; border-color:#b95d5d55; } +.speaker-off-icon { display:none; } +.icon-button[aria-pressed="true"] .speaker-on-icon { display:none; } +.icon-button[aria-pressed="true"] .speaker-off-icon { display:block; } +#stage { display:flex; align-items:center; justify-content:center; overflow:hidden; min-height:0; padding:0 8%; } +#content { width:min(990px,86vw); text-align:center; transition:opacity .25s ease,transform .25s ease; animation:appear .45s ease; } +#content.swap { opacity:0; transform:translateY(10px) scale(.99); } +#content h1 { font-size:clamp(2rem,4vw,4.3rem); font-weight:350; margin:.2em 0; line-height:1.2; } +#content p { font-size:clamp(1rem,1.6vw,1.35rem); color:#cdd1d4; line-height:1.55; white-space:pre-wrap; overflow-wrap:anywhere; } +.hero { max-width:min(100%,750px); max-height:56vh; object-fit:contain; border-radius:18px; box-shadow:0 18px 50px #0006; animation:appear .45s ease; } +.cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(205px,1fr)); gap:13px; text-align:left; } +.card { padding:18px; border:1px solid #ffffff1c; background:#ffffff0a; border-radius:15px; } +.card h2 { margin:0 0 8px; font-weight:500; } +.card p { margin:0; font-size:1rem; } +#wave { display:flex; align-items:center; justify-self:center; width: min(560px,37vw); height:100px; align-self:end; } +#wave canvas { width:100%; height:100%; } +.camera-panel,.transcript-panel { position:absolute; z-index:2; bottom:34px; border:1px solid #ffffff20; background:#222629db; box-shadow:0 14px 35px #0002; border-radius:16px; backdrop-filter:blur(12px); } +.camera-panel { left:36px; width:clamp(220px,24vw,355px); overflow:hidden; } +.transcript-panel { right:36px; width:clamp(250px,25vw,405px); padding:15px 17px 18px; min-height:136px; } +.panel-heading { display:flex; align-items:center; justify-content:space-between; gap:12px; min-height:42px; padding:0 13px; } +.transcript-panel .panel-heading { padding:0; min-height:30px; } +.panel-title { display:flex; align-items:center; gap:9px; font-size:10px; letter-spacing:.1em; font-weight:700; color:#abb5be; } +.panel-indicator { width:7px; height:7px; border-radius:50%; background:#71818c; } +.camera-panel[data-active="true"] .panel-indicator { background:#83c5cc; } +.transcript-panel[data-active="true"] .stt-indicator { background:#83c5cc; } +.panel-action { border:0; border-radius:8px; background:#ffffff0c; color:#e3e8ed; padding:7px 10px; font-size:12px; cursor:pointer; } +.panel-action:hover { background:#ffffff22; } +.camera-viewport { display:grid; place-items:center; position:relative; aspect-ratio:16/9; background:#101315; overflow:hidden; } +.camera-viewport > * { grid-area:1/1; } +.camera-viewport img { width:100%; height:100%; object-fit:contain; } +.camera-placeholder { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; width:100%; height:100%; color:#919ca4; font-size:12px; text-align:center; padding:12px; } +.camera-placeholder svg { width:30px; height:30px; opacity:.65; } +.transcript-state { color:#909ba3; font-size:11px; flex-shrink:0; } +.transcript-text { margin:11px 0 0; color:#edf0f2; font-size:14px; line-height:1.6; overflow-wrap:anywhere; max-height:110px; overflow-y:auto; } +.transcript-text.is-placeholder { color:#87939c; } +@keyframes appear { from { opacity:0; transform:translateY(8px) } to { opacity:1;transform:translateY(0) } } +@keyframes status-pulse { 0%,100% { opacity:1 } 50% { opacity:.45 } } +@media(max-width:1120px) { #wave { width:28vw; } .camera-panel { width:27vw; } .transcript-panel { width:29vw; } } +@media(max-width:760px) { .alfred-shell { padding:14px; grid-template-rows:50px minmax(220px,1fr) 80px auto; gap:8px; } #stage { padding:10px; } #wave { width:80%; height:70px; } .camera-panel,.transcript-panel { position:relative; left:auto; right:auto; bottom:auto; width:100%; } .camera-panel { grid-row:4; max-width:none; } .transcript-panel { grid-row:5; min-height:auto; } .topbar { grid-row:1; } } +@media(prefers-reduced-motion:reduce) { * { animation:none !important; transition:none !important; } } diff --git a/alfred/app.backup.weather_outfits/static/js/alfred-camera-settings.js b/alfred/app.backup.weather_outfits/static/js/alfred-camera-settings.js new file mode 100644 index 0000000..8ba293e --- /dev/null +++ b/alfred/app.backup.weather_outfits/static/js/alfred-camera-settings.js @@ -0,0 +1,175 @@ +/* Réglages matériels de webcam : curseurs générés selon ce que V4L2 expose. */ +(() => { + 'use strict'; + const pane = document.getElementById('audioSettingsPanel'); + const tabAudio = document.getElementById('audioTab'); + const tabCamera = document.getElementById('cameraTab'); + const sectionAudio = document.getElementById('audioSettingsSection'); + const sectionCamera = document.getElementById('cameraSettingsSection'); + const items = document.getElementById('cameraSettingsItems'); + const message = document.getElementById('cameraSettingsMessage'); + const reload = document.getElementById('cameraSettingsReload'); + const reset = document.getElementById('cameraSettingsReset'); + if (!pane || !tabAudio || !tabCamera || !items) return; + + const labels = { + brightness: 'Luminosité', contrast: 'Contraste', saturation: 'Saturation', + hue: 'Teinte', gamma: 'Gamma', gain: 'Gain vidéo', sharpness: 'Netteté', + backlight_compensation: 'Compensation contre-jour', + power_line_frequency: 'Fréquence anti-scintillement', + white_balance_automatic: 'Balance des blancs automatique', + white_balance_temperature: 'Température des blancs (K)', + auto_exposure: 'Exposition', exposure_time_absolute: 'Temps d’exposition', + exposure_dynamic_framerate: 'Fréquence d’image dynamique' + }; + const groups = [ + {name: 'Image', keys: ['brightness','contrast','saturation','sharpness','hue','gamma','gain','backlight_compensation']}, + {name: 'Éclairage et couleurs', keys: ['white_balance_automatic','white_balance_temperature','power_line_frequency']}, + {name: 'Exposition', keys: ['auto_exposure','exposure_time_absolute','exposure_dynamic_framerate']} + ]; + const exposureChoices = {1: 'Manuelle', 3: 'Automatique'}; + const powerChoices = {0: 'Désactivé', 1: '50 Hz', 2: '60 Hz'}; + let working = false; + + function showTab(which) { + const camera = which === 'camera'; + tabCamera.setAttribute('aria-selected', String(camera)); + tabAudio.setAttribute('aria-selected', String(!camera)); + sectionCamera.hidden = !camera; + sectionAudio.hidden = camera; + if (camera) load(); + } + tabAudio.addEventListener('click', () => showTab('audio')); + tabCamera.addEventListener('click', () => showTab('camera')); + document.getElementById('audioSettingsButton')?.addEventListener('click', () => { + if (!pane.hidden) showTab('audio'); + }); + + async function api(path, payload) { + const opts = {cache: 'no-store'}; + if (payload !== undefined) { + opts.method = 'POST'; + opts.headers = {'Content-Type':'application/json', 'X-Alfred-Request':'1'}; + opts.body = JSON.stringify(payload); + } + const res = await fetch(path, opts); + const data = await res.json(); + if (!res.ok) throw new Error(data.error || `Erreur HTTP ${res.status}`); + return data; + } + + function elt(tag, klass, text) { + const e = document.createElement(tag); + if (klass) e.className = klass; + if (text !== undefined) e.textContent = text; + return e; + } + + function controlRow(name, meta) { + const row = elt('div', 'camera-control'); + if (meta.inactive) row.classList.add('is-inactive'); + const header = elt('div','setting-row'); + const label = elt('label', null, labels[name] || name); + const output = elt('output','camera-control-value',String(meta.value ?? '—')); + header.append(label, output); row.appendChild(header); + let input; + if (meta.kind === 'bool' || meta.kind === 'menu') { + input = elt('select','camera-select'); + let opts; + if (meta.kind === 'bool') opts = [{value:1,label:'Activé'},{value:0,label:'Désactivé'}]; + else if (name === 'auto_exposure') opts = [{value:3,label:'Automatique'},{value:1,label:'Manuelle'}]; + else if (name === 'power_line_frequency') opts = Object.entries(powerChoices).map(([value,label])=>({value:Number(value),label})); + else opts = meta.menu || []; + const allowed = meta.menu?.length && meta.kind === 'menu' ? new Set(meta.menu.map(i=>i.value)) : null; + for (const opt of opts) { + if (allowed && !allowed.has(opt.value)) continue; + const option = elt('option',null,opt.label); + option.value = String(opt.value); + input.appendChild(option); + } + input.value = String(meta.value); + const updateLabel = () => { + output.textContent = input.options[input.selectedIndex]?.text || String(input.value); + }; + updateLabel(); + input.addEventListener('change', () => save(name, Number(input.value))); + } else { + input = elt('input','camera-range'); + input.type = 'range'; + input.min = String(meta.min); input.max = String(meta.max); + input.step = String(meta.step || 1); + input.value = String(meta.value); + input.addEventListener('input', () => { output.textContent = input.value; }); + // Appliquer au relâchement : évite des centaines de commandes shell. + input.addEventListener('change', () => save(name, Number(input.value))); + } + input.id = `v4l-${name}`; + label.htmlFor = input.id; + input.disabled = Boolean(meta.inactive || working); + row.appendChild(input); + if (meta.inactive) row.appendChild(elt('div','camera-disabled-hint', 'Désactive le mode automatique pour régler cette valeur.')); + return row; + } + + function render(data) { + items.replaceChildren(); + const available = data.controls || {}; + for (const group of groups) { + const found = group.keys.filter(name => available[name]); + if (!found.length) continue; + items.appendChild(elt('h3','camera-group-title',group.name)); + for (const name of found) items.appendChild(controlRow(name,available[name])); + } + if (!items.children.length) items.appendChild(elt('p','camera-settings-note','Aucun réglage V4L2 disponible.')); + } + + async function load() { + if (working) return; + message.textContent = 'Lecture des réglages caméra…'; + try { + render(await api('/api/camera/settings')); + message.textContent = ''; + } catch (err) { + message.textContent = err.message; + items.replaceChildren(); + } + } + + async function save(control, value) { + if (working) return; + working = true; + message.textContent = 'Application…'; + try { + const result = await api('/api/camera/settings', {control, value}); + working = false; + render(result); + message.textContent = 'Réglage appliqué et mémorisé.'; + } catch (err) { + message.textContent = err.message; + working = false; + await load(); + return; + } + working = false; + } + + reload.addEventListener('click', load); + reset.addEventListener('click', async () => { + if (working || !window.confirm('Rétablir tous les réglages caméra d’origine ?')) return; + working = true; + message.textContent = 'Restauration en cours…'; + try { + const result = await api('/api/camera/reset', {}); + working = false; + render(result); + message.textContent = 'Valeurs d’usine restaurées.'; + } catch (err) { + message.textContent = err.message; + } finally { + working = false; + } + }); + window.alfredSocket?.on('camera_settings', data => { + if (!sectionCamera.hidden && !working) render(data); + }); +})(); diff --git a/alfred/app.backup.weather_outfits/static/js/alfred-camera.js b/alfred/app.backup.weather_outfits/static/js/alfred-camera.js new file mode 100644 index 0000000..fc58979 --- /dev/null +++ b/alfred/app.backup.weather_outfits/static/js/alfred-camera.js @@ -0,0 +1,41 @@ +/* Lecture MJPEG activée manuellement : libère la caméra si désactivée. */ +(() => { + 'use strict'; + const button = document.getElementById('cameraToggle'); + const image = document.getElementById('cameraFeed'); + const placeholder = document.getElementById('cameraPlaceholder'); + const message = document.getElementById('cameraMessage'); + const panel = document.querySelector('.camera-panel'); + let active = false; + + function stop() { + active = false; + image.removeAttribute('src'); + image.hidden = true; + placeholder.hidden = false; + message.textContent = 'Caméra désactivée'; + button.textContent = 'Activer'; + button.setAttribute('aria-pressed', 'false'); + panel.dataset.active = 'false'; + } + function start() { + active = true; + placeholder.hidden = false; + message.textContent = 'Connexion à la caméra…'; + image.hidden = false; + image.src = `/api/camera/stream?t=${Date.now()}`; + button.textContent = 'Arrêter'; + button.setAttribute('aria-pressed', 'true'); + } + button.addEventListener('click', () => active ? stop() : start()); + image.addEventListener('load', () => { if (active) { placeholder.hidden = true; panel.dataset.active = 'true'; } }); + image.addEventListener('error', () => { + if (!active) return; + stop(); + message.textContent = 'Caméra indisponible ou déjà utilisée'; + }); + // Si l'onglet n'est pas visible, relâcher le périphérique USB. + document.addEventListener('visibilitychange', () => { if (document.hidden && active) stop(); }); + window.addEventListener('pagehide', stop); + stop(); +})(); diff --git a/alfred/app.backup.weather_outfits/static/js/alfred-controls.js b/alfred/app.backup.weather_outfits/static/js/alfred-controls.js new file mode 100644 index 0000000..8f3ae69 --- /dev/null +++ b/alfred/app.backup.weather_outfits/static/js/alfred-controls.js @@ -0,0 +1,93 @@ +/* Réglages volume/gain dans la fenêtre, mute micro + haut-parleur dans la barre haute. */ +(() => { + 'use strict'; + const panel = document.getElementById('audioSettingsPanel'); + const button = document.getElementById('audioSettingsButton'); + const close = document.getElementById('closeAudioSettings'); + const speaker = document.getElementById('speakerMuteQuick'); + const mic = document.getElementById('micMute'); + const volume = document.getElementById('volumeSlider'); + const volumeValue = document.getElementById('volumeValue'); + const gain = document.getElementById('micGainSlider'); + const gainValue = document.getElementById('micGainValue'); + const message = document.getElementById('audioSettingsMessage'); + let settings = { volume:70, mic_gain:null, speaker_muted:false, mic_muted:false }; + let volumeTimer, gainTimer; + + function render() { + volume.value = settings.volume; + volumeValue.textContent = `${settings.volume} %`; + gain.disabled = !Number.isFinite(settings.mic_gain); + if (!gain.disabled) gain.value = settings.mic_gain; + gainValue.textContent = gain.disabled ? 'N/D' : `${settings.mic_gain} %`; + speaker.setAttribute('aria-pressed', String(settings.speaker_muted)); + speaker.setAttribute('aria-label', settings.speaker_muted ? 'Réactiver le son' : 'Couper le son'); + speaker.title = speaker.getAttribute('aria-label'); + mic.setAttribute('aria-pressed', String(settings.mic_muted)); + mic.setAttribute('aria-label', settings.mic_muted ? 'Réactiver le microphone' : 'Couper le microphone'); + mic.title = mic.getAttribute('aria-label'); + } + + async function update(changes) { + message.textContent = ''; + try { + const response = await fetch('/api/audio/settings', { + method:'POST', + headers:{'Content-Type':'application/json'}, + body:JSON.stringify(changes) + }); + const data = await response.json(); + if (!response.ok) throw new Error(data.error || `HTTP ${response.status}`); + settings = { ...settings, ...data }; + render(); + } catch (err) { + message.textContent = `Impossible de modifier le réglage : ${err.message}`; + console.warn('[ALFRED AUDIO]', err); + await refresh(); + } + } + + async function refresh() { + try { + const response = await fetch('/api/audio/settings', {cache:'no-store'}); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + settings = { ...settings, ...await response.json() }; + render(); + } catch (err) { + message.textContent = 'Réglages indisponibles.'; + console.warn('[ALFRED AUDIO]', err); + } + } + + function setOpen(open) { + panel.hidden = !open; + button.setAttribute('aria-expanded', String(open)); + if (open) { refresh(); close.focus(); } else { button.focus(); } + } + button.addEventListener('click', () => setOpen(panel.hidden)); + close.addEventListener('click', () => setOpen(false)); + document.addEventListener('pointerdown', e => { + if (!panel.hidden && !panel.contains(e.target) && !button.contains(e.target)) { + panel.hidden = true; button.setAttribute('aria-expanded', 'false'); + } + }); + document.addEventListener('keydown', e => { if (e.key === 'Escape' && !panel.hidden) setOpen(false); }); + + volume.addEventListener('input', () => { + settings.volume = Number(volume.value); render(); + clearTimeout(volumeTimer); + volumeTimer = setTimeout(() => update({volume:Number(volume.value)}), 220); + }); + gain.addEventListener('input', () => { + settings.mic_gain = Number(gain.value); render(); + clearTimeout(gainTimer); + gainTimer = setTimeout(() => update({mic_gain:Number(gain.value)}), 250); + }); + speaker.addEventListener('click', () => update({speaker_muted:!settings.speaker_muted})); + mic.addEventListener('click', () => update({mic_muted:!settings.mic_muted})); + + if (window.alfredSocket) window.alfredSocket.on('audio_settings', data => { + settings = {...settings, ...data}; render(); + }); + render(); refresh(); +})(); diff --git a/alfred/app.backup.weather_outfits/static/js/alfred-mode-icons.js b/alfred/app.backup.weather_outfits/static/js/alfred-mode-icons.js new file mode 100644 index 0000000..9a36c90 --- /dev/null +++ b/alfred/app.backup.weather_outfits/static/js/alfred-mode-icons.js @@ -0,0 +1,35 @@ +/* Icônes de statut : compatible avec alfred.js existant, sans le remplacer. */ +(() => { + 'use strict'; + const el = document.getElementById('mode'); + if (!el) return; + const icons = { + idle: ['🛏️', 'Repos'], repos: ['🛏️', 'Repos'], + listening: ['👂', 'Écoute'], thinking: ['🧠', 'Réflexion'], + speaking: ['🔊', 'Parole'], error: ['⚠️', 'Erreur'] + }; + let current = 'idle'; + function apply(raw) { + const key = String(raw || '').trim().toLowerCase(); + if (!icons[key]) return; + current = key; + const [icon, label] = icons[key]; + if (el.textContent !== icon) el.textContent = icon; + el.title = label; + el.setAttribute('aria-label', label); + el.dataset.mode = key; + } + // L'ancien alfred.js écrit le nom du mode dans #mode. + // L'observateur le convertit immédiatement en icône, sans modifier ce script. + const observer = new MutationObserver(() => { + const value = el.textContent.trim(); + if (icons[value.toLowerCase()]) apply(value); + }); + observer.observe(el, { childList: true, characterData: true, subtree: true }); + apply(el.textContent); + // Récupère également l'état actuel après un rafraîchissement de page. + fetch('/api/state', { cache: 'no-store' }) + .then(r => { if (!r.ok) throw new Error('État indisponible'); return r.json(); }) + .then(data => { if (data && data.mode) apply(data.mode); }) + .catch(() => {}); +})(); diff --git a/alfred/app.backup.weather_outfits/static/js/alfred-views.js b/alfred/app.backup.weather_outfits/static/js/alfred-views.js new file mode 100644 index 0000000..0023e8f --- /dev/null +++ b/alfred/app.backup.weather_outfits/static/js/alfred-views.js @@ -0,0 +1,296 @@ +/* Alfred • visual views, powered by first-party Flask endpoints. No extra CDN. */ +(() => { + 'use strict'; + const root = document.getElementById('content'); + let current = null, camerasRefresh = null; + const DAYS = ['lun.','mar.','mer.','jeu.','ven.','sam.','dim.']; + const PALETTE = ['#77bcdf','#e5b677','#9dc99a','#d3a2d5','#8cb7c9','#ec9f95','#b6b5e1','#b9d78d']; + const el = (tag, cls, text) => { const x=document.createElement(tag); if(cls)x.className=cls; if(text!==undefined)x.textContent=String(text); return x; }; + const btn = (label, fn, cls='view-button') => { const b=el('button',cls,label); b.type='button'; b.addEventListener('click',fn);return b; }; + const api = async (url, options) => { + const r=await fetch(url,{cache:'no-store', ...options}); + const data=await r.json().catch(()=>({error:`Erreur HTTP ${r.status}`})); + if(!r.ok)throw new Error(data.error || `HTTP ${r.status}`); + return data; + }; + const post = (url,data) => api(url,{method:'POST',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},body:JSON.stringify(data)}); + const dateLabel = iso => new Date(iso).toLocaleDateString('fr-FR',{day:'numeric',month:'long',year:'numeric'}); + const timeLabel = iso => new Date(iso).toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'}); + const localKey = d => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; + const atDay = (d,h=0) => new Date(d.getFullYear(),d.getMonth(),d.getDate(),h); + const dayShift = (d,n) => new Date(d.getFullYear(),d.getMonth(),d.getDate()+n,d.getHours(),d.getMinutes()); + const eventOnDay=(ev,day)=> {const start=new Date(ev.start),end=new Date(ev.end);return startatDay(day);}; + const fmtRange=(a,b)=>`${dateLabel(a)} — ${dateLabel(new Date(new Date(b).getTime()-1000).toISOString())}`; + const wrap = (title) => { + clearInterval(camerasRefresh);camerasRefresh=null;root.replaceChildren(); + root.classList.add('alfred-view'); + const pane=el('div','view-pane'),head=el('header','view-head'); + head.append(el('h1','view-title',title)); + head.append(btn('×',goHome,'view-close')); + pane.append(head);root.append(pane);return {pane,head}; + }; + const showError=(err,pane)=>pane.append(el('p','view-error',err.message||String(err))); + const canRender = data=>['calendar','calendar_form','delete_candidates','temperatures','cameras','weather','home'].includes(data?.type); + function render(data){ + if(!canRender(data))return false; + current=data; + if(data.type==='calendar')drawCalendar(data); + if(data.type==='calendar_form')drawForm(data); + if(data.type==='delete_candidates')drawDeleteChoices(data); + if(data.type==='temperatures')drawTemps(data); + if(data.type==='weather')drawWeather(data); + if(data.type==='cameras')drawCameras(data); + if(data.type==='home'){root.classList.remove('alfred-view');root.replaceChildren(el('h1','', 'Bonjour.'), el('p','',data.text||'À ton écoute.')); clearInterval(camerasRefresh);camerasRefresh=null;} + return true; + } + const getSelected=()=>document.getElementById('calendar-source')?.value || ''; + async function loadCalendar(start,end,view,filter){ + const q=new URLSearchParams({start:new Date(start).toISOString(),end:new Date(end).toISOString()}); + if(filter)q.append('calendar_id',filter); + const data=await api('/api/calendar/events?'+q); + render({type:'calendar',view,start:new Date(start).toISOString(),end:new Date(end).toISOString(),...data,selected:filter||''}); + } + function moveCalendar(direction){ + if(!current || current.type!=='calendar')return; + const start=new Date(current.start),end=new Date(current.end),view=current.view; + let a,b; + if(view==='month'){ + a=new Date(start.getFullYear(),start.getMonth()+direction,1); + b=new Date(a.getFullYear(),a.getMonth()+1,1); + }else{ + const days=Math.round((end-start)/86400000) || 1; + a=dayShift(start,days*direction);b=dayShift(end,days*direction); + } + loadCalendar(a,b,view,getSelected()).catch(err=>{const {pane}=wrap('Agenda');showError(err,pane)}); + } + const details = (event) => { + const d=el('div','detail-modal'),box=el('section','detail-box'); + box.setAttribute('role','dialog');box.setAttribute('aria-modal','true');box.setAttribute('aria-label','Détails de l’événement'); + box.append(el('h2','',event.title)); + box.append(el('p','',`${dateLabel(event.start)}${event.all_day?' · Toute la journée':' · '+timeLabel(event.start)+'–'+timeLabel(event.end)}`)); + box.append(el('p','',event.calendar_name)); + if(event.location)box.append(el('p','',`Lieu : ${event.location}`)); + if(event.description)box.append(el('p','',event.description)); + const acts=el('div','view-actions');acts.append(btn('Fermer',()=>d.remove())); + if(event.can_delete) acts.append(btn('Supprimer…',async()=>{ + const recurrence=event.recurrence_id ? 'Cela supprimera cette occurrence.' : 'Pour une série récurrente, cela peut supprimer toute la série.'; + if(!confirm(`Supprimer « ${event.title} » du calendrier « ${event.calendar_name} » ?\n${recurrence}`))return; + try{await post('/api/calendar/delete',{calendar_id:event.calendar_id,uid:event.id,start:event.start,end:event.end,recurrence_id:event.recurrence_id||''}); d.remove();await loadCalendar(current.start,current.end,current.view,getSelected());} + catch(err){alert(err.message)} + },'view-danger')); + box.append(acts);d.append(box);document.body.append(d); + d.addEventListener('click',e=>{if(e.target===d)d.remove()}); + }; + function drawCalendar(data){ + const {pane,head}=wrap('Planning'); + const toolbar=el('div','view-toolbar'); + const first=el('div','view-nav'); + first.append(btn('‹',()=>moveCalendar(-1)),el('span','view-period',fmtRange(data.start,data.end)),btn('›',()=>moveCalendar(1))); + toolbar.append(first); + const modes=el('div','view-modes'); + [['Jour',1,'day'],['3 jours',3,'days'],['7 jours',7,'days'],['Semaine',7,'week'],['Mois',31,'month']].forEach(([label,n,v])=>{ + const b=btn(label,()=>{ + const now=new Date(),start=(v==='week')?dayShift(atDay(now),-((now.getDay()+6)%7)):v==='month'?new Date(now.getFullYear(),now.getMonth(),1):atDay(now); + const end=v==='month'?new Date(start.getFullYear(),start.getMonth()+1,1):dayShift(start,n); + loadCalendar(start,end,v,getSelected()).catch(err=>showError(err,pane)); + },'view-pill'+(data.view===v?' active':''));modes.append(b); + });toolbar.append(modes); + const src=el('select','view-select');src.id='calendar-source'; + src.append(new Option('Tous les calendriers','')); + (data.calendars||[]).forEach(c=>src.append(new Option(c.name,c.entity_id))); + src.value=data.selected||''; + src.addEventListener('change',()=>loadCalendar(data.start,data.end,data.view,src.value).catch(err=>showError(err,pane))); + toolbar.append(src,btn('+ Rendez-vous',()=>openForm())); + pane.append(toolbar); + if(data.errors?.length)pane.append(el('p','view-error','Calendriers partiellement indisponibles : '+data.errors.join(' · '))); + const grid=el('div','agenda-grid'); + const start=new Date(data.start),end=new Date(data.end); + let days=[],cursor=atDay(start); + const month=data.view==='month'; + if(month) cursor=dayShift(cursor,-((cursor.getDay()+6)%7)); + let limit=0; + while(cursor[c.entity_id, PALETTE[i%PALETTE.length]])); + days.forEach(d=>{ + const cell=el('section','agenda-day'); + if(month && d.getMonth()!==start.getMonth())cell.classList.add('out-month'); + if(localKey(d)===localKey(new Date()))cell.classList.add('is-today'); + cell.append(el('div','agenda-date',d.toLocaleDateString('fr-FR',{weekday:'short',day:'numeric',month:month?undefined:'short'}))); + const events=(data.events||[]).filter(ev=>eventOnDay(ev,d)); + if(!events.length)cell.append(el('div','agenda-empty','—')); + events.forEach(ev=>{ + const e=btn((ev.all_day?'':timeLabel(ev.start)+' ')+ev.title,()=>details(ev),'agenda-event'); + e.style.setProperty('--event-color',colors.get(ev.calendar_id)||'#7bbbe2'); + e.title=`${ev.title} · ${ev.calendar_name}`;cell.append(e); + }); + grid.append(cell); + }); + pane.append(grid); + pane.append(el('p','view-footer',`${data.events?.length||0} événement(s) · ${data.calendars?.length||0} calendrier(s) Home Assistant`)); + } + async function openForm(values){ + let calendars=[]; + try {const info=await api('/api/calendar/calendars');calendars=info.calendars||[];} + catch(err){const {pane}=wrap('Nouveau rendez-vous');showError(err,pane);return;} + render({type:'calendar_form',calendars,values:values||{}}); + } + function drawForm(data){ + const {pane}=wrap('Ajouter un événement'); + const v=data.values||{}; + const form=el('form','agenda-form');form.autocomplete='off'; + const field=(label,node)=>{const row=el('label','form-field');row.append(el('span','',label),node);form.append(row);return node}; + const makeInput=(type,value)=>{const i=el('input');i.type=type;i.value=value||'';i.required=true;return i}; + const title=field('Titre',makeInput('text',v.title));title.maxLength=200; + const src=el('select');src.required=true;src.append(new Option('Choisir un calendrier','')); + data.calendars.filter(c=>c.can_create).forEach(c=>src.append(new Option(c.name,c.entity_id))); + src.value=v.calendar_id||'';field('Calendrier',src); + const day=field('Date',makeInput('date',v.date||localKey(new Date()))); + const startTime=field('Début',makeInput('time',v.time||'09:00')); + const duration=field('Durée (minutes)',makeInput('number',v.duration_minutes||60));duration.min=15;duration.max=1440; + const location=field('Lieu (facultatif)',makeInput('text',''));location.required=false; + const description=field('Description (facultatif)',el('textarea'));description.required=false;description.rows=2; + const allDay=el('input');allDay.type='checkbox';allDay.required=false; + const allDayWrap=el('label','form-check');allDayWrap.append(allDay,el('span','','Toute la journée'));form.append(allDayWrap); + allDay.addEventListener('change',()=>{startTime.disabled=allDay.checked;duration.disabled=allDay.checked}); + const msg=el('p','form-message'); + const actions=el('div','view-actions');actions.append(btn('Annuler',goHome)); + const submit=el('button','view-button primary','Enregistrer');submit.type='submit';actions.append(submit); + form.append(actions,msg);pane.append(form); + form.addEventListener('submit',async e=>{ + e.preventDefault();submit.disabled=true;msg.textContent='Enregistrement…'; + try{ + const from=allDay.checked?new Date(day.value+'T00:00'):new Date(day.value+'T'+startTime.value+':00'); + if(Number.isNaN(from.getTime()))throw new Error('Date invalide.'); + const end=allDay.checked?dayShift(from,1):new Date(from.getTime()+Number(duration.value)*60000); + await post('/api/calendar/create',{calendar_id:src.value,title:title.value,start:from.toISOString(),end:end.toISOString(),all_day:allDay.checked,location:location.value,description:description.value}); + const first=atDay(from);await loadCalendar(first,dayShift(first,7),'days'); + }catch(err){msg.textContent=err.message;submit.disabled=false} + }); + } + function drawDeleteChoices(data){ + const {pane}=wrap('Choisir un événement à supprimer'); + const arr=el('div','delete-list'); + (data.events||[]).forEach(ev=>{ + const row=el('div','delete-row');row.append(el('span','',`${ev.title} • ${dateLabel(ev.start)} • ${ev.calendar_name}`)); + row.append(btn('Détails / Supprimer',()=>details(ev),'view-button'));arr.append(row); + });pane.append(arr); + if(data.awaiting_confirmation)pane.append(el('p','view-footer','Dis « confirme » pour supprimer, ou « annule ».')); + } + function drawTemps(data){ + const {pane}=wrap('Températures Home Assistant'); + if(!data.items?.length){pane.append(el('p','view-empty','Aucun capteur de température trouvé.'));return} + const grid=el('div','temperature-grid');pane.append(grid); + data.items.forEach(item=>{ + const c=btn(`${item.name}\n${item.value.toFixed(1)} ${item.unit}` ,async()=>{ + const q=new URLSearchParams({entity_id:item.entity_id,hours:'24'}); + try{const data=await api('/api/ha/temperature/history?'+q);showHistory(item,data.items||[],pane)} + catch(err){showError(err,pane)} + },'temperature-card');grid.append(c); + });pane.append(el('p','view-footer','Touchez un capteur pour afficher son historique sur 24 h.')); + } + function showHistory(item,points,pane){ + pane.querySelector('.temperature-history')?.remove(); + const section=el('section','temperature-history');section.append(el('h2','',item.name+' · 24 dernières heures')); + if(points.length<2){section.append(el('p','', 'Historique insuffisant.'));pane.append(section);return} + const vals=points.map(p=>p.value);const lo=Math.min(...vals)-0.4,hi=Math.max(...vals)+0.4; + const a=Date.parse(points[0].time),b=Date.parse(points[points.length-1].time); + const coords=points.map((p,i)=>`${30+560*((Date.parse(p.time)-a)/Math.max(1,b-a))},${140-110*((p.value-lo)/Math.max(0.1,hi-lo))}`).join(' '); + const ns='http://www.w3.org/2000/svg',svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 620 170');svg.setAttribute('role','img');svg.setAttribute('aria-label','Courbe de température'); + const poly=document.createElementNS(ns,'polyline');poly.setAttribute('points',coords);poly.setAttribute('fill','none');poly.setAttribute('stroke','#83c5cc');poly.setAttribute('stroke-width','3');svg.append(poly); + section.append(svg,el('p','',`Min ${Math.min(...vals).toFixed(1)}° · Max ${Math.max(...vals).toFixed(1)}°`));pane.append(section); + } + function drawCameras(data){ + const {pane}=wrap('Caméras Home Assistant'); + if(!data.items?.length){pane.append(el('p','view-empty','Aucune caméra Home Assistant disponible.'));return} + const grid=el('div','ha-cameras');pane.append(grid); + const imgs=[]; + data.items.forEach(item=>{ + const card=el('div','ha-camera-card');card.append(el('h2','',item.name)); + const img=el('img');img.alt='Instantané caméra '+item.name;img.loading='lazy';imgs.push([img,item.entity_id]);card.append(img);grid.append(card); + }); + const reload=()=>{if(document.hidden)return; imgs.forEach(([img,eid])=>img.src='/api/ha/camera/'+encodeURIComponent(eid)+'/snapshot?t='+Date.now())}; + reload();camerasRefresh=setInterval(reload,15000); + pane.append(el('p','view-footer','Instantanés réactualisés toutes les 15 s lorsque cette vue est ouverte.')); + } + + const WEATHER_ICONS = { + 'sunny':'☀', 'clear-night':'☾', 'cloudy':'☁', 'partlycloudy':'⛅', + 'fog':'≋', 'rainy':'🌧', 'pouring':'🌧', 'hail':'🌨', + 'lightning':'⛈', 'lightning-rainy':'⛈', 'snowy':'❄', + 'snowy-rainy':'🌨', 'windy':'〰', 'windy-variant':'〰' + }; + const weatherIcon = cond => WEATHER_ICONS[cond] || '◌'; + const tempText = (val, unit='°C') => typeof val === 'number' ? `${Math.round(val)}${unit}` : '—'; + function drawWeather(data){ + const {pane}=wrap('Météo · '+(data.name||'Home Assistant')); + const nav=el('div','weather-toolbar'); + nav.append(btn('Actualiser',openWeather,'view-pill')); + nav.append(el('span','view-footer','Source : Home Assistant')); + pane.append(nav); + const c=data.current||{},now=el('section','weather-now'); + const badge=el('div','weather-current-icon',weatherIcon(c.condition));badge.setAttribute('aria-hidden','true'); + const details=el('div','weather-now-details'); + details.append(el('h2','',`${tempText(c.temperature,data.temperature_unit)} · ${c.description||'Conditions inconnues'}`)); + const metrics=[]; + if(c.apparent_temperature!==null && c.apparent_temperature!==undefined)metrics.push(`Ressenti ${tempText(c.apparent_temperature,data.temperature_unit)}`); + if(c.humidity!==null && c.humidity!==undefined)metrics.push(`Humidité ${c.humidity}%`); + if(c.wind_speed!==null && c.wind_speed!==undefined)metrics.push(`Vent ${c.wind_speed} ${data.wind_speed_unit||'km/h'}`); + details.append(el('p','',metrics.join(' · ')||'Conditions actuelles'));now.append(badge,details);pane.append(now); + const heading = text => pane.append(el('h2','weather-section-title',text)); + heading('Prévisions pour les prochains jours'); + if(data.daily?.length){ + const cards=el('div','weather-days'); + data.daily.slice(0,7).forEach(day=>{ + const d=new Date(day.datetime); + const box=el('article','weather-day'); + box.append(el('p','weather-day-name',d.toLocaleDateString('fr-FR',{weekday:'short',day:'numeric',month:'short'}))); + box.append(el('div','weather-day-icon',weatherIcon(day.condition))); + box.append(el('p','weather-day-condition',day.description)); + box.append(el('p','weather-day-temp',tempText(day.temperature,data.temperature_unit))); + if(day.templow!==null)box.append(el('p','weather-day-low','Min '+tempText(day.templow,data.temperature_unit))); + if(day.precipitation_probability!==null)box.append(el('p','weather-rain','Pluie '+day.precipitation_probability+' %')); + cards.append(box); + });pane.append(cards); + }else pane.append(el('p','view-empty','Prévisions journalières non disponibles.')); + heading('Prochaines heures'); + if(data.hourly?.length){ + const list=el('div','weather-hours'), nowMs=Date.now(); + data.hourly.filter(x=>new Date(x.datetime).getTime()>=nowMs-3600000).slice(0,12).forEach(h=>{ + const box=el('div','weather-hour'); + box.append(el('span','',new Date(h.datetime).toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'}))); + box.append(el('span','weather-hour-icon',weatherIcon(h.condition))); + box.append(el('strong','',tempText(h.temperature,data.temperature_unit))); + if(h.precipitation_probability!==null)box.append(el('span','weather-rain',h.precipitation_probability+' %')); + list.append(box); + });pane.append(list); + }else pane.append(el('p','view-empty','Prévisions horaires non disponibles.')); + if(data.errors?.length)pane.append(el('p','view-footer','Certaines prévisions ne sont pas fournies par cette source météo.')); + } + async function openWeather(){ + try{render(await api('/api/ha/weather'))} + catch(err){const {pane}=wrap('Météo');showError(err,pane)} + } + + async function goHome(){ + try{await post('/api/ui/home',{})}catch(err){render({type:'home'})} + } + async function openCalendar(){ + const d=atDay(new Date());try{await loadCalendar(d,dayShift(d,7),'days')}catch(err){const {pane}=wrap('Agenda');showError(err,pane)} + } + async function openTemperatures(){ + try{render({type:'temperatures',...await api('/api/ha/temperatures')})}catch(err){const {pane}=wrap('Températures');showError(err,pane)} + } + async function openCameras(){ + try{render({type:'cameras',...await api('/api/ha/cameras')})}catch(err){const {pane}=wrap('Caméras');showError(err,pane)} + } + document.getElementById('viewHome')?.addEventListener('click',goHome); + document.getElementById('viewCalendar')?.addEventListener('click',openCalendar); + document.getElementById('viewTemperatures')?.addEventListener('click',openTemperatures); + document.getElementById('viewWeather')?.addEventListener('click',openWeather); + document.getElementById('viewCameras')?.addEventListener('click',openCameras); + const socket=window.alfredSocket; + socket?.on('calendar_changed',()=>{if(current?.type==='calendar') loadCalendar(current.start,current.end,current.view,getSelected()).catch(console.warn)}); + window.AlfredViews={render,canRender,openCalendar,openTemperatures,openCameras,openWeather,goHome}; +})(); diff --git a/alfred/app.backup.weather_outfits/static/js/alfred.js b/alfred/app.backup.weather_outfits/static/js/alfred.js new file mode 100644 index 0000000..b143532 --- /dev/null +++ b/alfred/app.backup.weather_outfits/static/js/alfred.js @@ -0,0 +1,126 @@ +/* Interface principale Alfred : statut, scène, onde et transcription Vosk. */ +(() => { + 'use strict'; + const socket = (typeof io === 'function') ? io() : null; + window.alfredSocket = socket; + + const content = document.getElementById('content'); + const modeEl = document.getElementById('mode'); + const statusDot = document.getElementById('statusDot'); + const transcriptText = document.getElementById('transcriptText'); + const transcriptState = document.getElementById('transcriptState'); + const transcriptPanel = document.querySelector('.transcript-panel'); + const canvas = document.getElementById('waveCanvas'); + const ctx = canvas.getContext('2d'); + + let mode = 'idle', level = 0.02, phase = 0, previousContent = ''; + const labels = { + idle:'En attente', waiting:'En attente', listening:'À l’écoute', + thinking:'Réflexion en cours', speaking:'Alfred parle', error:'Erreur' + }; + + function resize() { + const dpr = window.devicePixelRatio || 1; + canvas.width = Math.round(canvas.clientWidth * dpr); + canvas.height = Math.round(canvas.clientHeight * dpr); + } + window.addEventListener('resize', resize); + resize(); + function draw() { + const dpr = window.devicePixelRatio || 1; + phase += 0.035; + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.strokeStyle = 'rgba(232,241,243,.76)'; + ctx.lineWidth = 1.6 * dpr; + ctx.beginPath(); + const mid = canvas.height / 2; + const amp = (mode === 'idle' ? 1.5 : mode === 'thinking' ? 7 : Math.max(5, level * 45)) * dpr; + for (let x = 0; x < canvas.width; x += 4 * dpr) { + const y = mid + Math.sin(x * 0.018 + phase * 4) * Math.sin(x * 0.006 + phase) * amp * + (0.35 + 0.65 * Math.sin(x / canvas.width * Math.PI)); + if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); + } + ctx.stroke(); + window.requestAnimationFrame(draw); + } + window.requestAnimationFrame(draw); + + function makeText(tag, value, className) { + const el = document.createElement(tag); + if (className) el.className = className; + el.textContent = String(value ?? ''); + return el; + } + + function render(data) { + if (!data || typeof data !== 'object') return; + const key = JSON.stringify(data); + if (key === previousContent) return; + previousContent = key; + if (window.AlfredViews && window.AlfredViews.canRender(data)) { + window.AlfredViews.render(data); + return; + } + content.classList.remove('alfred-view'); + const fragment = document.createDocumentFragment(); + if (data.type === 'image' && typeof data.url === 'string') { + // Refuser les URL javascript: et les schémas non prévus. + if (/^(https?:\/\/|\/[^/])/i.test(data.url)) { + const img = document.createElement('img'); + img.className = 'hero'; img.src = data.url; img.alt = String(data.alt || 'Image affichée par Alfred'); + fragment.appendChild(img); + } + if (data.caption) fragment.appendChild(makeText('p', data.caption)); + } else if (data.type === 'cards' && Array.isArray(data.items)) { + const cards = document.createElement('div'); cards.className = 'cards'; + data.items.forEach(item => { + const card = makeText('article', '', 'card'); + card.append(makeText('h2', item.title || ''), makeText('p', item.text || '')); + cards.appendChild(card); + }); + fragment.appendChild(cards); + } else { + if (data.title) fragment.appendChild(makeText('h1', data.title)); + fragment.appendChild(makeText('p', data.text || '')); + } + content.replaceChildren(fragment); + } + + function updateTranscript(s) { + // Une phrase finale reste visible lors du retour en veille. + const finalText = String(s.transcript || '').trim(); + const partial = String(s.partial || '').trim(); + if (finalText) { + transcriptText.textContent = finalText; + transcriptText.classList.remove('is-placeholder'); + transcriptState.textContent = 'Reconnu'; + } else if (s.mode === 'listening') { + transcriptText.textContent = partial || 'Je vous écoute…'; + transcriptText.classList.toggle('is-placeholder', !partial); + transcriptState.textContent = partial ? 'En cours' : 'Écoute'; + } else if (s.mode === 'error') { + transcriptState.textContent = 'Erreur'; + } + transcriptPanel.dataset.active = String(s.mode === 'listening'); + } + + function updateState(s) { + if (!s || typeof s !== 'object') return; + mode = String(s.mode || 'idle').toLowerCase(); + modeEl.textContent = labels[mode] || mode; + modeEl.dataset.mode = mode; + statusDot.dataset.mode = mode; + updateTranscript(s); + if (s.content) render(s.content); + } + + if (socket) { + socket.on('alfred_state', updateState); + socket.on('alfred_render', render); + socket.on('audio_level', data => { level = Math.max(0, Math.min(1, Number(data.level) || 0)); }); + socket.on('connect', () => { + fetch('/api/state', {cache:'no-store'}).then(r => r.json()).then(updateState).catch(() => {}); + }); + } + fetch('/api/state', {cache:'no-store'}).then(r => r.json()).then(updateState).catch(() => {}); +})(); diff --git a/alfred/app.backup.weather_outfits/templates/configuration.html b/alfred/app.backup.weather_outfits/templates/configuration.html new file mode 100644 index 0000000..6746c73 --- /dev/null +++ b/alfred/app.backup.weather_outfits/templates/configuration.html @@ -0,0 +1,130 @@ + + + + + + Alfred — Configuration audio + + + +
+
+

Configuration

+

Alfred · Paramètres audio

+
+
+
+
+ + 50 % +
+ +
+
+
+ + + Couper le haut-parleur + + +
+

Désactive les réponses vocales d’Alfred.

+
+
+
+ + + Couper le microphone + + +
+

Suspend l’écoute et la détection du mot « Alfred ».

+
+
+

+
+ + + diff --git a/alfred/app.backup.weather_outfits/templates/index.html b/alfred/app.backup.weather_outfits/templates/index.html new file mode 100644 index 0000000..54bd6bc --- /dev/null +++ b/alfred/app.backup.weather_outfits/templates/index.html @@ -0,0 +1,133 @@ + + + + + + + {{ name }} + + + + + + +
+
+ +
+ + En attente +
+ +
+ + + + + + + + +
+
+ + + + +
+

Bonjour.

À votre écoute.

+
+ + + + + + + + +
+ + + + + + + + diff --git a/alfred/app.backup.weather_outfits/templates/index.html.1 b/alfred/app.backup.weather_outfits/templates/index.html.1 new file mode 100644 index 0000000..8b35c18 --- /dev/null +++ b/alfred/app.backup.weather_outfits/templates/index.html.1 @@ -0,0 +1,31 @@ + + + + + + {{name}} + + + + + +
+
{{name}}repos
+

Bonjour.

À votre écoute.

+
+
+ + + + 70 % + + + + — + +
+
+ + + + diff --git a/alfred/app.backup.weather_outfits/templates/index.html.ori b/alfred/app.backup.weather_outfits/templates/index.html.ori new file mode 100644 index 0000000..d0b38fd --- /dev/null +++ b/alfred/app.backup.weather_outfits/templates/index.html.ori @@ -0,0 +1,2 @@ +{{name}} +
{{name}}repos

Bonjour.

À votre écoute.

diff --git a/alfred/app.backup.weather_outfits/test.py b/alfred/app.backup.weather_outfits/test.py new file mode 100644 index 0000000..95385a2 --- /dev/null +++ b/alfred/app.backup.weather_outfits/test.py @@ -0,0 +1,19 @@ +python - <<'PY' +import sounddevice as sd +import soundfile as sf + +print(sd.query_devices()) +print("Microphone utilisé :", sd.default.device) + +print("Parle pendant 8 secondes...") +audio = sd.rec( + int(8 * 16000), + samplerate=16000, + channels=1, + dtype="int16" +) +sd.wait() + +sf.write("/tmp/alfred-micro.wav", audio, 16000) +print("Enregistrement terminé : /tmp/alfred-micro.wav") +PY diff --git a/alfred/app/__pycache__/app.cpython-313.pyc b/alfred/app/__pycache__/app.cpython-313.pyc index 09d2b8b735370c3ad499b9f4f1f66ea5d451124b..6b7ea880a5b10d5e1ac756f53f7f0b748b2e96bc 100644 GIT binary patch literal 32058 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 delta 4595 zcmZu!YiwM_6`uRp_ak=v+Fh^h^*d|F&eO3S+esWqQ|!bcd%Y217u@ao;;ggY-OOD- zLIX}v#Y><#8dC8H5L$jD1qmvsfT|Br)k4)m9miGZZ9@2gsQRN-AW?r%Rq8o&bJt6n zD|vS2eCM1wb6zw4#W%vK`@Ppa9w&kCQPW|xD?N>^a(q54Ul+{X^Qm#}em7SterT98!S{)Z6J?w*)pq19ZwBMN4 z#mgf*aJmId?pOQ)GAYn{B@iIAK^ai0`MOaF!n!G50$VgGH36d3!Y0jee`GHnqPosB zLkR^6E}*S|@4p4FFT%qG`G$ggV-em~z?%wqa}nNN(7%GW6ya+Ncxyp^O%dK<;%rQE zvOdXe=`8HnUXWN@B(ctvsO~7>okjTi0=}+*uP?$kn0V`k0=}^b-x&8Po3`FVNa9g~ zDe?e2(Ou!$taNpWMuJ3AtbR?vt8D3mb)p5B$|I8)J$9|kVJm@kb7Trwg28wok~L(C z$GG3rTGp-fm|EjKJR$jR;a+8{NiW#wQu=!35Sb7rgz&b1pu&-`%>H0+WB;{3!oF~P zDfrkQoh^OIn|aK)gpwmJ_`CiH45vwytl?y^+d*IzySXAzl&yrYtX9VkxjgKv#@dLu z0-CnK?j$UY&>)EE?nL?ls*aCo`9xj~%eqAw9UR@ITc>m5K+x@4HhEahr?R@NPNni; zkp@|xdrJ=*;D;w3{XL*qD}hS4upQMM8+|y%vi@oYxb zC3P;TVR40wkJqN|EeuteQv>Dq+A{#n~3-piMPYX=2VQhIXc%MfAvZw1G7Rp$O z#*upIlb~fJOg^c%0OgYw3FqO#Rr`7*_CdPxt+yKkf^d-ixvB1;7c|mg99BA*yh>CdRq9!14J6#jXe4rnu|rHb{)w#9@^l24ITqZayf4*(=RzFE(w8 zDLLW1^po6x^TX|s@H|B@;+WWJGqI5qYmg5b+uqXd2_uilmC}AT+cFcfA_E$(yTQsa zNRT|Fvv!_P%HhK>ZLpU73VJ{52 ztmU&g-Ev1VttKeljU+3UkvdK%k*VN*xX@&{#}c}fO^@qRMxCYi0fQ#PfQby}JJaT~ z%)|01d#^2Jx$B9$o*rhc?R6ICDd#h8Hr#$j(uH++WR~ns^i}~UI9bjlrc|hT)I%q5 zIE2Fy91Op~Ot+*mT^dhR+Ybws(0)IerC{`8`GG~x0e*-iFcY?^r&M@}edcZyV={Z`aF8wJ z?d4`yhI_(}T==mM?kJyEq}#nWV%VWH|xW(HJ$ld+MJBB_DoDBXn%>U!$3*$}lOx2~*`lZtPbMl4K zj%B&?ZTmZ(pLyQR{3*J;URjPO7uD3_WM*+Zdoi9{jOMr#*vY;CJJaG2j)uH6_$!r-vna$4)iD18zjek0JGIC~0)u(jxvJhI{w0V1tbD=IeEiN=r_UX{(AxdW zL%*4NZ|b9ci}CL*Z%Zss9$D087w3*G(uXciKD?NG*f?`*?!72C6CYy~lhZKMVssiT zX(J*Lobg({B^av`xzt!rD^Z0|JsYGJzCugiHljjwZI1AP%dT{^02P$(Jp^XZX!?nz++=e!lB(9pBS{^o1&!yu zMlUtsdBD*vvspS$-6&UCkUON(DODRwrc)}sXRr&ve-t;B8dt$8RCgBwHn<{FG@H@w zV`JD}$HsIik;@rXk42%&d!Wbjc2A=yHiSgyd@7U5rxNMZgU0L0$WeTZ8221cE}p1( zjp=eO3y&o1_`EUm-pKm}uOObMJkxlZ@F??m@CL72(ph{R4WslVYLn>n-5FiN*V-+q zYww@Rj!&o6ZS);r#AdJk04nL4BnZM)Qht?ey-2oRA&pl^?Nze-0@;0qbY3AHKw-a{ z%dq~VP31RaQ5g8zPK0}f8}g7KZ2vkgGIn%CXk+(2*zn!Ik^|QzB36A$4ty%ekNHmc q9$$M=sQOA2gd+kXzH5B?`Ax9nqToYto`Ze_=LG;+|FLiQ#r{79-}wCi diff --git a/alfred/app/app.py b/alfred/app/app.py index 34e7d75..b7f447b 100644 --- a/alfred/app/app.py +++ b/alfred/app/app.py @@ -1,4 +1,6 @@ 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 @@ -7,6 +9,8 @@ from services.wakeword import WakeWordListener from services.hermes_client import ask_hermes from services import audio_settings # ALFRED_AUDIO_SETTINGS_V1 from services.camera import video_stream +from services import ha_views, alfred_views_controller +from services import camera_settings, ha_weather, weather_outfits load_dotenv() @@ -25,6 +29,8 @@ conversation_lock=threading.Lock() mic_stop_event=threading.Event() audio_settings.initialize() audio_settings.update_settings({"mic_gain": 30}) +# Recharge les réglages V4L2 enregistrés, sans interrompre Alfred si la webcam est absente. +camera_settings.restore_saved_settings() def emit_state(mode=None, **extra): with STATE_LOCK: @@ -73,6 +79,28 @@ def conversation(score=1.0): content={"type": "text", "text": r.text} ) + # Vue dynamique : commandes déterministes exécutées sans faire + # dépendre l'affichage des capacités ou sessions Hermes. + visual = None + try: + visual = alfred_views_controller.route(r.text, (STATE.get('content') or {}).get('type')) + except (ha_views.HAError, ValueError) as exc: + print(f"[ALFRED] Vue indisponible : {exc}", flush=True) + visual = ({"type":"text", "text":str(exc)}, str(exc)) + + if visual is not None: + content, speech = visual + with STATE_LOCK: + STATE["content"] = content + socketio.emit("alfred_render", content) + 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) + emit_state("idle", partial="") + return + print("[ALFRED] Envoi à Hermes", flush=True) # ComfyUI publie l'image pendant cet appel Hermes. with STATE_LOCK: @@ -154,6 +182,41 @@ def set_audio_settings(): socketio.emit('audio_settings', new) return jsonify(new) +@app.get('/api/camera/settings') +def get_camera_settings(): + try: + return jsonify(camera_settings.get_settings()) + except camera_settings.CameraSettingsError as exc: + return jsonify({'error': str(exc)}), 503 + +@app.post('/api/camera/settings') +def set_camera_settings(): + # Même garde-fou que les commandes calendrier existantes. + if not _same_origin_json(): + return jsonify({'error': 'Requête non autorisée.'}), 403 + data = request.get_json(silent=True) + if not isinstance(data, dict): + return jsonify({'error': 'JSON invalide.'}), 400 + try: + result = camera_settings.update_setting(data.get('control'), data.get('value')) + socketio.emit('camera_settings', result) + return jsonify(result) + except ValueError as exc: + return jsonify({'error': str(exc)}), 400 + except camera_settings.CameraSettingsError as exc: + return jsonify({'error': str(exc)}), 503 + +@app.post('/api/camera/reset') +def reset_camera_settings(): + if not _same_origin_json(): + return jsonify({'error': 'Requête non autorisée.'}), 403 + try: + result = camera_settings.reset_defaults() + socketio.emit('camera_settings', result) + return jsonify(result) + except camera_settings.CameraSettingsError as exc: + return jsonify({'error': str(exc)}), 503 + @app.get('/api/camera/stream') def camera_stream(): # Flux MJPEG à la demande ; ne démarre jamais la webcam en arrière-plan. @@ -208,6 +271,139 @@ def render_payload(): STATE["content"] = data socketio.emit("alfred_render", data) return jsonify({"ok": True, "image_revision": IMAGE_REVISION}) +# --- Agenda / capteurs / caméras Home Assistant --- +def _calendar_range(): + tz = ZoneInfo(os.getenv('ALFRED_TIMEZONE', 'Europe/Paris')) + now = datetime.now(tz) + def parse(name, default): + raw = request.args.get(name) + if not raw:return default + value = datetime.fromisoformat(raw.replace('Z','+00:00')) + if value.tzinfo is None: value = value.replace(tzinfo=tz) + return value.astimezone(tz) + start = parse('start',now.replace(hour=0,minute=0,second=0,microsecond=0)) + end = parse('end',start+timedelta(days=7)) + return start,end + +def _json_error(exc): + code = 400 if isinstance(exc,ValueError) else 502 + return jsonify({'error':str(exc)}),code + +def _writes_enabled(): + return os.getenv('ALFRED_ALLOW_CALENDAR_WRITES','false').lower() in ('1','true','yes') + +def _same_origin_json(): + # POSTs require a custom header and same-origin browser requests. + if request.headers.get('X-Alfred-Request') != '1':return False + origin=request.headers.get('Origin') + if origin and origin.rstrip('/') != request.host_url.rstrip('/'):return False + return True + +@app.get('/api/calendar/calendars') +def calendars_list(): + try:return jsonify({'calendars':ha_views.list_calendars(),'writes_enabled':_writes_enabled()}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/calendar/events') +def calendar_events(): + try: + start,end=_calendar_range() + selected=request.args.getlist('calendar_id') + return jsonify(ha_views.get_events(start,end,selected or None)) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.post('/api/calendar/create') +def calendar_create(): + if not _same_origin_json():return jsonify({'error':'Requête non autorisée.'}),403 + if not _writes_enabled():return jsonify({'error':'Écriture calendrier désactivée (ALFRED_ALLOW_CALENDAR_WRITES).'}),403 + data=request.get_json(silent=True) + if not isinstance(data,dict):return jsonify({'error':'JSON invalide.'}),400 + try: + tz=ZoneInfo(os.getenv('ALFRED_TIMEZONE','Europe/Paris')) + def read_dt(key): + dt=datetime.fromisoformat(str(data[key]).replace('Z','+00:00')) + return dt.replace(tzinfo=tz) if dt.tzinfo is None else dt.astimezone(tz) + result=ha_views.create_event(str(data.get('calendar_id','')),str(data.get('title','')), + read_dt('start'),read_dt('end'),str(data.get('description') or ''), + str(data.get('location') or ''),bool(data.get('all_day',False))) + from services import alfred_intents + alfred_intents.clear_create() + socketio.emit('calendar_changed', {'type':'created'}) + return jsonify(result) + except (ha_views.HAError,ValueError,KeyError) as exc:return _json_error(exc) + +@app.post('/api/calendar/delete') +def calendar_delete(): + if not _same_origin_json():return jsonify({'error':'Requête non autorisée.'}),403 + if not _writes_enabled():return jsonify({'error':'Écriture calendrier désactivée.'}),403 + data=request.get_json(silent=True) + if not isinstance(data,dict):return jsonify({'error':'JSON invalide.'}),400 + try: + eid=str(data.get('calendar_id',''));uid=str(data.get('uid','')) + start=datetime.fromisoformat(str(data['start'])) + end=datetime.fromisoformat(str(data['end'])) + if start.tzinfo is None or end.tzinfo is None:raise ValueError('Date avec fuseau requise.') + matches=ha_views.get_events(start-timedelta(days=1),end+timedelta(days=1),[eid])['events'] + rid=str(data.get('recurrence_id') or '') + if not any(e['id']==uid and e['start']==start.isoformat() and e['recurrence_id']==rid for e in matches): + return jsonify({'error':'Événement introuvable ou modifié ; actualise le planning.'}),409 + result=ha_views.delete_event(eid,uid,rid or None) + from services import alfred_intents + alfred_intents.clear_delete() + socketio.emit('calendar_changed',{'type':'deleted'}) + return jsonify(result) + except (ha_views.HAError,ValueError,KeyError) as exc:return _json_error(exc) + +@app.get('/api/ha/weather') +def ha_weather_data(): + # Read-only route, token always stays on the Alfred server. + try: + q = request.args.get('q', '') + return jsonify(ha_weather.get_weather(q)) + except (ha_views.HAError, ValueError) as exc: + return _json_error(exc) + +@app.get('/api/ha/weather/outfits') +def ha_weather_outfits_data(): + try: + q = request.args.get('q', '') + refresh = request.args.get('refresh_illustration', '').lower() in ('1','true','yes','on') + return jsonify(weather_outfits.build_payload(q=q, date=request.args.get('date', ''), label=request.args.get('label', ''), refresh=refresh)) + except (ha_views.HAError, ValueError, RuntimeError) as exc: + return _json_error(exc) + +@app.get('/api/ha/temperatures') +def ha_temperatures(): + try:return jsonify({'items':ha_views.get_temperatures()}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/ha/temperature/history') +def ha_temperature_history(): + try: + eid=request.args.get('entity_id','') + hours=int(request.args.get('hours','24')) + return jsonify({'items':ha_views.temperature_history(eid,hours)}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/ha/cameras') +def ha_cameras(): + try:return jsonify({'items':ha_views.list_cameras()}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/ha/camera//snapshot') +def ha_camera_snapshot(entity_id): + try: + binary,mime=ha_views.camera_snapshot(entity_id) + return Response(binary,mimetype=mime,headers={'Cache-Control':'no-store','X-Content-Type-Options':'nosniff'}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.post('/api/ui/home') +def ui_home(): + with STATE_LOCK: + STATE['content']={'type':'home','text':'À ton écoute.'} + socketio.emit('alfred_render',STATE['content']) + 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") diff --git a/alfred/app/cache/weather_outfit_cache.json b/alfred/app/cache/weather_outfit_cache.json new file mode 100644 index 0000000..197e8c1 --- /dev/null +++ b/alfred/app/cache/weather_outfit_cache.json @@ -0,0 +1,195 @@ +{ + "fd153f67af106d2b88ea4e83adbcdf9acef9c76f43458bd3399b01344439c872": { + "url": "http://192.168.1.13:8188/view?filename=ComfyUI_00296_.png&subfolder=&type=output", + "filename": "ComfyUI_00296_.png", + "prompt_id": "384abef2-fd03-43a8-8e79-d9badd2d0e48", + "prompt": "Crée une illustration verticale propre pour un tableau météo d’un assistant domestique. Montre en pied deux personnages côte à côte, style illustration moderne semi-réaliste douce, lisible, familiale, fond simple et discret. À gauche : une fille de 8 ans. À droite : un homme adulte de 42 ans. Contexte météo pour aujourd’hui: pluie, température maximale 13.0°C, température minimale 10.1°C, probabilité de pluie 100.0%, vent 35.7 km/h. Habille les personnages selon les recommandations suivantes. Fille 8 ans: vêtements veste légère, pantalon, baskets, chaussures fermées; accessoires imperméable, parapluie, coupe-vent. Homme 42 ans: vêtements veste mi-saison, pull fin, jean, chaussures fermées; accessoires parapluie, coupe-vent. Aucun texte dans l’image. Montrer clairement les tenues et accessoires conseillés. Composition propre, adaptée à une interface d’assistant, haute qualité.", + "cached_at": 1792965507.1873505, + "from_cache": false + }, + "5ca9c2930c4ae2e10a726605a9219539a17d8f14da6556e690ab6101ad7646ab": { + "url": "http://192.168.1.13:8188/view?filename=ComfyUI_00298_.png&subfolder=&type=output", + "filename": "ComfyUI_00298_.png", + "prompt_id": "8355af62-71d5-480c-bf61-bcaf4c4ca084", + "prompt": "Professional clean flat 2D digital illustration, like a high quality family clothing lookbook, square composition, TWO human characters EXACTLY, side by side, full body, comfortable spacing, simple plain studio background, no text, no captions, no logos. LEFT CHARACTER: one little GIRL, 8 YEARS OLD, unmistakably a primary school CHILD, shorter and much smaller than the adult, innocent age-appropriate appearance, child body proportions, simple pleasant face, standing naturally. RIGHT CHARACTER: one ADULT MAN, AGE 42, unmistakably a mature masculine man, adult height and mature adult male body proportions, short hair, subtly defined adult male jaw, neatly trimmed beard optional, clearly NOT a teenager, NOT a woman. Precisely two people: ONE small child girl and ONE adult man. Symmetrical well-defined faces, two natural eyes per face, normal hands, five fingers per hand, no distorted features, tidy linework, modest friendly cartoon styling instead of photorealism. Weather context for 2026-10-26: pluie, maximum temperature 13.4 Celsius, minimum temperature 9.4 Celsius, precipitation probability 100.0%, wind speed 28.1 km/h. Outfits must match the following French clothing specifications. LEFT child GIRL (8): clothing: veste légère, pantalon, baskets, chaussures fermées; accessories: imperméable, parapluie, coupe-vent. RIGHT ADULT MAN (42): clothing: veste mi-saison, pull fin, jean, chaussures fermées; accessories: parapluie, coupe-vent. Show clothing clearly, accurate head-to-toe outfits. Exactly one child and one adult male.", + "cached_at": 1792966964.8393974, + "from_cache": false + }, + "1b7492941fb642439e00c1e4a2d1124dad2d9d27e9f8573ae372334b3f250b64": { + "url": "http://192.168.1.13:8188/view?filename=ComfyUI_00299_.png&subfolder=&type=output", + "filename": "ComfyUI_00299_.png", + "prompt_id": "1bb8236f-14dd-46ef-8477-22cf2ef1f6eb", + "prompt": "Professional clean flat 2D digital illustration, like a high quality family clothing lookbook, square composition, TWO human characters EXACTLY, side by side, full body, comfortable spacing, simple plain studio background, no text, no captions, no logos. LEFT CHARACTER: one little GIRL, 8 YEARS OLD, unmistakably a primary school CHILD, shorter and much smaller than the adult, innocent age-appropriate appearance, child body proportions, simple pleasant face, standing naturally. RIGHT CHARACTER: one ADULT MAN, AGE 42, unmistakably a mature masculine man, adult height and mature adult male body proportions, short hair, subtly defined adult male jaw, neatly trimmed beard optional, clearly NOT a teenager, NOT a woman. Precisely two people: ONE small child girl and ONE adult man. Symmetrical well-defined faces, two natural eyes per face, normal hands, five fingers per hand, no distorted features, tidy linework, modest friendly cartoon styling instead of photorealism. Weather context for 2026-10-28: pluie, maximum temperature 14.0 Celsius, minimum temperature 9.7 Celsius, precipitation probability 30.0%, wind speed 18.6 km/h. Outfits must match the following French clothing specifications. LEFT child GIRL (8): clothing: sweat ou gilet, pantalon léger, baskets, chaussures fermées; accessories: imperméable, parapluie. RIGHT ADULT MAN (42): clothing: veste légère, haut confortable, jean ou chino, baskets, chaussures fermées; accessories: parapluie. Show clothing clearly, accurate head-to-toe outfits. Exactly one child and one adult male.", + "cached_at": 1792966987.7316265, + "from_cache": false + }, + "8c852151bb6532f9e82a1eff1be4c8046c3e7f7e150415878e62c4026f1cc943": { + "url": "http://192.168.1.13:8188/view?filename=ComfyUI_00300_.png&subfolder=&type=output", + "filename": "ComfyUI_00300_.png", + "prompt_id": "f32838b4-07d6-4e33-b400-97f0b65ce964", + "prompt": "Professional clean flat 2D digital illustration, like a high quality family clothing lookbook, square composition, TWO human characters EXACTLY, side by side, full body, comfortable spacing, simple plain studio background, no text, no captions, no logos. LEFT CHARACTER: one little GIRL, 8 YEARS OLD, unmistakably a primary school CHILD, shorter and much smaller than the adult, innocent age-appropriate appearance, child body proportions, simple pleasant face, standing naturally. RIGHT CHARACTER: one ADULT MAN, AGE 42, unmistakably a mature masculine man, adult height and mature adult male body proportions, short hair, subtly defined adult male jaw, neatly trimmed beard optional, clearly NOT a teenager, NOT a woman. Precisely two people: ONE small child girl and ONE adult man. Symmetrical well-defined faces, two natural eyes per face, normal hands, five fingers per hand, no distorted features, tidy linework, modest friendly cartoon styling instead of photorealism. Weather context for 2026-10-27: pluie, maximum temperature 18.7 Celsius, minimum temperature 12.1 Celsius, precipitation probability 100.0%, wind speed 23.5 km/h. Outfits must match the following French clothing specifications. LEFT child GIRL (8): clothing: sweat ou gilet, pantalon léger, baskets, chaussures fermées; accessories: imperméable, parapluie. RIGHT ADULT MAN (42): clothing: veste légère, haut confortable, jean ou chino, baskets, chaussures fermées; accessories: parapluie. Show clothing clearly, accurate head-to-toe outfits. Exactly one child and one adult male.", + "cached_at": 1792967562.554482, + "from_cache": false + }, + "0426920d0a11983e7ac1459cd87d1a58073380245ab906809991f1cdad65d0d2": { + "file": "0426920d0a11983e7ac1459cd87d1a58073380245ab906809991f1cdad65d0d2.jpg", + "prompt_ids": [ + "a7708758-2ed6-49f2-88c6-bd4066a4dcde", + "41839964-3f4e-406e-a937-9d40f9c4b615" + ], + "cached_at": 1792967868.400006 + }, + "3fc1f1e2c91374ed46944ef3b3938b8d8a9862d6055c66876b5e45af838e7e8a": { + "file": "3fc1f1e2c91374ed46944ef3b3938b8d8a9862d6055c66876b5e45af838e7e8a.jpg", + "prompt_ids": [ + "c32ee4f5-2181-4787-856b-6f2825905552", + "6209d148-7902-4ae5-8891-75bed8eff74b" + ], + "cached_at": 1792967885.8751926 + }, + "93379bdabb939699e8565f358c79b96de623fbffb33153aeaf847ad9b339631a": { + "file": "93379bdabb939699e8565f358c79b96de623fbffb33153aeaf847ad9b339631a.jpg", + "prompt_ids": [ + "eb4120f7-759e-4f42-98ef-7485184fb8ba", + "bf109e53-ee70-441a-8736-5613a2b1ac84" + ], + "cached_at": 1792967954.7709117 + }, + "6f4194e5dbd0d9c49cf5f520d02f229120608bb1f20ed4f610ab128cd4e5eea3": { + "file": "6f4194e5dbd0d9c49cf5f520d02f229120608bb1f20ed4f610ab128cd4e5eea3.jpg", + "prompt_ids": [ + "d292b5da-42fd-4fff-8445-5452de6390eb", + "e7e5b04d-41f1-4312-a450-e1b275d96115" + ], + "reviews": { + "child": { + "score": 7.0, + "ok": true, + "issues": [ + "review_fallback:'1'" + ] + }, + "adult": { + "score": 7.0, + "ok": true, + "issues": [ + "review_fallback:'1'" + ] + } + }, + "cached_at": 1792968490.5675747, + "focus_label": "aujourd’hui" + }, + "498692dd8981e4e5fa457d9fbab7d5666f38125afbb0812d7e72fac08ce24ad9": { + "file": "498692dd8981e4e5fa457d9fbab7d5666f38125afbb0812d7e72fac08ce24ad9.jpg", + "prompt_ids": [ + "01d1067a-1f4c-4772-987d-7e0d08f6dbcc", + "03b6f551-7694-4440-900c-01a38df696f1" + ], + "reviews": { + "child": { + "score": 7.0, + "ok": true, + "issues": [ + "review_fallback:'1'" + ] + }, + "adult": { + "score": 7.0, + "ok": true, + "issues": [ + "review_fallback:'1'" + ] + } + }, + "cached_at": 1792968530.2779512, + "focus_label": "mardi 27 octobre" + }, + "1caff5c005736b32c2c322165e7d2d57cc2d01e7247b458ffeef179851d13611": { + "file": "1caff5c005736b32c2c322165e7d2d57cc2d01e7247b458ffeef179851d13611.jpg", + "prompt_ids": [ + "980236c9-b301-495f-b789-1933ad1c4969", + "4a97dd41-d516-467a-bcc3-f2d359139b61" + ], + "reviews": { + "child": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + }, + "adult": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + } + }, + "cached_at": 1792969350.0963697, + "focus_label": "aujourd’hui" + }, + "ebb79b8e11936267b3477c5d930536fc61cb226a4f357c09c21db8192ca134ff": { + "file": "ebb79b8e11936267b3477c5d930536fc61cb226a4f357c09c21db8192ca134ff.jpg", + "prompt_ids": [ + "39118811-54b6-4be8-a9c6-ec4662074bb6", + "982dab51-e2ed-43e1-8ea3-00f2bf02c87f" + ], + "reviews": { + "child": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + }, + "adult": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + } + }, + "cached_at": 1792969363.4898813, + "focus_label": "mardi 27 octobre" + }, + "a4928b9950baed6aad15a8e69c6c09bb6b068eba6f2e78e1e126c42be66aced3": { + "file": "a4928b9950baed6aad15a8e69c6c09bb6b068eba6f2e78e1e126c42be66aced3.jpg", + "prompt_ids": [ + "e4a383a6-d6b8-4d54-944a-d4f6802d3951", + "dbec0a66-eb40-4988-b512-7a4c3503f02a" + ], + "reviews": { + "child": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + }, + "adult": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + } + }, + "cached_at": 1792969491.7701209, + "focus_label": "samedi 31 octobre" + } +} \ No newline at end of file diff --git a/alfred/app/cache/weather_outfit_images/0426920d0a11983e7ac1459cd87d1a58073380245ab906809991f1cdad65d0d2.jpg b/alfred/app/cache/weather_outfit_images/0426920d0a11983e7ac1459cd87d1a58073380245ab906809991f1cdad65d0d2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4b8cc5e4821240b828dadc909d2e41bcab6ecf27 GIT binary patch literal 77376 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 literal 0 HcmV?d00001 diff --git a/alfred/app/cache/weather_outfit_images/1caff5c005736b32c2c322165e7d2d57cc2d01e7247b458ffeef179851d13611.jpg b/alfred/app/cache/weather_outfit_images/1caff5c005736b32c2c322165e7d2d57cc2d01e7247b458ffeef179851d13611.jpg new file mode 100644 index 0000000000000000000000000000000000000000..68efc5f66338e7105fd74eb326ab83f9c7b343f6 GIT binary patch literal 25984 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 literal 0 HcmV?d00001 diff --git a/alfred/app/cache/weather_outfit_images/3fc1f1e2c91374ed46944ef3b3938b8d8a9862d6055c66876b5e45af838e7e8a.jpg b/alfred/app/cache/weather_outfit_images/3fc1f1e2c91374ed46944ef3b3938b8d8a9862d6055c66876b5e45af838e7e8a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..56203cda5befb28ec3c28ef07b9d1f6ecd37715d GIT binary patch literal 69812 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�bj?OYizDXxwK#ZWM58kXAgVILb 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 literal 0 HcmV?d00001 diff --git a/alfred/app/cache/weather_outfit_images/498692dd8981e4e5fa457d9fbab7d5666f38125afbb0812d7e72fac08ce24ad9.jpg b/alfred/app/cache/weather_outfit_images/498692dd8981e4e5fa457d9fbab7d5666f38125afbb0812d7e72fac08ce24ad9.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0fc68e8609691974b1204fba4818d38d9fca90dd GIT binary patch literal 37215 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 literal 0 HcmV?d00001 diff --git a/alfred/app/cache/weather_outfit_images/6f4194e5dbd0d9c49cf5f520d02f229120608bb1f20ed4f610ab128cd4e5eea3.jpg b/alfred/app/cache/weather_outfit_images/6f4194e5dbd0d9c49cf5f520d02f229120608bb1f20ed4f610ab128cd4e5eea3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a8274f945da548241cc6fa98c1508d14e259294c GIT binary patch literal 29011 zcmb5Vbx>SQ*Y-QOyE_C1cMlR=26uONcS(TY&fxCuK3Kw_fgr)%-8Dc0As_esyzh6O zf6l2oJ-d2p*RJXAshYWZ^>1D4@ABVm0JefGNEQGC0|3Chy#RmL08#)rSlEBAw+8=q zAs{0lz{4Y;A|W9nqobmuqoJaqVPN87V_@Q7qM>0EV&mZ96A%!fV-XP(;uGWI6X5?d z2n^iYXW$V~5D-xCG0-sZ|9`i?{Qw+f7!%lUa4_!xusASqI52;Q02FWQgopcQx&PZ> z0Wb(iaPWx8C~uFou>mk}u<)>N^THvaAi*NNeJ5BrcpL;oTpBJ3BxF2wbNqMQ?xEo+ z#ci~LlFd*84GSKRu$GyfYZO8{AfqO@WPpg5Ps-ElM!J+l!18nJ?C#sJWdDo;^WU=q z;9+19;1H4C9&+OVU}0h4-lj!>hlTrhSun70IPf%FxZDy5@7|_#r0}ul|f_y@;@^V8)BVj`cz2&b#B-of$)%LKSB|*K z&R3AcOyM@0+j*5gltX*U=A|gub_#GtJl5M-8%wgwTQqe(P-FSSOgxYYn$Gtvy{b$# z%Te|l&wLS33+hC+sT1gQTTJ@w~=*t?4Yq<=?=j5r6v zO3cq<3{Mr|b7*yEn)O*+N6u?}$%mxz%#066Wr1vpZ9O+a%GqBOtmiuo6X(0xlN66E z&*=o6_>z^$o4Nh0d`d>!1o2LdDjdFhd`X5XAK=Wjt@FPx82RjI(!UltPM<5HvwtLa za1m?_Lr8!@w9x!)bo7L*_f&0Aussfs`*f)QZ&srb_;rO$?DHS4o zW6ToXt~qEcDl--R0%Ch1cvRZpT)dB&O=OETUs&MWo)t}Vlcqf6mnZdMcf}IiSbL11 z;m5UeuX~?JDy-Wz`){}YLmfYk6J+@!`5#aKb~3Ywb+MZU!Mdj8mD&zr4djds|y{#?txVjeixwZ^7j))F*@pK5Ss{F4e8 zW`C}7rs7aY0Bmw4EyRzISI4Zqkz^hXfJ4r+mW!Pj>(wG-e%$RSsqTh@@3GQ1IG`YZ zY+1JI6ekP)1~r%r__joizFny|m;zgre3{KRw*deu{v3PmwD}A0{QZsKNA<@pF`x6T z%TM~SM07ld+`kRypD$0+g0If#O?fkCH!HV%mCCO^>2t>kGT|N|hv7@GdLc8QgveL{ zcCaM{q_N?`B{)^y`WBqcPNXEJ| zy8JXv>?^0Q(?c;#H=ux5VxH?`MtFqCi;qoKM#OKM_ z-?5HC(OZ;uUTu&pR}q0V7*a~~%y|}at6C;cu?CwJ!R_kZJT@gluiZ!I6%}jyvY;Dp zG3QuU&3~TbG;SwXiYEft+v*b0E}z##jKUdht*}Zdp5+~k)RdPiB}TXcJb~B#0(dz@ zwC1}zq|o$)_4CU>BYWNJJxf1SYJxH6b~bQ#+9AA?UhNUH3&Tv!JSIb**JV1G#56SX zKVh~x{`PF>5jyu1K0;olG&U1&zpTd0wN5n9#G_rH?L>MeD)J=MYAhOH@H{DZ%tHH| zvmyD~>^*Ss66u+5MY;{v8!n__750tTH)0_1YVW#m%=e5d@DYiNJE(Tkqv2_4@gFIg z6=*qV)q&+=Wp@CP)Hj;X&-Fc+^iAiRdQ!Shd1`ZKpmU&xmI#_^Ga~%i=QUbeZr1$i zG~l*tJHqwe(V*YxsXBXk)O_Spfd7fXac+aYnd&#rSKZ23`|tN#PahnW(+>vd&p)Fn zNw;eEvABsLTqKr>zW1;ewrEyM6P^Fw16Dd3@->Lkn0IXj!kyDeMZ z{l|2v=Nf8o`dl(ri{cnoL+_NW08Di;bO3%OA8Ac}0IK7=7DsACkSJK3f>{^D?NoIF z?p6SR=rRca>?m~E;;5TLb@?db$i*~5j?^IvxX~5N;v+lWVJ%?hRa>Zta|T0O`$V7% zvPiT@#yGBem@4Oe_IxJn#Fe)RlI}BikM?&HWXp)jy2HyQnD%oQ0E9VLRFCsxRDkKX zuV>>qFN6A8tFZydG?x1K&h|VY+6Q(!O1ovFly%)71_ru|THM6Ln|tQly*v5G7N3kk zD~w;yUpn!<9uMkY-6`7>8c%9EBYOD%yq{VFr>+pjG3^dT|E zGj+O>dFs9oA9zSP&QEg@jr`C;O9?)U*i&^S`aJqHEL^qz&|?YJ`jyLK*42scLSd|+ zA2ZD&gJA+b6q3{Pap|~Nck(5>a)j-Znzfa?`!V$E-cXl?xfSp&y~XQudOjOV8@fRn z`0)DK=CX}9<6d4vY}t`|O}p_z z!=I)03tKMBNwu4U?S$SRjfLKgc!rz4*XWK{JG%8J4tvXBg4cn`biU^L=TRwvL{@s_~}^RJ(jQ`L05 zOssHWW-j^2Xq9bMvlR=Yy<56pIBbFUR1^B1Kxfap2SNLy`^cg&Y%{Dhj2j7r{@8Km z!_??Jk0Ej@OVXhOYYaBFb-XMs$sBeXc`i!C80oS?aBTk0Vidvyn{SCgEJ1_t-|{_G zo!CY7Kk^+ewf!spOUBO+&)o+cfxpKxCxQ2!)sGuPBV?8jkgyfmlS;V;agiSV>JE383nZcR z!@@GS^#xI|S$2IX|1qX!QGrR5Ho+esko&v)Xub}!$-h8Jj$0TdbYDsW&%jBXqP2JL z0s!d!DWiGtG%OS99-YnEO=e&mRlYRkR=oo{#-C+yIb?2UEB@u$hzn#EHSFfyA~VB6K=J88~YtC4r9br zHDQNSWrgwZgJGl=z?58S(knsO!8r-%0&w#iYkrgfcb}w6I&@Q+3-<7l>P6qrlxHEY zJZ#KZ2H=&VOqtaW_;7WX6GXltHLr007oaIJGEUm-5-C7AjzU7`ED}iTEbtl^1+{cw zIR@Jd6M7GquI5)=v3fu?o@KqY^gf~bc3)tTFIJ>#a@ya^;aAVkeWaq0QcAL)+uZAV z;1KhUoQj8XI&`C2yz_bt&oEWLf-7`u+WQ)N|B->#zH0vyVslxcS@$phCE5z+a|YsJ zi^{_0bMev&HtMc9h-3DjJ(Y?!zpXCciuW0&pVM0g25z-}U6tqIv3!p+pZ!$tZb`8s z4fQL{c%W$e92Y`&;xf>0>`wKtz{B~p5pi$@912C4Q+%I6|9IjYx&_J4giC8#Up}x; zdGrr#I1-Lwh|$%-0iQ5ckyGLM9Ifkw#aDijA>T7urs39Mh_zhakns*u!qtL|G1yFE z&^=*91zaAs(?K8E0{sjvL=`;|2aC%sy*T;AcYlh0@u)0?KY`P?+wH!;uOm0=-c%GW zxh(W#ke&Os`hG(@YWN{lZNe4B71#MmHO_f7)a3Eo%D`u_0}&2R^P~kiQ{?ob8}sc$ zs<(f6u;L*ly}qiHmej|^`i)%Qu2$L7yE$lpZx#P1Z&`|j1JrDe;m|&|kz@xGk`0cy_^h$=q2jY?yAu=kw>*VZZHqx;e!5-=K z6}eSgRb)-eT_L3@9|ExZBQSt64|Aq+&-F)!jJFUL)V6t_JcC9YQ~&Hi{sKHESrD6v zi5NVRTQ#0S=9x^h7NOVCb`pWKcl9U29=(~1V(_V~-o<13nQffv7Wb8y8EX7k_Q0aw zmqcYkDkrKK$BitC(b}sOUlf0_p&8pwTu~k-+Wyp{DV?OS@CcfixJI?ZFm!0jGbqTD zWjt`%#@cil^1a!xI;F4ZCY#7^E{$t&ShoOz-3FI=v-!f<*$LYu^=iJC#lO!>@>mP| zjQ1UXJ`J)MC2v5yn)MZ&y?77|6>-uTnzmePf<41jv)}tXCpipE@3v*icNznpb9yTY(h;i?V#UplyV(v%|r%A!{ctfXUso`58;f^p9Ta z*c^0DzLDxb;i)0slV7%dIek6p0_kh=Di@!mO{?QsyeoT5&)A%FV$v5_tKvKCtVBzV zX3>o1`ae&d*`h;FWVxSA$)t`#lm_H*=gJ3n${IgdLt3|F?&by~`+h={y5l&17g@yv zd@&BU%pcevR5F;G$yM2>ICEl2R|6`!D+K%}&hx&L2`?fJu|-%}cO3$?N?ZJ8COq!B zWo%{9qQZ{cdm-N}IFcsCuwpM69kLBU` ztDUAu+lLc9Rffp0wb-=sv@Xp_xllv*zShBf%3tEsd1VQ}J&jE%SW<)*YqJkaPcyIA zrg{?yKQb$1Z9dlpSDcY;bW_OF(yx2Cbeg5hG_Tb|D2q!#-Tn}la;AgrBKs|S(JG~i zg|kUiG9Cj?1uv%#j(H^|8~3AK>si%489yFCa%^qn*@xFRwp;l+=}p%Upg z@5g4BPc}iqm}onH0eW?dFK4rn)NnCjIlfJ3>qJDjcYgr{qg4nGhIK0JZYoip)}G4w z6lmX76BkW!dZU;2W>sl0gIc?$FleUQFFx5TYp!zGx?AkCqKa^!#_Dn+L5zLy6j#h$ zf}`0*oLXh%FiG{%sy3HDw`FQgR+=|21Ddo|Rar%o($pf`Ld)d$&Ng3%%PKO)kh|9c zTWwLRNlB{KO|y(5f~~wZfZt>q_lG+6@|QpgJ4&OXiIpk)%Y5L?b%EU=e|9qdd5MVo z&oYL>R?*zqvt2C#>}b?t(7+d;wjex3%3 zK=eiA<&-$GY>|i5}17V*b}#6L2XE%CQwgdhaIut zdSmE%HPhkavxRiTVY6}Gb^{>S29A=CIIJcE2*3@(Pezi=IWgvTDmtM@F$RdvDvt@h z9R$+n0f}Q@K<*#_my%`$BS68BgW!8T%+N6F*XX;RqIa&yn7y*tK0Pw9)c{As5XU6*UM)E>d1Q=j%q&y>HD z`0vTI7v4?fD5%Ot-H$Esget3h-8;xsAJKMp2DvKH5aUZl`vrQ;tVjywHpaUL)S>c0 z`{Sll1wm>nHSv)S!NH)#5dIa5Q45TLc%7ALv%i2WCRhpdYxs`}&GF{+*9MxNvC3(q zN=?Gp{kOv=!qUeP{OssWbKlOKmf9QO_0}Ad49@-nCeg*39f#%y1`wyHZ+3>*TngZ1 z#RKzx?5;wP;Xe_i?UgTNu5NF==221&WTqF?koFlmklp?jn(872!Y7ky47;d&JJupx@*%rskle3qIshbx?qqjA%vRs%^i zDfwP*)w;~EXTIg0E!vZzx3iSa@r{05=#QP|8?BG-^|4W9PX0emFHYA`1PEcz0c%5R zpEQzEth(rGK?S)TUDRD8M+Im3b-@kYv4^oixcOgWCV?J&i4Yc35dHR#h%;QJn3_)n zWIT}i@ybgrr-ltdcDRIbvI-#Id^&eieU?!>sm_p$1Y=rd@O-}T6Jvm{n?~Byc8y*} z=yx9mH}xL`eK^~7AR4@k2^Rx(f=&_%S*NDDNj>6L&Z1OCt!bmA9v|07vAR$rsHUjL zhl`c3>#r^F{z%X}l3=#e;770V505`nIdT;LOY3$1JpdpNz?-vrk-vir!;YthMH#@3 zriN9)gO>mZxw&M$BP&88V}SF53W{UaytbrACY2b;j*RGUC#^K9zt*$~GAq_6_*y3) z+d&1YYur2itX#yZjF0s@7+3;H4&sBduOGh61k;H%E!7WzpWQa@UVXk~C~lghUE zCAOr#-if#E3g;~Fs(Oji(TQz6iCseRSzLndYa4R`X7p#7ON>#9B(y*s^2uy20e(UA zit4&VQE?fO3oG%|6T}h0X9=S{&LD$^oZ|Ea_Q>e=*$;_%S*3LR&nZX3tEv>Ue*rOH z)#jdDDd?v(@*$TVqc@OBR_yjRHxOC0efy<>Oa~pLHm)|2_@rC2h@D<8itpf(4zixw zb<~VnF~%hkzN4)*!jLJX%NNOI6EXtu-S^Sm(njT<_bPOUM1=MV=fLh&4ODZy7gFy_ zcoJRzQ{<>)7m-B>cLeB=ooJb`Jb+vJj3;QTlXt>mTs{?=f^{PQfhBc?SrK$-(8>^_1bkA z5S2E-s%=gaMOZ#MP@lFGlR61hCiD`74`fGNsWBRnCnE%3F8&@9fOGEOm06VANm*Rj zUZALmdC6fBO=JshE3owPtK(6ZH)L|QTXeCyqjEjMQZyg#FT~%U|LsoHHGmssV$~Ae z$Jy7&{e^^d?l2Bb5YF$65VO`gT1$q#t{VUhjn38wkg00o>I4z!OD=6h#Y>27Z;Kg zUL*$%v~u#(2XKWzT@gAnky3YN=S4PFT@(h>87HlS(pAVD=!|*BpdwUqzYCUxT8oTu z1t?^}B{f-oh~v9RP^ht`xAadAb5}`q!RwDqdU7wyR3&|3qn&+;qwQEwb`%DqxCE=Q z^K1aO6(|{yHvE1!s|ZnfQpgGbDFe&H`lYSoDx9>0f#{aRu_i@-Y>57VE6b(>oPdEq zz&n?$vP@o>8LQ(cM@^>t5DdzV>HUf`21^rSBX$XR#`t2gA#V;gnkeOj4hHX5Ehx(- z%I1rX^3~?(smZ*Z+8k;X?xi^&!baskQd4dzOP#RAlYTCgtH;k0%G9imlm;#s zkJRglWR%R3kl6DvkkXbUhiE@5iTyw#Q+TBx$gOJ~&G~e@!=p^4rnYv-M3lkaw%T?r zDS8%(GX4tMnboEQ#S5YKKYT^y&l6~+7EC3Grp_N&uXe8)su35K!!ha$yg?_`vmPAs z_G)KqF3(;y8l0Bu`5Jz`FS)zO27V^%n|mXucv{4Zx&`| z*7F=ru{g5dZCmSXXUJ`RkDrME$yQVe5%&fei*+Fw`1`wGBDJxb|CB-J(Foryk}^=l z3S#Oa%v?VOfv#I<53~p%Pwpnv#|IMe82pJ{E6Pxk^lSP}cp51tr?t(T3hpxMN*C}C+f0o^O9*t93+n~pm5r6Wv{<0TyK-!jI*H~N~_|> zBxDJd(X1A7Q7Dg*N8O5%>+jszuLJer7^*C5$>l2p*L>{M{QYa{G6G_;<4qlooj7NB{{I zdXyh?Xa&a_o>$C~bU%|V@(bic!27FM&g$)=%l4Bxd4gBu4mEJE_I0^qw!6D?rIeKIk zOW8HVL~~yeoSO?9pSR$k_KWF1E^|t4}YU8DlA@Ufo%3z-sgk z@a73$xDSof!f`{7{Ils!RZ`9gk|q{DcdjrL^hMjcHwirjO4{TS)v>+M9)jf0_(7QV zU8$lGDD!7*rLD?~<($uQ2(9nu$TBRcII< z|Ev9%&89q!zkqLsDA%5P=i$FH+S~P#H0oJGs-{v#1Z9v3e}c7B;(B>|jaySXMlPJE z^MagejYKyM3(f=}n2s}=H{v~8NPZ10KfZ}*62jug$$H2wH!-PW0AahzGZe%>1J zCF8F1=fU;Ahk|UnzS?Z33}BR{Z4N@{n?qex1Ag+uz4kZh-hpeHkJh+ehkYX?e*$X= zL;*W*XC_Z$U5cLG>#N`C_k?Wu=RcB=L9;Jlqc{vGI|v8`2;dJB;RD|S@4R9WY&dMN zbcHwy9atQ%oD1|WkCASSt_TT*UUdv01Okg=X456Z0$t4@pDsuTmS7J#9o@*~FVQ3v z&HU&VwzY<}_cziq_;+PG{}7Rt?IA8PC@fl~3ej7rm~qAWN-v@zrZ^%H80%J)sx1q8 zUTMxp7DbI}+eXI41paYA%5x0XB?&6EY)PS@E&*1;T?gkP@gMH(U6t*%qidtT0A=vQ2EEm(VLn@|J~Hf! z$;{w#_sYr1_MqkL4;#^ozR8O-gZaimePPLOqd1N}7ElU4L&svL#-P^s2|W-~w~vaD z%0v+sbeyIHu1=5l>0{R2y8MYgKKbFZ%@dfmWf;*5cWC^05xc}6aCrIJag=_Uz>y%+ z@fr+mtbsq8h-eT6+N^T;kZ*nnUqr(R1}aNEjjKY%b?tqqi0P+d4Fe*UOjlw>c*M6C z*$qlW{RlLpuiT6TB~LC_#w_J7R^>1lqUvfG%1B2klZz`H;}olD*=Z9R4%~Ma*kQ8= zsbY8KzCE++)aG7Y$(QP{XfPh;^3b#n=OQC*ZS-y3h5?;0qfGMJ7^s)j95>W0 z7guP#IK{w6mN)&bicdWfa1!?uNjMv+%g%1Aud%42JW!dbuzLYWw&fi$>`3y9^0^WL zVJ?aN6nHLN6mR&?P0znKAwg7J|DBfoRrVj`Z%y@v z{L`KOML5wk{~#Pv0Uk$#~f%z@?|=RfNC!ICSubVcWgQy?k(@HI|Kw(sH(0v{HRe4S@5hqAIE zNlz(;&**Nm8G(h8bv<21))phBkYBxEv#gGJaC{jV|E8};;#fPw%XwH~LQ(pcF9#LN zIWmP2O-g%oU+o?Rare5e4ADJLn|%|2(9n7SC0)T^fW@dnAP+4>Z}Y|9;2ZTk6LW!p zJ3H8Ec{`wf)E>c75 zKKN_*wbeEUAoes1aw}*ashv_MO)hG_&Dplsa8=6%$aTSIGWK#F!F6ec$wRv$QYLcZ zlaY)$n!khrj!HXHs3Z%s={)W}*HM*+GjBGElKa`Lv3EDgKsa&qvP99HvUS_?T3W=X zH>7+2&Sw91#q-xx>q7>$A=RzYo&LEo`f^mN+4=0K44;@6~VNsD=_Uj_P>jdM2Bsn&+XcF#C9Q@8N3MVvz zy%<($3vS)&$Z*{!q)Clrx;E`p8#2|2m{A&1IptrR0^BMu6!8KMso9#@d)W>B#Kt ztDRFfP9-cr3VQ=~;#{(;PwuuqP*L*~G{ZIgPO!JxQ;ly#yBdD2*V=3JMa-?iAVaPz-)5~(`A^|kH2iVHaOX0n!ICH z=5V+qBL0f4O*DJSKuJ{4^vjsn{r!AGd+`PELtr17tF@O_Z%g^nV%5jI!}GfeVqu|b z)f6oLd}!v~#_&sUsE(p#uW;ftazQQ8!1oornp3n7<07C#!N&0myO%{&s4jsh4;M*Q z*^t2o4}WNmrR`I;6{eC^4tY*Xa2CW0Cq+7 zdqx9u5BvqV|1KF@1sa=Q*oxV9jn?>&Rz3?m{Vakw64L5| z|LdjcS|#9GVz}JM{WE<=t^HqM#^nD6%phP(3cf?JEHW2V!b|u86E1-)jSYj58os20 z4Fms{+Qfzn$)`I8(5Fe^9k{nz+WUV17zb^gkO}nerfVo_0=aT@l~?9Q>xbSCh3)!K zmfr_7P%)Ji;C0%Qm45YxQq}yr%5}@y%V<^pQeC~h(*&t`%}~?{ zTr`9HmHq*pjLOFIbmU1zEYr2W0a|$Fl$|}L-GXIs=erJlrL`Z_TUNhOIO~X zUm<|Mfa+lM(*-5!qQ(y&w8@UkKEV4jI_W>!qj+{r?p^`3}}t@^;xILFgb8L09|Xi9g-hM(mMA|C$m+3u{yvS}ySrNyG~-Gd)*%gzjiqYK-r z*ut>XZYyHn;K;ZptY})gGvTY+%RIf9yYMKm=vn$NfD2AOEM|11i;e4#1qt zo8F;-u$_s2^nI*iTFYDG)En-=Z~1+0{fkM9&^f#PhANQL4uZ?ypL=^__}&3kcb4zI zt%PxvV^v`-H(>nmE|Q6z!GIRe1d$98Zav>$(Oqz-FUi9jIT#e%b!ib%%tkM?OlA+Ya!CJ8P)48#r$4=-U#25fMtXyW6dY-d$6 z#R4oe=``^H4PdKe_){xLSw15j7zAJ`%Lc+@d8NJu>fJKj@-QpJ8M4hIlkOM{{S8r= z-{T8pvre)V$>efvq7+?ZuH5(;AP7^nWE|rRgEYx5M(oa;Df7dr1M`nwzvnc8~K=T=NiLMkyVc)x*n5vMZ4UhsWw; zSaUP=*#Z@b--V@gS{gPXT_@eF*VXu*B{Up4QW->zr&;8pIo8{b@9qX0GLiaAInc#E zZ0-mI{jPjQ(I%H(_wSLb=M`>>yJN~b>o}PR48t_5@36EsA0Wp8FdADg0 zWqQ(9G37meu;2&0Do~1S9`j&B6KFe~hr%9dRqBmc<=BM@R+MPVE4f+=Jz}!PR=N_% zh`LHP+afD{vR5lxIn8(@6|!r}$WmLNIj6(90AWb=GV!XA+Du1E_f9QyB2yL`smr3f z;&o!JFSub?&!Vo%esw4*r`EF`UN9pdt}P+a%WI}B6DE)>H)x?Nrxa(8pq}D1ZDM5* zVX$@5k)@fK>A)s3gUGlaG0q4rxt7(^v);ZlTz?tUr1%zlvH#l`nUEM?|6`Z{f*bL^ zO69PFMarRE^Qz)E03Ii#-=MjTH36CK+k%q! z|6`V%`tLh@?CJl_rE2_dE>-BCTx#Y&xzr3dX@7RJ7=WxGFfsHkhbsH#kTm~C(&2Zw z-9nHVl=%PgOrF3W!%;{*Gy9u9$9sN(J%j3%>eIF3efcn}N#o}kuJwv9=?` zK1}K_fXM{6)_V^;@UR=~s4F!T&Sx}K+^qnelux+=mI*F*x;pH2GkhNVEm`bo8@N^> z+b!S{jMv@bd2SGBVO+g-?x(LSN+j1zn^Dh>28!qUQ@fwy{Tmm=O_%$n{kP*wMr0t-C}CLfmoWP)R&pXL zPg%k6?3RkP2hlXK>n779?|aoE!DsRWNkQYdqY4xI`k{)N_LmZ0(2YCof%Uy&u^R1I zoAN85_k2q$SKwVUejLS-V->mD=7Xlo2aB+whrXv8ux$IQHKZ{^lugp@wZKywuMZ|x z{vqjz?6vk(xX>};j;9`h;k=>w=`L~-Ttea_lDC*Y$Lq36)apdg2ZLVM;2Nj$mdtX9 znLxaSta&r3TnP4=iI9^z|G_SCAME)fb*uVO-6$|o80TXQrJsN0onbzZY65g1{yoVr$hX2$?{K<*oa{qfPaMQUNohy_66^Mpx{oNyA!+R=j@U@k zUT&PffCrN@)UZ>D2!AB;G|d!C!)R^QubkwxRP)TQ+s)4YxxBRfV8t*Q4_5BCfDj&+ zuTxUtU3d|Ul7fd?8X!h8ui}kAYbb#M`BxyivE49*6Yv`$iItJiVJY_lYr_D&QjGyP zMz4O;3w|;m@G`46fWnUPv&!luvr5jLE1$;?)}F8kM7>F}IPGrg`1RsQPgc>2{K?v1 z>ikWk+L>w^bmkbLm%?Q&1g0d))>fa3ElpJ`IEjDoJNlq~o@&xkvlP@eCU2<7(;gV@ zD+_rb3OVfQr6O3e9xtVZGeR@M$iu?c@&)NBwF?qRx9wf*x;j#P_3uf_LOW`5!PEG~ zbCRmwf=R_2po`*aE2B(;P$r8Ji&ACut4B7MVC>#IuXHk74tcmVeJZww{yX{NU7hR& zZ8nnSsqxn|BmV}+*q`;)mqCx^i`SeG4Ev2WV>_FinMmtAgNBE5dm#9=f}mqb5O$Yc z;%U-09jt~!+$nC7BO-HVmM3MZM$vP<_TcKS@m6|J?FmyFUD z&{f$QI5|u#QXi6)9?6g( zypTtChV5R1btN0tgCq;*yIf6U)&#M^n#o^)j0G_Z*{>CKt6M)#-58ZsXyq`bZrhiT z+$7+!{c)`AKS)(Q8 zb22tkcc{EHKPnvkgt@JqUMZTeWSEqlzCmc!017&@WrOO*e`rR7*?+~%VOUwUC$?|V zM^D3l6m`y2QK<0Ge-!n5ejfarqDFg&B9Q8*o5TWZC7cioyqa=S|dt>Yp}@YSIJ%mz1bghJ{NGRVGA?vG#I*! z)!mAA6&F|-NmvNRUi_PLf);%6Msli_JHAQh#(x1S>9EM=PJthEf-*^d)U#yM^DvQh z>sf5Aep1Ul97(|Q4+GY zM3$|5+&>vc<+f;?90d2ikueT?vm6kUmE&C=Oae4N{bF;{^Q*`UH3+0x^wc_XM^1Ni zmB`s6OhvXtatvkvOVH1y!2}MK8EsxYt(iH{@s^v>)=V(skjQ9Pk-@NEYU4+Av8kZu zm}LV8-G0zlLXpC;?|mG&g8Bm@V;X-1UuIUPg4jMDC4B8y_k(r?H#*V|({ozt1#JVC zWS1wS-a&tdeI~mza$YZ-Zqu%xBBR*ko_R_vj4_EA$NwUgVP+{?$cq*toLP8791lsr zr#3Y@cYT2d7@}6957@6%LR=apH(K-8>Fjjp+Bo~3b5{rFAtKWn6k6YMTP40$`!3H< zRz{BWhxlsy&@j~voxgu%(T5VSww|i$mKrLsm)BBRu~H96n<#Y?zD}(~lx}1^oSD z@wZ47_f1Qcz%n8p-(s!aw3Hfblm-ARfsrY=Wmu?Fu3A7bAedC}aFr}cVoVkrg7HQt z!`;)31|KJXLmUXO?rG@vlPbd;Y)=H{Xt#!}fAXGpMr1-MM%)=*hrKZ=uhiOH-8K9R zNZkrBDc+J)&ANC+`4A>jhzM;)Z{*o?O5+26S0-DB@xq0C+n~*Ij9cq;yV4m=4q+d~ zIb)8k9kgeti|kggtIT0J@yzw*fAK0%Fvt|P(EFIV^D|O>igy7?lVaOH$s)F$Nk-N& zEGJ1NKh?APZo8A^%p7`bQ`?Xuwa(JvoW_8+aMBSeDrB z=Ax9ROu2k*4UAlBscU(qlm(IjR_Yu-Y1G$sMRNU$G*-sMQRlsUpPRn6yZn;3Zq_Bs z?2>UqTGMT(f=s$%Wl2Guyiz>X{=`%_oH%E$7g|w;jtQ0%*35QW3=?qg7&!b^vulGR zp{$ON4wl#JC-uWK>k^sFx`tGmO+a`XGUU1@y*>mYL%wqQHbJ!V%7K@GVey_9a{iiR z4m*~^(fsWpMlQkcg!~K?jr-2!!k}Wx*#`MDEef$&!a1k5ALG&|@)-}DOu7a4M&4nP zldu@gs6+wfk=e3i6lSe4-V#S&10vR8*J7 z9-qg4`1m=s7|tcXgyoquN?<1NVlka+o-It@{|V1?SIT39<76p807w(Kfzm6zTVXaF znl_vUd{IfP#sYXCn)k8~Mi*qAs2^k2iTF+FE(CzIL_m@^KFz2QrVLs1spG1<*w9K| zji+&-14w96cznF*`p05|;2ELxmO=cV;vsC%o85%<&2Hj7{zf%CIdUA|)ceg^_^)_- zw1y-BC*Df@uoWp_k^hnAw;pCani>vez%1HL9218IITBt^T78kzDc!D{L_|L*c-_ske+;t9>Szm( z!#ICej7Db|*sAf2!x?i$G+Lfd#~G9%cN6{M_JX*EY2}xCt)4DsA2T=LB23&)(FIBU z1?WNNfXT&*$fTU7{b%REwvCC;LY9p+3@;%R($z5EcSww6Jx52(sw|_*jJ1sa&1&jR7fnv9}Z^;wowjk*`UYrw8F`6&YyA$R@tO{*D zYtLH*T%Fhqaq{~r?bmC=Kgp^RYqxrqQr~#wSU_a_Zr((HtnNH1`>D^<;M*GCP#E&} zPvIReI;7)sBF=8q>zK?^IGD8*dYUG(jX>opKl$jy~{lanE=) z{ww4_bPoyjrp3c^N|wKXpMVz_v6B0C7A0PE&xE$Cufgd!shnfAJR3ZlM<8-^&p#|7 z@=ZHux)pT07Kwzv>fQ=LT@n|EE-&SG@3bAu=h zzCXGUToBOht)nY~&!$TYS)y^*&qe-d*ieyOGOxWeD?0;CM4WQnm#g=wgeuwQ)w$Od zo%&_h)TGqs01$uYh#Wr4j6$t`Cn&5oA3>W>GP~JTg+;SX&GEW>TadG?Q}+4dUG~#g zrhn{X6$NXhw`f{iIe=X(#g>$ZKBY}=e7y`k*x3z^Ypcp^ zkL(9Oo@1Rh^Zu4E&m}ayc6Rt98|+uS!yzX2=MzH~J(NpGY0glif7RgBu7~Me@>ahN>Ha)yK*s>+A*PYHR) z(+?D{EE(VQWjmPc4rK7VNl+n1TJopMB(RKtWOku{_!LWwXs>F>+EAKi@9 zNit+(#iicH&+JPDT1Sh1=`&&SAJgtCFRwAk_9DO1L!VU4Z&5*sG}|b*OmO1X5|lmc zY_&A@K&$^GG>%ECcYIB}N=hu{7l?n{?;iAKgdIM1tNe-@x{6-cvF+|@IMin7LBtfJ z%6n-oVg|kipoysq^AR$E%(iA1RKT662YDXe`ri2kXsWKmzCj=jGzuVRX=x$7;O@T6Q*im+3uU)8s1!jk-t7#<;=L2S4OJ^Xpgz0s@4+) zoSLPW|B159%zY%G;KwAW+^^0XWmay^99|m0(=xSCPqN~~6Bee_UxG{;BM$!tpJ^|* z{)NxF;#yzd{DL3<^$WrmaxKkHz14IYmHt!H`K#hfhWG!}3w33(2-1sWa!O-k#nN>D zn?4MehZ&#_$Nyg+IJQdGJ7kyvFQ^b8Sb*9$mjlIyn0!Lx3!Zz0Z)?;|a^EUO^tFHq z>-4hW&NT79P`Hw{SGQv0k^j(2k-P9;fIXU`?DalFmHr5-W!WhcOSQ6y?Ie10Cy;8s z6{)Q8WVMmb;>lXnhb-%Oi?b?K)4Dt(@BK|*qCLFHLFJ-wrfK4g94jAyU%%Xb_ONYj zp$Tz|z{q^%E6K5ES#MXq+k_Cmmij#LU%)K;ahc`i64yF1M8uzBP8SGlR`6N54w(CJ zPm^fn&?{L%!hB-~jPF2pP-%b;4|U5zoSvhE9!VkWiU{K=yV`7`g_dd<9fX~sXp8UV z>RhCzAxb#Ax9F%PHn68opw7gH7w&}U8-9(}d+SX1rH&qyoQ(RYDW|j+zQ2Gw-d9Ip zB~dpoes{)1LjYetaL^df+WE<)ae|(6+0Ysx(&|pCygRg$0b@y5QEG(~ZAC538u5Xg zQ0RmNfhX_I`3sAE+fL_QR%;?Lu9IX&b$RN~AO0kkiL%UBurjqw)1P8*7F5xDgeE6Y#Ry19-?Y1&_OSNp-b?SBDddtt~e9quD> zPNzA`cFR{giQIR0eou5)9~T7-21H%_$|4^bGtBzO@Vm;2Qc@{V#&Ol)a6?KM6#%$V zV2`Xcz)&&py`&oKKG*^bC}g4+(E!?!OJ~9_f!+#$Tu>NcE>qsXir3ixY3eJ(qWa&b zmo7=^7Ix|Gl$7pHrMp`R=~#N{6eOf!m+tP6Zj^2i1OX|Z)$i|muK$bObI!h>bI)h) znOXRJusQ+*#^rk1tzJAGk-s)K(3U+I@N!4JL0vLIC6#d}*Yd+K{#{A@^2V1EVw8x) z_nWSF@ee=%tG#pR#uz)bXG2Tmhw%_<#|fIEm|nGFqE7~*mQc^4sw)SPmYvV3n3PeS zkDs-^H7%;(8D&()MOv*cV@gMB@OQ}W<%9z))OMUVUiIZofL>D~afNs@TeIj&E`2pv z>acqfB!g7MqjxJFPs3GEm%CI2gS_`w*d;G0d)itLkj`;`@YMH#7?JyKOSNc@xH<3e z#!!;t?3O!lfeL0G1x(hBjv3~+am&4Q&}5L*J=$1C*r>V`#Z_MpXKs5XBDTJmoK7$r zsQ6r71BSuUYTA$Tbk4snnXk9sk4aoZ>BVXFnt0=76xH@crjiohXby3v`b5;%iW4|8 zGIFT(c{E}%^r2bexm>>8>Ywje%j5ADz{q#7Ewytdta>R$b58P3;};@!r}^8vE)ad8 zmouYO*I3C{9*oNJ!Snq$wgHM5o$Q*hM9LWvgMY-A|K3azON7o%eA%T*e9@MFMBNlE|Vn$n;h_aFt>ZPa4jC;j}jo zAWVYto}~1cO`q^6zZS9gDBfBnKA{Zy-4t4Q<8=X=5!$g3>8gj(%do8l~vv3LJNP~|VbTsakg?ghh}KY%y=MznE$?-Co` zvwl<%W9dffzKK}W?>88_Ngm~w$zKw3JwSqzAX%RT*w|>xE{O$C&pIFP>*I%6&bNjv zyo%C+Dg!xPgL*D54G0z;Kw_=kw>RRseB|CQm^kO2e~T?89kbf@QpiUSzjQpJG|Q^- zuDUr5%x5wBKt6_OTj2eMa%okGY}`cn+bM0eLx&orXsOsoY&2PdVakWtA1$K(h+h+C z=5&Na$nUn2OxDr(9;M#S#=hp)%=sB`t1EwfJJz@rfu{DvXr1BXB|O!+m5xY$F&1+C zAeVBbRrhQD$QS#{Dur{KxarEg4dV|WWJ}lY0cvODwa^Bqwj9P-oVdbdVB+eTr4hf>5Up{@RAT?%Vy&FX)I?`{wW{V+ZiJ5nx zU;6C)YVI;0*hLrCD+AuM!qMNXkV%-@Z`s|Gb}dmsY>>$2g&mwOv)qpF zwHy{A*y2B8-}+44yxozK-UhRH;?!NLXV=?mQ6cr3@cP^`+UlA3^8-TDq+r`UW7PDU z$QkWfNhI@70ok4zGbAIx@`v=e^Y|Gx2ILB_(LR$RD`5U2251CdIyZd4C73kz?>tZ# zD1@ID;5HWIFCcjC|57@cIYtt_II}pIaaK_frNEtvPTLf2{RU>qAj~TA1XFP<0s%Fd z#aRGK_0v+z;Vm}_erv4`k)7lXJrn)F@UN@0+7h=#fCqw%>J-uW+g`e>mA2s-RwY=N ze(uJj^tyiyn_S^`U5r1%Kzh(gtG^b#LSz<%yBHB;uUvZR=ro^kS_-RZfbmOm@y%r7 zA_+~bsoswqra-?vH%f1ilY@(hjiB4!uyq_K&P0oow}HaZbneDFI6guYE zmUVQkq$MKc4M=NS_9gO`ZaO+!6)WC0j2B$a^u2Vu)GiSPOU)P4CigT5OV&x^t?R#(C2h*S^t~ax zMdc*5^Iwa~U&j3h@UKQipJ&S!rD)XSue}2sGAtQNo5_d{h&&9%54!%3W^chmm;cPw zhj7irT@sm4z@dyUHCef!HRJ~9A+YvKo3=VYDN|2l5+49r3-%v6NrQS;4`SOp>6(&t zy=2Kfi5WGFYMG<*v2NN#SUrg!HT1qi)$ZXwSX9??1SO{o{6sRKJ|O0E4vcndl>o1? zqFj63l$W?+&*LO-`qETvUOY~!JSWu4A&weS(}#@GXHkqSlngh-+?+4?Jg`Bp(^yNG zUK_2Q`%?wZ&h-%whc5D!g`7%)cj>6QrkuU7NJXhb3B7ht6zF7d7 z>2;E4?gmiLIwNSz8%2(W8={ESA0pyxvzFX>Yf4SqckD=JMQ#1WBh0B#Id(>N827|QjLuy*cu#aEg^M~na)O5vywORN%!SFYJGPa-PO5KhN@ zc`6siE8S92T_W**@^8ONQb1-ko}$>YB|nfGOLy(?>x@0?wySdUAP#$ntXPvjr!3Oj zx#l(HYtAjrodA)`r>J#jeYL!J*$#$Q-u=p64K4`4HCKyUZ1p#;y=__60U`wkjZVk^LDa>0Gxz^QQJ3aK$m-K2Emt`XMI2FPWdOp$;v8U)oome zi5K;HF6#uwz7aPSN;OvKOzf%oj{ez-PFvZKk6{VF-RJV0x!j4PnP+RBaFYz{gnaWO zr7T39`Yp@(Xl*Rxwb$dDuWZ&*0fj%Y2)_LR1nYXg&{8y3r&3kX)E4eH*+_1unEZ~s zA!PgY>RS=|$ciFtoXTlt>scF1(aL;HD8p!JmeP5%1MZ|59pkQj$giK?Og(VCfIdG* zY=vxdR3F~H7<2%HZ__SP*}4rA7`h%F>ArOhSs-*OcM9oqSnN`!rhWSzi+L)mp28#D zC=8>YTIR?+Au=s2r^ym+w{m^UlmTag6%EI04n2?UD@SLv%kG2Px5oW>-_cXxlG@(j z0(stc3h%r-Sn;!+uYY9yeXY!jj<|lEuX!gKPvPnG{c$;nWZQ^s=1Hsi9BW1kYNGz> z1Xxm*^X^;#UqB`8R?&x$G<8sfAQTmZzywB`M2^#dSKvR4r6C1DIlp4A1u0R46=Z6F?LY2nXr334+Od-n*#!$uh=R zt}hIxST9(UVI8s*J<80PlF*P_YK>d_mJj+l7wx?Y}P_9^}JqcZwMr=ko$rkSpSv zeb38aPMC=dy;(+C)(3Y|)A~V00S--CAF4_C=Sv)nh#Yqc(LTk&n`~%3s zZ+x;0k#QJ#^XYSWSWdvwM~g_9IwfU3d-(jwtV3CuJE8=|4vFrKQ~%`)r359^S$S2( z;#Hwc^2NJH-F|)AfFOD!+ywn)iSF z?YJ6)hhY9b{1Sh5x#?{CUIFKIXXVgb?H@R zL2M>aZMC+!p)(UhCXO^H18@k<4<>pwkMCZ_K!c^uPz z68K^h|E@@(s%)4dyyV0GD0D90P$?le>B;PyJu~qMMbpw-&QOJ$ucc*~&y%%%vqJWh z+;8zGpjSkC9sW&dS7)e}V1wE3p{E`+qF?e{)@9tM(CE!oBZ^qZV!ZeL(1cAI}%L5Jd zeON|UlXeft$1GB0N^9GTqi-AlO9^& z5RWrf%(MiRV>Xz+K9P`7*FCPV@B1`=vxpz5ZPsGVk3uPpY2<^x6X#}!<#r)G2lLuG zSaaTs;a23IMiiTd(^e*0i{Mn*0uXX2{Owo>0$@nP{UQXx_)Ll$bbqhR3?LA#*D%O1COHNp$*BDWFDG6Zx>BdGV-+B{7{E-5l6Z z;a+u(`Q1m41B(eVKQTqB0ukVaXhgdF`(`GfIU8E0V$J+u>Ljv*&hN8V9;DwhO*h-J zn?)GkW$=I9f%;qspWW1`z*tX5wt#d}N4R{?v94G)xeQ5EU85y-7PkSsym?*bsywg9 z(iw33h7uvuGwf@G3*r!-O>c`>@{p-(pmvmMw-K2MV$9_W5D3HaVPrjT->p=rh}hPU;VYkR9s!%9Rsj5L7(r&BkSX zOY2M#yTv+%Ik%S6ehe>8;uhE$ixAP2CEjt&7T|q`gx34l0tc&oa$#o_947G#v9oE! zF|X3U#U+^E#6E-D-QH%Xb5wkuFivN);X5Ksd9lll!3dMmsN15zLw42$Q8YYCXBLSO z@=Jg?<@`9D4rA_ldzI(Xu;l!h!SI)u6@;6psjp#+)jmSX(!7$^AX`te0wJ_Kzva8{ zi;chCe669SA9e!Qd42zOdSJY^<}3+Re?D(j{`OpBeWe-5di?RfEnEr*vD;g*3?_;q zbVROn5tI|PmvGItR}K#mEVNay$S(^q?g|jqvP1wJEb$W>kxLWko%wRl zFrsbjXqM~==*MaYa>0flXlc!?ok42a7w&29htcis$JDHl36(3{vdbT)i_B^i*clX= zg%2dl1Wwxc{<)t}K>3y;dHtc~j$x+S%R^onIpU9J6Rf^sZ4-f=qOO}2cJ|*5!93_v zUa%2T;S^6Zw1!eL*+7hA+ms>0Lx=p%%fqsvW2+i>`k)EwsaJmhSc*552fgrc4kl%7 zdR;CZLMb_>LV31CW)H?ql6s?ZL>gm8@<3gp(66?h5VDVCjxGQQ#JQ8VU) zzm(DFhM1&j>%+&Y^cSbbpO2e|8&XpSg?!z*Ql88@Ml;kRVe3OTKa9M_`OVY@WAY|N zqbv~Cl=Wf@yryXHX-%GE9YP5%TmB--_-b9|HwsL^Ka?hZ_XqrlQyf-Bpm`eN;MqJ*)C;pZ)ZSq6BAVSz zn|o;n-${v&j!MH7NsZK9I9%hDhLjkoS;+(>(NmGXxEqYZufP>5KP%7{26o_{J02Z@-|p^ z-z}-CLUnJohYjrvnz@}8(_Sz!Ify#=6{FcgYm9m88xl5Mh*_6s<yb8 z6E=M|ygXX!=XHn$QI@S-PIB15CtUrzztoXtL?c*-7~q|WK}w=rq788=M_iAoiKJQE zGr!rXXp+U%Lq%%Mbp8P3{JK-N`Gx#9Y{o?G{s3~tInxrMlDY{j9G~z~&lYin$sT@Qzn@3zhw&A>L1Xqfle`$19 zJRK3lHh<1)>MCssNW-SrI|mz97jf!9`6fMH8j_(qH6RadL%gag3|#D11~CQhDhQl= zd@+rZR>$aYUMbC9Vn)fT&E$nN7 zBnj<5rW@}|trNu=b~uV@FU-0$xQ)WUQa{R{mCj(FV>6+nQn6pg9=$L!Sc{98I5=$D zIB|UONr#=5U9o6OXAs zhzIx7L{MSD2~s{id|3B>QO{O~-0jelO1H&_J79fpOnr4~yxbbO96X`On?X8Z5Pw^I3zEZOAwK z&DM^**A*BR`zxH6&9^ln#iEm`46M7d z`x`#4sMzLY6T<}}Is2&513$D$Hv!|M?uMd>KY)&*PmsAOe(XR^N7dEoCcCfqZ8`AR zXg9=P9^M0X5wFyUVj{@mQ|VtOGUH!uWy>>rm_qThqsu=3OiNS261r{R>w0owdJ+<| zQ5@u{UOY45d%r+6BgeceTykB!l9o&y^5}dOHFkUsRt4bp1S!AN#)%~>mu)MEN<|aY z#$NSfIxaOWaUI#f;c(f!bp5cDRe08}6^7!S$Ly^&;@t0jU?PXpSL1hf*pZn1i^Hbq z%)Xd-d$waK+kWyjzs@YXFIs@~!nF9hthb}p2qGR{y^squsc~XcOD!#xM-IkN+L#id zXRs0$t<_MvsHu@RpT?UZVEWQ43a;t+^h#-ohGv!e^lCtvF^t_mSKn>{WB-BH^WV(fwb%J>;@Y0z^0#(H7ZNZIhj2mQ|AFuV(mwOZwmg9Nc?|l75ECFYh$*lP9)`0X z-f6LN+jr&M0}0`{zDHfUHMEgB>7*K1 z+vyp**9zaSS~Pl6UJM-k4evx%Yqnkyx|FL%BSXYvqA9I=mG|#c_?wRgFiTZM+@Jlke2Z-2qASYwYH^M*N0te>qd(O)-XqpGSu7*&5-_% zVs3m;mU8(3cdrvDMQyVsKS#6TX)fDMwhIxV-89S*uTLK$*5a-$bR(f2IU}Cj;uF$Tx7}*esAC-w zPl6Vev;L*R-mX0Rh=#)YJ>uZnGIKrTRj>9=jkjdByu0_w@njK(Pkf=2NUT3<byKPoU#@8tp7H}9$=ht*wJgS*02_zQOt$8zfH=Iq9FKEd+6bA z+0^z#pMY|qnsWjp3{o}(_Ki&JZi5B<>y+pX`>s;Fa3(sM8vCo#uoTyoWy=kwCR;Lb>8!LVHjlASu zC%z7c3s==Ct6_w#xh|5ZuHX{kA3*(o*fmjwYoyU1SB3@s;o0g!NWkPfr8YQ{VgC)| zihFH1lF#7MEgZ>)y|bV(d_=efg3SvP?lS%>&I%Qx2uY&iBO~BRqUQY_#Ej_L;Q+;6 z>0DYw-DmUKv&-Wq?gG-tsJ8EI5}m%A6{6z%_W`ZiF$+pRkK0BmN4EFL{E^OJKeVRQ z^u)~ec_YlP!kl(`D87w>zy4Ha%L`ZKOnU&#RyFHc2tDRiZk?9j9S4myeO+GQc<~#{ zWYj@{op`;}qR!nFWNEBQO`*4FGRYh88~wmVX0rFaU%017xfovRGc0QPwRUMyx zsQLTQIqSh!{dIy*VqRY}%H8Qi(UxF;m*{cwzLEaQlJ8XcNlfw?Jz7levCk3YB_S-UA;D`%4W24*~P;-DXywHvpaooY^+|dWE1H88ecqiXp1^HDFEGV zg1+!4DRmyu)En;3r79T|cJdf3xZ&Q>RpCdQ0%W`c!`oQ(#=wl%Zp-$RJ8a$_Z6&p| zN67}-cP5!c(`oOeH2E{?r3Pw1zF_Az)LjEunedcz4Q6aK~@ zk!&9!v+8Jm`Hh`PkhD2dHg9|;3^kKrc#1+&NsS3BMiOy5WJ+xt+ByPIhR<$Rirf;w zl*hmUqh~s!ut8n!F3LA1le0xZ(Ln&2_pCvTq9ifq8xDI@J8%MAQVga7j>4huqDUAc ziFpKuQv@EXJxqW_INWal!BDArB0Up$ATa*P+G#_xhi%U=d7(Rn%V-In6}uonu~as& zFH_Fumk37cv~}=Lq4*L90Qt+PpI z(cLi*c^7wJHc(%0${g+nX_otj1g^SLL4%c1ozed?ElX!tDXHX#wfKt>lL(A|kYo3S ze$aj~?cLtR~FxoSC$|2!|oPeoQ!lFVPNk`}!bk^TP`+9QO5~4r&xGHzA^&|Bpq7-4Sbgq#)PduLV0<-;^Qoh z3O`3{YP$j4VO888gb@PYs0=CknWW9_-1WO1HVn7lnRu9hyuH$D-zO#E(zZ7wE9~Wc z*UK)FpDWd6vqMx`jJ*@Cp{cmnnlmGr{8pXJb-j=p5!SGXDA?Gs+lx4tQzV;e8!|~2 zkW}K5d^5^s1d#%zCM#SjL=-dez?*{=MrRcakuiASB?rJf9t<*WwPYy(15*lKaY)ho z1U-pc6WmE7!d?g$3Dz=%$V3{(W&dJcqw6`jvh?Z(5? zeLdZRvun0e?1@}O;0n94Sru|9_3`FuU02+1eKDg#Gv|i3)71#a5vVNAHFY4 zbUD_RP-z66ul&{6tg3UV=;3SI%|f`FMXHZB3POvAfSAi53-3+G2=(`b>uIHMmhb;X zbrRm4__9(>T-2HiWkV8SBY6QKqZP_Q5gR}d{Fc81c!(t7cO;pTgL zc0*v|`)nD{Rhq#zEfuF3%WnMN)vJp)o*jI6)7t>ICqk#vdtU*Hi00m0+3lKX>T{oO z$K%HV{MX}AYZr4G8Q8TXv0BFJPx>ur+I=2zZ)<>!UcZ&zT^p`FB;quO(5z>4+Mqe)1J#!L zRwna$I6jg)d0n5^A@+lhcH@+$_eS^iPLkvnk|vQoh14h?m5E%|-X8RTi~%KsC+4mI zqo6lFD~{--f%IRJ49{s$dUfJCki>+naH`249d(W0QTsB>YAIuR_zG%qBChU+lM3`nC%JASO^-11Bb zn$632EkLuZ_oPtw)sZ#f3#dMryee_x6 z#G4Fqn~GhDLMAm(Sq+LTj9fJNTnbX1C4IHLhC}0`z>qJyW#gZXGB32;s4-b`ASuBY z8U{(x)ulw`B%pfoNP`uNGLg-|4c`9%`0ru;FB(l)5<}JD`4M#;{O75Eh_k*jgsa*? z5-b1t>{9E);N{e`_{de!{A^x$)B{qq^HOC9!Lo>wj4%NFqEJ zoCcF3r>ux4q0!9t^Ir$XA&d#6bhAtOed421^;B%Ut9851Dthc6`W2hVxQz1<%Zan& zCh&eQs8g~dX=Z3#Tlo)w;vvTLTY`wf57wsE#&hX?ULtM78|US;IZN+L^HV>d#O#NAFzX>tDh9;#(yWag?iB9_IonF=4>>FMkS%<(E`v{RJLEi29|hUsv+ghk)cJ4 z$%k}4RZxFaAP|jwxH8UvaWIMm7S-X{@yEJjjVjmfIno_CHI>(xIhn=$!u`^xwX{D8 z*4UOOJaau7AuB<|Z@h7QI%2|pW^43YzRM}|b7LO~Tgax;&|Xr3M=-Y#o0_2sR9JyD z7b;X$WAL_0iL2wZuBEEh;H`#Y%n3znRRZhFBmf}@AKi}$prkmW2r$}+FiUhVL*&ig z5^jgDCG6-g$OkJtna5Wabhyr`sLqZhzI~2G{MKW-Vo_%t7Gr)Bp-gNxW9JNpw9d0+6uIvrw2y*Yc9&daAslLJ+_a3a@-kRv$}38%B< z>Bh}>uD>H=d(|@ni@1^6HGdVz9!-}RG}+R>l}1c!~@jIXiv~hI1)3AutfWa zGF4=@oVKxKhDKM$xwP-@ODRo>vimlB(~F`FIw&jv9MZqoZCJ4@R>MM7>f-0)gdVutk(s+rG17L{4EEn0lP{=t6U`U*5qfC5gF0a;`c-=*f$Uc{^}|t#hmv??BmaaU4DP%y|8lVaPg3 zZ$uGwH|LJ1x^xvv(VR80I3oi*ho}ps1TK=au?Q*mAg;5Ourg=wlQ`F{8+v4(S(YDB zKa0rq4|=B$$a`EJYn$Ak=d$ELorJ`tUc>Hw+y{_!1iwW&Mnm^i_8UmpIP2Z*j^P>h z=)8kfzfU^#BjaHOwxlCR3|^1Sp2vC$k^BL)bes)Go0hM5B# zc}q1?)nXe`0V+GqWr_V>(t{@E`sLA}Y{h)89zHe<*w?jMwB*J-4kkc$@BM?3O-|W^ zl%*DcGp$3ru$@Gr_Avh3XSzxKZ$q~*=zW=`##Wx#CYx`O-pZkoI^AK6>tr{-hGph~ zHGM<9XVbA|;e?(OZ}EDG5$-2qR&`oz@t*+sp485q(uC=;_4_hGbR|J-4*Hg)sCgJ# zuoy8TTf%l-rzBt-j`PG7i}y{(l0)$O4a&%Ir1dI8#GiF7-{)h_88@LqWdKEk$r-pY W2PvtDIr`fOzzIS}AW8FQ<^KUw2sX6< literal 0 HcmV?d00001 diff --git a/alfred/app/cache/weather_outfit_images/93379bdabb939699e8565f358c79b96de623fbffb33153aeaf847ad9b339631a.jpg b/alfred/app/cache/weather_outfit_images/93379bdabb939699e8565f358c79b96de623fbffb33153aeaf847ad9b339631a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9791bf071f7f7b8cab231bbc0bd8a6d16df566d4 GIT binary patch literal 78582 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�d^M=^x)RV`R>*mpGGRRlBql2e^_2mO^Z1H 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 literal 0 HcmV?d00001 diff --git a/alfred/app/cache/weather_outfit_images/a4928b9950baed6aad15a8e69c6c09bb6b068eba6f2e78e1e126c42be66aced3.jpg b/alfred/app/cache/weather_outfit_images/a4928b9950baed6aad15a8e69c6c09bb6b068eba6f2e78e1e126c42be66aced3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0d9794f118bdadbf366bb0e5b9e8a65db964ed59 GIT binary patch literal 25485 zcmb5Vbx>Tvw=F!l1P>A%f({x8?hb=9SQsRNySs%DBqaEt!I=RDhu|9A-Q6L$JA_B> z{oQ(1?|%P$d+JP`>h9Ckr>oEIwbx$#H2bs)AW&8SDFBd=005-tAK+;LAPYc2{%8OG zQJxJI9rd4KqNAgsV_{-rV_{-pVdLQAV86h7frW)jh>M3$KtM=8TSy{X9+-wC92TUqeR6 zKte%9!^C>7RwH<>MnOhFLqkJH#=t{TLKWEaQ@0 zLBjAB+%~5D3F}K-PG$QVDWj~ZYw&6A2^lksZv3x~=k6$<=LGHF-27kr*B?3>#&e|r zApjW}2^kdy0|oWD)xRdsm4vADXhb~HFSS0Rzvgw0&8`S)J0WHmTVv#t0lS=jdYT8| zpddZJ4+?e3m;HlQ_ z6Tl|lEwJmkSwR=7)?f#wXzt(Fl9Gpto=qxU>f0)3C+~W_YHWog%{XkHp<*ewT>dfl0jGDH|y24YJx6R0JG21La0nqFE_DXNu(4t z>CBuHdd9S~$R=O7uY)3>+>H_jddd|iud7o{bz8X!E!gSKnK~_5A(&?pSHnTfz*TIW>H+ezZc>HMwgge6&f>_1%9^yo42g zM)qTaYDuB1kxL*aLSIgoqF0J&moq!PYdb6aaZwz#T>$9`;bu+it%#3;y7~caq)&*` zOxshZ(eQU=1H1oqPNLUytaTH%uf#@F=53OP(A94$)7HsCj$r+Ftgg;bi+8j2_a|ym0C9P)S zHCBJCL%`GSzw(l6JO?*3E*{VO*Yy(;adJKtr>--8Ool66(3-cmt)f&G&%V zrf#39jv-m!YU?W;CL;=SR&$L_4C2p~#1#iYgXs4+dieU>c*;C`BeJ7y1~k@ghV~C< zlvqyyh3%Ah8wXR720H_G#be|JY|!xEyj+LZ9FR*^W2F=KYy$Qw~uSqzFbH8 zgNM%E$`?k&u_#l6Sa1Wr84zna=v90@kr{SLQZ%z-=Ck~u({HZAnMPM^cDO2%`o*Vc ztszlGYi!J7SZL&rx4o@WJp3hH53WcO=>P)QWBF-p?rrgC+R4z{{gzgBgvs_<4&fwttWU_ zxpkop>2hamy?kFUy4x6L!S%YPNYyIok=8@=aiLWCUfWudxTw(ojdfY+jd_7kpnym| zsl@^_>nnZMuvD?2jFkJrbI{6kcK6}%+ofSV@s*lN*>7TnQZzy&Vov+t$W-jZ=iNDU zR`@oXn9cMRy#3NN&I-I(KLqQqmDXx7E^J4ay&uiyB>icKK}LdA8tE3ue;?c4ys>AM zFHt|Py4z|D6RO&n?lvD70VGhJPVFBVo7-_@&TtDu5&W0+XZsB5@j=;AtyGGmCxY5(6Ti z&y8?B8Gz8x+7_vo7@0DQ3F>@P#VhS`7As}+b8-=9%O@xts7ln=?_3|hU2W{(D$^(_ zTxcO-TU-|J$9O{LpHVpz+A%t?+(Q4V z0>Ypv7NPSl4YhcLgx8}|*@Vn>xXMwcMn+)Yj$W&Ss=cGYSeUsj0!C+r#l|x>cWe;w}vp>C1NDmC3(WLyziN|BZ zMp-_iXomFABNl$ioV)ba(&Gu>A7*1^*6WY!H(89or_siok!CR&15*N)YGmL(W9MsUv|~N*+G-+kM_fBOq6rv#Aqm47te9r7mhq z*O@MxH(CK8Buvq3`)GGbuKdANr`@mb-8qN(6T8T7dAHdc?eiYDZjVPXpGYTQ*TUIdABxkhDky?x|r!foC3RwuI_@XxT z)9zK8kFV4$Cr3j7BrxL`M6BNq+$C`3uKKvb?2?Sr498}GEwu;01aDdWTGi>Mz_}$6#??Gx4ql^lvaY+nu3z4E@Y(27n!92}A#zo2Y zKXGdAD?$W&(Zzacr|Sl)meL^>u4GoYsMS18A~6C?;(-j{UH4y=Mm3&v() z7Ix+@@$*rQlUU2RL*N<13K*lwPd_6eWjYF#|8jfuazV{H*sK>>RumZNnwR^!YOF7$ zMj#DHC?)jGg*%~aUax`W{HpQNEF>C`n8<{Y@d6Bi-

t;3PmDwa5C>V;}D;Fb8QD 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 C%`m9| literal 0 HcmV?d00001 diff --git a/alfred/app/cache/weather_outfit_images/ebb79b8e11936267b3477c5d930536fc61cb226a4f357c09c21db8192ca134ff.jpg b/alfred/app/cache/weather_outfit_images/ebb79b8e11936267b3477c5d930536fc61cb226a4f357c09c21db8192ca134ff.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3adceaaee9a135d9fac0701f3238bff84fb7ae7e GIT binary patch literal 24960 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 literal 0 HcmV?d00001 diff --git a/alfred/app/services/__pycache__/alfred_views_controller.cpython-313.pyc b/alfred/app/services/__pycache__/alfred_views_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0248854ccdbd0a64c8de0f4bc0e137b5da7039e GIT binary patch literal 11578 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 I<#Y)D0}l{%>i_@% literal 0 HcmV?d00001 diff --git a/alfred/app/services/__pycache__/camera_settings.cpython-313.pyc b/alfred/app/services/__pycache__/camera_settings.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd3e743a7fab8d46042cc31baa96f44acd8e630d GIT binary patch literal 11994 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 literal 0 HcmV?d00001 diff --git a/alfred/app/services/__pycache__/ha_views.cpython-313.pyc b/alfred/app/services/__pycache__/ha_views.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8942c869d62f2721292e98578b33d71c6f2fd1c4 GIT binary patch literal 19838 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 literal 0 HcmV?d00001 diff --git a/alfred/app/services/alfred_intents.py b/alfred/app/services/alfred_intents.py new file mode 100644 index 0000000..f2a2044 --- /dev/null +++ b/alfred/app/services/alfred_intents.py @@ -0,0 +1,189 @@ +"""Conservative French voice router. Never guesses dates for destructive actions.""" +from __future__ import annotations + +import os +import re +import threading +import time as monotonic_time +import unicodedata +from datetime import date, datetime, timedelta, time +from zoneinfo import ZoneInfo + +TZ = ZoneInfo(os.getenv('ALFRED_TIMEZONE', 'Europe/Paris')) +MONTHS = {m:i for i,m in enumerate('janvier fevrier mars avril mai juin juillet aout septembre octobre novembre decembre'.split(),1)} +DAYS = {'lundi':0,'mardi':1,'mercredi':2,'jeudi':3,'vendredi':4,'samedi':5,'dimanche':6} +PENDING_LOCK = threading.Lock() +PENDING_DELETE = {'value':None, 'expires':0} +PENDING_CREATE = {'value':None, 'expires':0} + + +def simplify(text): + return ''.join(c for c in unicodedata.normalize('NFD',text.lower()) if unicodedata.category(c)!='Mn') + + +def parse_day(s, now=None): + now = now or datetime.now(TZ) + text = simplify(s) + today = now.date() + if re.search(r'\b(apres.demain)\b',text):return today+timedelta(days=2) + if re.search(r'\bdemain\b',text):return today+timedelta(days=1) + if re.search(r'\b(aujourd.hui|ce matin|cet apres.midi|ce soir|cette nuit|dans la journee)\b',text):return today + m = re.search(r'\b(?:le\s+)?(\d{1,2})(?:er)?\s+(janvier|fevrier|mars|avril|mai|juin|juillet|aout|septembre|octobre|novembre|decembre)(?:\s+(20\d{2}))?\b',text) + if m: + month=MONTHS[m.group(2)];day=int(m.group(1));year=int(m.group(3)) if m.group(3) else today.year + candidate=date(year,month,day) + if not m.group(3) and candidate < today: candidate=date(year+1,month,day) + return candidate + m=re.search(r'\b(?:le\s+)?(\d{1,2})[/-](\d{1,2})(?:[/-](20\d{2}))?\b',text) + if m: + year=int(m.group(3)) if m.group(3) else today.year + candidate=date(year,int(m.group(2)),int(m.group(1))) + if not m.group(3) and candidate < today: candidate=date(year+1,candidate.month,candidate.day) + return candidate + for d, weekday in DAYS.items(): + if re.search(r'\b'+d+r'\b',text): + delta=(weekday-today.weekday())%7 + if 'prochain' in text and delta == 0: delta=7 + return today+timedelta(days=delta) + return None + + +def parse_clock(s): + text=simplify(s) + m=re.search(r'\b(?:a|vers|de|pour)\s+(\d{1,2})(?:\s*h(?:\s*(\d{1,2}))?|\s*heures?(?:\s*(\d{1,2}))?)\b',text) + if not m: return None + hh=int(m.group(1)); mm=int(m.group(2) or m.group(3) or 0) + if hh>23 or mm>59: raise ValueError('Heure invalide') + return time(hh,mm) + + +def period(s, now=None): + now = now or datetime.now(TZ) + text=simplify(s) + explicit_day=parse_day(text, now) + morning = 'matin' in text + afternoon = 'apres-midi' in text + evening = 'ce soir' in text or 'ce soir' in text + days=re.search(r'\b(?:sur|pour|pendant|les)?\s*(\d{1,2})\s*jours?\b',text) + if explicit_day: + start=datetime.combine(explicit_day,time.min,TZ) + end=start+timedelta(days=1) + if morning:start=start.replace(hour=6);end=start.replace(hour=12) + elif afternoon:start=start.replace(hour=12);end=start.replace(hour=18) + elif evening:start=start.replace(hour=18);end=(start+timedelta(days=1)).replace(hour=0) + return start,end,'day' + if days: + n=int(days.group(1)) + if not 1<=n<=31: raise ValueError('Demander entre 1 et 31 jours') + start=datetime.combine(now.date(),time.min,TZ) + return start,start+timedelta(days=n), 'days' + if 'mois' in text: + start=datetime(now.year,now.month,1,tzinfo=TZ) + next_month = datetime(now.year+int(now.month==12), now.month%12+1,1,tzinfo=TZ) + return start,next_month,'month' + if 'semaine' in text: + start=datetime.combine(now.date()-timedelta(days=now.weekday()),time.min,TZ) + if 'prochaine' in text:start+=timedelta(days=7) + return start,start+timedelta(days=7),'week' + start=datetime.combine(now.date(),time.min,TZ) + return start,start+timedelta(days=7),'days' + + +def classify(text): + t=simplify(text).strip() + if not t:return None + if re.search(r'\b(annule|non merci|laisse tomber)\b', t) and (current_delete() or current_create()):return {'kind':'cancel_pending'} + if re.search(r'\b(confirme|oui supprime|confirmer la suppression|oui c.est bon|oui valide)\b',t): + if current_delete():return {'kind':'confirm_delete'} + if current_create():return {'kind':'confirm_create'} + if re.search(r'\b(accueil|reviens|retour|ferme (le|la) (planning|calendrier|vue)|ecran principal)\b',t) and not re.search(r'\b(rendez.vous|reunion|evenement)\b',t):return {'kind':'home'} + if re.search(r'\b(montre|affiche|voir|regarde)\b.*\b(camera|cameras|video|videos)\b',t):return {'kind':'cameras'} + if re.search(r'\b(affiche|montre|donne|quelles|quelle|voir)\b.*\b(temperature|temperatures|capteurs|chauffage)\b',t):return {'kind':'temperatures'} + # Do not hijack general requests for images, HA or Hermes. + if re.search(r'\b(supprime|efface|retire|annule)\b.*\b(rendez.vous|evenement|agenda|calendrier|planning)\b',t):return {'kind':'delete'} + if re.search(r'\b(ajoute|ajouter|cree|creer|inscris|planifie|note)\b.*\b(rendez.vous|evenement|agenda|calendrier|planning)\b',t):return {'kind':'create'} + cal_words=r'\b(agenda|calendrier|planning|rendez.vous|evenements?)\b' + query_words=r'\b(pre[v]?u|prevu|programme|organise|j.ai|ai.je|quoi|qu.est.ce|choses?|occupe|libre)\b' + time_words=r'\b(demain|aujourd.hui|matin|soir|apres-midi|journee|semaine|mois|jours?|novembre|janvier|fevrier|mars|avril|mai|juin|juillet|aout|septembre|octobre|decembre)\b' + if re.search(cal_words,t) or (re.search(query_words,t) and re.search(time_words,t)) or ('8 novembre' in t): + return {'kind':'calendar', 'question': bool(re.search(query_words,t))} + return None + + +def extract_create(text, calendars): + """Parse straightforward instructions only. Missing/ambiguous fields become a prefilled form.""" + raw=text.strip(); normalized=simplify(raw) + day=parse_day(raw) + hour=parse_clock(raw) + options=[c for c in calendars if c.get('can_create')] + preferred=os.getenv('HA_DEFAULT_CALENDAR','').strip() + matching=[] + for c in options: + if simplify(c['name']) in normalized or simplify(c['entity_id'].split('.',1)[-1]).replace('_',' ') in normalized: + matching.append(c) + calendar = matching[0]['entity_id'] if len(matching)==1 else None + if calendar is None and preferred in {c['entity_id'] for c in options}:calendar=preferred + if calendar is None and len(options)==1:calendar=options[0]['entity_id'] + # Strip date and time syntax to derive a plausible title. + title=re.sub(r'^.*?\b(?:ajoute|ajouter|cree|creer|inscris|planifie|note)\b', '', normalized, count=1) + title=re.sub(r'\b(?:dans|sur)\s+(?:le\s+)?calendrier\s+[\w\s_-]+?(?=\b(?:le|demain|aujourd.hui|a)\b|$)', ' ', title) + title=re.sub(r'\b(?:le\s+)?\d{1,2}\s+(?:'+ '|'.join(MONTHS)+ r')(?:\s+20\d{2})?\b',' ',title) + title=re.sub(r'\b(?:demain|aujourd.hui|apres.demain|lundi|mardi|mercredi|jeudi|vendredi|samedi|dimanche)\b',' ',title) + title=re.sub(r'\b(?:a|de)\s+\d{1,2}\s*(?:h|heures?)\s*\d{0,2}\b',' ',title) + title=re.sub(r'\b(?:dans|sur)\s+(?:le\s+)?(?:calendrier|agenda)\b.*$', ' ',title) + title=re.sub(r'^\s*(?:moi|un|une|le|la|les|rendez.vous|evenement|pour|de|a)\s+', '',title) + title=re.sub(r'\s+', ' ', title).strip(' ,.:') + # A guided confirmation is safer than using a guessed title. + title=title[:200] + data={'title':title,'calendar_id':calendar or '', 'date':day.isoformat() if day else '', + 'time':hour.strftime('%H:%M') if hour else '','duration_minutes':60} + complete=bool(day and hour and calendar and title and title not in ('calendrier','agenda','rendez vous')) + return data, complete + + +def extract_search(text): + normalized=simplify(text) + normalized=re.sub(r'^.*?\b(?:supprime|efface|retire|annule)\b','',normalized,count=1) + normalized=re.sub(r'\b(?:le\s+)?\d{1,2}\s+(?:'+ '|'.join(MONTHS)+ r')\b','',normalized) + normalized=re.sub(r'\b(demain|aujourd.hui|apres.demain|lundi|mardi|mercredi|jeudi|vendredi|samedi|dimanche)\b','',normalized) + normalized=re.sub(r'\b(de|du|mon|ma|un|une|le|la|les|rendez.vous|evenement|agenda|calendrier|planning)\b',' ',normalized) + return re.sub(r'\s+',' ',normalized).strip() + + +def set_delete(event): + with PENDING_LOCK: + PENDING_DELETE['value']=event + PENDING_DELETE['expires']=monotonic_time.monotonic()+90 + + +def current_delete(): + with PENDING_LOCK: + if monotonic_time.monotonic()>PENDING_DELETE['expires']: + PENDING_DELETE['value']=None + return dict(PENDING_DELETE['value']) if PENDING_DELETE['value'] else None + + +def clear_delete(): + with PENDING_LOCK: + p=PENDING_DELETE['value'];PENDING_DELETE['value']=None + return p + + +def set_create(details): + with PENDING_LOCK: + PENDING_CREATE['value']=dict(details) + PENDING_CREATE['expires']=monotonic_time.monotonic()+90 + +def current_create(): + with PENDING_LOCK: + if monotonic_time.monotonic()>PENDING_CREATE['expires']: + PENDING_CREATE['value']=None + return dict(PENDING_CREATE['value']) if PENDING_CREATE['value'] else None + +def clear_create(): + with PENDING_LOCK: + p=PENDING_CREATE['value'];PENDING_CREATE['value']=None + return p + +def cancel_pending(): + clear_create();clear_delete() diff --git a/alfred/app/services/alfred_views_controller.py b/alfred/app/services/alfred_views_controller.py new file mode 100644 index 0000000..cc8238b --- /dev/null +++ b/alfred/app/services/alfred_views_controller.py @@ -0,0 +1,132 @@ +"""Fast voice routing for visual actions; Hermes still handles general tasks.""" +from __future__ import annotations + +import os +from datetime import datetime, timedelta, time +from zoneinfo import ZoneInfo + +from . import ha_views as ha +from . import alfred_intents as intents +from . import ha_weather + +TZ = ZoneInfo(os.getenv('ALFRED_TIMEZONE','Europe/Paris')) + + +def _label(s,e): + return f"du {s:%d/%m} au {(e-timedelta(days=1)):%d/%m}" + + +def _event_summary(events, question): + count=len(events) + if count == 0:return "Non, je n'ai trouvé aucun événement pour cette période." if question else 'Aucun événement prévu sur cette période.' + names=', '.join(ev['title'] for ev in events[:3]) + if question: + return f"Oui, {count} événement{'s' if count>1 else ''} prévu{'s' if count>1 else ''}. {names}." if count<=3 else f"Oui, {count} événements sont prévus. Notamment : {names}." + return f"Voici ton planning avec {count} événement{'s' if count>1 else ''}." + + +def _calendar(text, question=False): + start,end,view=intents.period(text) + payload=ha.get_events(start,end) + content={'type':'calendar','view':view,'title':'Planning','start':start.isoformat(),'end':end.isoformat(), + 'events':payload['events'],'calendars':payload['calendars'],'errors':payload['errors']} + speech=_event_summary(payload['events'],question) + if payload['errors']: speech+=' Certains calendriers sont indisponibles.' + return content,speech + + +def _create(text): + calendars=ha.list_calendars() + details, complete=intents.extract_create(text,calendars) + if not complete: + return {'type':'calendar_form','values':details,'calendars':calendars},'Je prépare le rendez-vous. Complète ou vérifie les informations à l’écran avant de l’enregistrer.' + # No calendar mutation until a second explicit confirmation. + intents.set_create(details) + pretty=datetime.fromisoformat(details['date']).strftime('%d/%m') + speech=f"Je vais ajouter {details['title']} le {pretty} à {details['time']}. Dis confirme pour enregistrer, ou annule." + return {'type':'calendar_form','values':details,'calendars':calendars,'awaiting_confirmation':True},speech + + +def _delete(text): + now=datetime.now(TZ) + day=intents.parse_day(text,now) + if day: + start=datetime.combine(day,time.min,TZ); end=start+timedelta(days=1) + else: + start=datetime.combine(now.date(),time.min,TZ);end=start+timedelta(days=45) + data=ha.get_events(start,end) + needle=intents.extract_search(text) + events=[e for e in data['events'] if intents.simplify(needle) in intents.simplify(e['title'])] if needle else data['events'] + if not events: + return {'type':'calendar','view':'days','start':start.isoformat(),'end':end.isoformat(),**data},'Je ne trouve pas cet événement. Aucun événement n’a été supprimé.' + if len(events)!=1: + return {'type':'delete_candidates','events':events[:30]},f"J'ai trouvé {len(events)} événements correspondants. Choisis celui à supprimer sur l'écran." + ev=events[0] + if not ev['can_delete']: + return {'type':'delete_candidates','events':[ev]},'Ce calendrier ne permet pas la suppression de cet événement depuis Alfred.' + intents.set_delete(ev) + warning=' Attention, si cet événement est récurrent, cela peut supprimer toute la série.' if not ev['recurrence_id'] else '' + return {'type':'delete_candidates','events':[ev], 'awaiting_confirmation':True},f"Confirme la suppression de {ev['title']}, ou dis annule.{warning}" + + +def _confirm_delete(): + e=intents.current_delete() + if not e:return {'type':'text','text':'Confirmation expirée.'},'La demande de suppression a expiré.' + # Recheck existence in HA to avoid deleting an event that changed meanwhile. + start=datetime.fromisoformat(e['start'])-timedelta(days=1) + end=datetime.fromisoformat(e['end'])+timedelta(days=1) + found=ha.get_events(start,end,[e['calendar_id']])['events'] + if not any(x['id']==e['id'] and x['start']==e['start'] and x['recurrence_id']==e['recurrence_id'] for x in found): + intents.clear_delete() + return {'type':'text','text':'Cet événement a changé. Suppression annulée.'},'Cet événement a changé. Rien n’a été supprimé.' + ha.delete_event(e['calendar_id'],e['id'],e['recurrence_id'] or None) + intents.clear_delete() + content,speech=_calendar('planning sur 7 jours') + return content,'Événement supprimé. '+speech + + +def _confirm_create(): + data=intents.current_create() + if not data:return {'type':'text','text':'Confirmation expirée.'},'La demande de création a expiré.' + if os.getenv('ALFRED_ALLOW_CALENDAR_WRITES','false').lower() not in ('1','true','yes'): + return {'type':'calendar_form','values':data,'calendars':ha.list_calendars()},'L’écriture est désactivée. Active ALFRED_ALLOW_CALENDAR_WRITES dans la configuration.' + start=datetime.fromisoformat(data['date']+'T'+data['time']).replace(tzinfo=TZ) + end=start+timedelta(minutes=int(data.get('duration_minutes',60))) + ha.create_event(data['calendar_id'],data['title'],start,end) + intents.clear_create() + content,speech=_calendar('planning sur 7 jours') + return content,'Événement ajouté. '+speech + + +def route(text, last_view=None): + # Weather is a read-only dynamic view; avoid sending routine forecasts to Hermes. + if ha_weather.is_weather_request(text, last_view): + data = ha_weather.get_weather(text) + return data, ha_weather.describe(data, text) + intent=intents.classify(text) + # Short temporal follow-ups to a displayed calendar: 'Et demain ?', 'Ce matin ?' + if intent is None and last_view == 'calendar' and len(text.strip()) < 45: + t=intents.simplify(text) + if any(word in t for word in ('demain','matin','apres-midi','soir','journee','semaine','mois','jours','novembre','decembre','janvier','fevrier','mars','avril','mai','juin','juillet','aout','septembre','octobre')): + intent={'kind':'calendar','question':True} + if intent is None:return None + kind=intent['kind'] + if kind=='home':return {'type':'home','title':'Alfred','text':'À ton écoute.'},'Je reviens à l’accueil.' + if kind=='calendar':return _calendar(text, intent.get('question',False)) + if kind=='create':return _create(text) + if kind=='delete':return _delete(text) + if kind=='confirm_delete': + if os.getenv('ALFRED_ALLOW_CALENDAR_WRITES','false').lower() not in ('1','true','yes'): + return {'type':'text','text':'Écriture calendrier désactivée.'},'La suppression est désactivée dans la configuration.' + return _confirm_delete() + if kind=='confirm_create':return _confirm_create() + if kind=='cancel_pending': + intents.cancel_pending() + return {'type':'home','text':'Demande annulée.'},'D’accord, aucune modification effectuée.' + if kind=='temperatures': + temps=ha.get_temperatures() + return {'type':'temperatures','items':temps},f"Voici {len(temps)} mesures de température provenant de Home Assistant." + if kind=='cameras': + cameras=ha.list_cameras() + return {'type':'cameras','items':cameras},f"Voici {len(cameras)} caméras Home Assistant disponibles." + return None diff --git a/alfred/app/services/camera_settings.py b/alfred/app/services/camera_settings.py new file mode 100644 index 0000000..7665385 --- /dev/null +++ b/alfred/app/services/camera_settings.py @@ -0,0 +1,187 @@ +"""Réglages V4L2 de la webcam locale pour l'interface Alfred. + +API en lecture/écriture sécurisée par une liste blanche de contrôles, plages +et états renvoyés par le pilote. Aucun accès shell ni commande arbitraire. +""" +from __future__ import annotations + +import json +import os +import re +import shutil +import subprocess +import tempfile +import threading +from pathlib import Path + +_ALLOWED = { + 'brightness', 'contrast', 'saturation', 'hue', 'gamma', 'gain', + 'sharpness', 'backlight_compensation', 'power_line_frequency', + 'white_balance_automatic', 'white_balance_temperature', + 'auto_exposure', 'exposure_time_absolute', 'exposure_dynamic_framerate', +} +_AUTO = ('white_balance_automatic', 'auto_exposure') +_LINE = re.compile(r'^\s*(\w+)\s+0x[\da-f]+\s+\((int|bool|menu)\)\s*:\s*(.*)$', re.I) +_KV = re.compile(r'(min|max|step|default|value)=(-?\d+)') +_MENU = re.compile(r'^\s*(-?\d+):\s*(.+?)\s*$') +_LOCK = threading.RLock() + + +class CameraSettingsError(RuntimeError): + pass + + +def _device() -> str: + return os.getenv('CAMERA_DEVICE') or f"/dev/video{int(os.getenv('CAMERA_INDEX', '0'))}" + + +def _config_path() -> Path: + return Path(os.getenv('CAMERA_SETTINGS_FILE', str( + Path(__file__).resolve().parent.parent / 'config' / 'camera_controls.json' + ))).expanduser() + + +def _run(*args: str) -> str: + binary = shutil.which('v4l2-ctl') + if not binary: + raise CameraSettingsError('v4l2-ctl est absent : installer le paquet v4l-utils.') + try: + result = subprocess.run( + [binary, '-d', _device(), *args], capture_output=True, + text=True, timeout=6, check=False + ) + except (OSError, subprocess.TimeoutExpired) as exc: + raise CameraSettingsError(f'Caméra indisponible ({exc}).') from exc + if result.returncode != 0: + explanation = (result.stderr or result.stdout or 'erreur inconnue').strip() + raise CameraSettingsError(f'V4L2 : {explanation[:300]}') + return result.stdout + + +def _parse_controls(output: str) -> dict: + controls: dict[str, dict] = {} + current = None + for line in output.splitlines(): + match = _LINE.match(line) + if match: + name, kind, raw = match.groups() + current = name if name in _ALLOWED else None + if current: + props = {k: int(v) for k, v in _KV.findall(raw)} + controls[current] = { + 'kind': kind.lower(), 'inactive': 'inactive' in raw, + 'menu': [], **props, + } + continue + menu = _MENU.match(line) + if menu and current and controls[current]['kind'] == 'menu': + value, title = menu.groups() + controls[current]['menu'].append({'value': int(value), 'label': title}) + return controls + + +def _get_controls() -> dict: + return _parse_controls(_run('--list-ctrls-menus')) + + +def get_settings() -> dict: + with _LOCK: + return {'device': _device(), 'controls': _get_controls()} + + +def _validate(control: object, value: object, controls: dict) -> int: + if not isinstance(control, str) or control not in _ALLOWED or control not in controls: + raise ValueError('Réglage caméra inconnu ou non pris en charge.') + data = controls[control] + if data['inactive']: + raise ValueError('Réglage indisponible tant que le mode automatique est actif.') + if type(value) is not int: # écarte bool et chaînes + raise ValueError('Valeur entière attendue.') + if data['kind'] == 'bool' and value not in (0, 1): + raise ValueError('Valeur booléenne attendue (0 ou 1).') + if data['kind'] == 'menu': + choices = [v['value'] for v in data['menu']] + if choices and value not in choices: + raise ValueError('Valeur absente des options de la caméra.') + minimum, maximum = data.get('min', 0), data.get('max', 1) + step = data.get('step', 1) + if not minimum <= value <= maximum or (value - minimum) % max(1, step): + raise ValueError(f'Valeur hors plage ({minimum} à {maximum}, pas {step}).') + return value + + +def _write_config(controls: dict) -> None: + path = _config_path() + path.parent.mkdir(parents=True, exist_ok=True) + snapshot = {name: props['value'] for name, props in controls.items() + if not props['inactive'] and 'value' in props} + fd = None + temp = None + try: + fd, temp = tempfile.mkstemp(prefix='.camera_', suffix='.tmp', dir=path.parent) + with os.fdopen(fd, 'w', encoding='utf-8') as stream: + fd = None + json.dump({'controls': snapshot}, stream, indent=2) + stream.write('\n') + os.replace(temp, path) + temp = None + except OSError as exc: + raise CameraSettingsError(f'Impossible de mémoriser les réglages caméra : {exc}') from exc + finally: + if fd is not None: + os.close(fd) + if temp is not None and os.path.exists(temp): + os.unlink(temp) + + +def update_setting(control: object, value: object) -> dict: + with _LOCK: + controls = _get_controls() + n = _validate(control, value, controls) + _run('--set-ctrl', f'{control}={n}') + refreshed = _get_controls() # réévalue les contrôles devenus actifs/inactifs + _write_config(refreshed) + return {'device': _device(), 'controls': refreshed} + + +def reset_defaults() -> dict: + """Restaure les valeurs exposées par le pilote, sans forcer les champs inactifs.""" + with _LOCK: + original = _get_controls() + # D'abord les modes auto, puis les contrôles restés actifs. + for name in [*_AUTO, *(c for c in original if c not in _AUTO)]: + live = _get_controls() + item = live.get(name) + if not item or item['inactive'] or 'default' not in item: + continue + try: + _run('--set-ctrl', f"{name}={item['default']}") + except CameraSettingsError as exc: + raise CameraSettingsError(f'Échec restauration {name} : {exc}') from exc + refreshed = _get_controls() + _write_config(refreshed) + return {'device': _device(), 'controls': refreshed} + + +def restore_saved_settings() -> None: + """Applique les valeurs persistées; une webcam absente ne bloque pas Flask.""" + with _LOCK: + path = _config_path() + if not path.is_file(): + return + try: + saved = json.loads(path.read_text(encoding='utf-8')).get('controls', {}) + if not isinstance(saved, dict): + raise ValueError('Format invalide') + for name in [*_AUTO, *(n for n in saved if n not in _AUTO)]: + if name not in saved: + continue + live = _get_controls() + try: + n = _validate(name, saved[name], live) + except ValueError: + continue # matériel/pilote modifié ou contrôle devenu inactif + _run('--set-ctrl', f'{name}={n}') + print('[ALFRED CAMERA] Réglages restaurés.', flush=True) + except (OSError, ValueError, CameraSettingsError) as exc: + print(f'[ALFRED CAMERA] Restauration ignorée : {exc}', flush=True) diff --git a/alfred/app/services/ha_views.py b/alfred/app/services/ha_views.py new file mode 100644 index 0000000..41e2bdf --- /dev/null +++ b/alfred/app/services/ha_views.py @@ -0,0 +1,236 @@ +"""Home Assistant data adapter for Alfred. Tokens remain on the Flask host.""" +from __future__ import annotations + +import os +import re +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import date, datetime, time, timedelta +from urllib.parse import quote, urlparse +from zoneinfo import ZoneInfo + +import requests + +TZ = ZoneInfo(os.getenv('ALFRED_TIMEZONE', 'Europe/Paris')) +CREATE = 1 +DELETE = 2 + +class HAError(RuntimeError): + pass + + +def _cfg(): + base = (os.getenv('HA_URL') or os.getenv('HOME_ASSISTANT_URL') or '').strip().rstrip('/') + token = (os.getenv('HA_TOKEN') or os.getenv('HOME_ASSISTANT_TOKEN') or '').strip() + if not base or not token: + raise HAError('Configurer HA_URL et HA_TOKEN dans l’environnement du service Alfred.') + if urlparse(base).scheme not in ('http','https'): + raise HAError('HA_URL doit commencer par http:// ou https://') + return base, token + + +def _request(method, path, *, params=None, payload=None, timeout=12): + base, token = _cfg() + try: + response = requests.request(method, base + path, + params=params, json=payload, + headers={'Authorization': 'Bearer '+token, 'Accept':'application/json'}, + timeout=timeout) + response.raise_for_status() + return response.json() if response.content else None + except requests.RequestException as exc: + code = exc.response.status_code if exc.response is not None else 'réseau' + raise HAError(f'Home Assistant indisponible ou action refusée ({code}).') from exc + + +def states(): + return _request('GET', '/api/states') + + +def _allowed(): + return {s.strip() for s in os.getenv('HA_CALENDARS', '').split(',') if s.strip()} + + +def list_calendars(): + raw = _request('GET', '/api/calendars') or [] + allow = _allowed() + state_map = {s.get('entity_id'):s for s in states() if s.get('entity_id','').startswith('calendar.')} + items = [] + for c in raw: + eid = str(c.get('entity_id','')) + if not eid.startswith('calendar.') or (allow and eid not in allow): + continue + st = state_map.get(eid, {}) + features = int((st.get('attributes') or {}).get('supported_features') or 0) + items.append({'entity_id':eid, 'name':c.get('name') or eid.split('.',1)[-1], + 'can_create':bool(features & CREATE), 'can_delete':bool(features & DELETE)}) + return sorted(items, key=lambda x:x['name'].casefold()) + + +def _parse_dt(raw): + # HA REST can return RFC3339 strings, date strings, or {date}/{dateTime} dictionaries. + if isinstance(raw, dict): + raw = raw.get('dateTime') or raw.get('date') + if not isinstance(raw, str) or not raw: + raise ValueError('Date de calendrier invalide') + if len(raw) == 10: + return datetime.combine(date.fromisoformat(raw), time.min, TZ), True + dt = datetime.fromisoformat(raw.replace('Z', '+00:00')) + return (dt.replace(tzinfo=TZ) if dt.tzinfo is None else dt.astimezone(TZ)), False + + +def _normalize_event(e, cal): + start, all_day = _parse_dt(e.get('start')) + end, _ = _parse_dt(e.get('end')) + return { + 'id':str(e.get('uid') or ''), 'recurrence_id':str(e.get('recurrence_id') or ''), + 'title':str(e.get('summary') or '(Sans titre)'), + 'description':str(e.get('description') or ''), + 'location':str(e.get('location') or ''), + 'start':start.isoformat(), 'end':end.isoformat(), 'all_day':all_day, + 'calendar_id':cal['entity_id'], 'calendar_name':cal['name'], + 'can_delete':cal['can_delete'] and bool(e.get('uid')), + 'source':'home_assistant' + } + + +def get_events(start:datetime, end:datetime, calendar_ids=None): + if start.tzinfo is None or end.tzinfo is None or start >= end: + raise ValueError('Période incorrecte') + if (end-start).days > 65: + raise ValueError('Période limitée à 65 jours') + available = list_calendars() + ids = set(calendar_ids or [c['entity_id'] for c in available]) + if not ids.issubset({c['entity_id'] for c in available}): + raise ValueError('Calendrier non autorisé') + selected = [c for c in available if c['entity_id'] in ids] + results, errors = [], [] + def load(cal): + raw = _request('GET', '/api/calendars/'+quote(cal['entity_id'], safe=''), + params={'start':start.isoformat(), 'end':end.isoformat()}) or [] + return [_normalize_event(item,cal) for item in raw] + with ThreadPoolExecutor(max_workers=min(5,len(selected)) or 1) as pool: + futures = {pool.submit(load,c):c for c in selected} + for f in as_completed(futures): + try: results.extend(f.result()) + except (HAError,ValueError,KeyError) as exc: errors.append(f"{futures[f]['name']}: {exc}") + results.sort(key=lambda e:(e['start'],e['title'].lower())) + return {'events':results, 'calendars':available, 'errors':errors, + 'start':start.isoformat(), 'end':end.isoformat()} + + +def _find_calendar(entity_id, capability): + cal = next((c for c in list_calendars() if c['entity_id'] == entity_id),None) + if cal is None: + raise HAError('Calendrier absent ou non autorisé.') + if not cal[capability]: + raise HAError('Ce calendrier ne permet pas cette opération depuis Home Assistant.') + return cal + + +def create_event(entity_id, title, start, end, description='', location='', all_day=False): + _find_calendar(entity_id, 'can_create') + if not title.strip() or len(title) > 200: + raise ValueError('Titre requis (200 caractères maximum).') + if start >= end: + raise ValueError('La fin doit être après le début.') + payload = {'entity_id':entity_id, 'summary':title.strip(), + 'description':description[:2000], 'location':location[:300]} + if all_day: + payload.update(start_date=start.date().isoformat(), end_date=end.date().isoformat()) + else: + payload.update(start_date_time=start.isoformat(), end_date_time=end.isoformat()) + _request('POST', '/api/services/calendar/create_event',payload=payload) + return {'ok':True,'message':'Événement ajouté.'} + + +def delete_event(entity_id, uid, recurrence_id=None): + _find_calendar(entity_id, 'can_delete') + if not isinstance(uid,str) or not uid.strip() or len(uid)>600: + raise ValueError('Identifiant d’événement manquant ou invalide.') + base, token = _cfg() + ws_url = ('wss://' if base.startswith('https://') else 'ws://') + base.split('://',1)[1] + '/api/websocket' + try: + from websocket import create_connection + except ImportError as exc: + raise HAError('Installer websocket-client dans le venv d’Alfred pour supprimer des événements.') from exc + import json + try: + ws = create_connection(ws_url, timeout=12) + try: + auth_required = json.loads(ws.recv()) + if auth_required.get('type') != 'auth_required': + raise HAError('WebSocket Home Assistant : négociation inattendue.') + ws.send(json.dumps({'type':'auth','access_token':token})) + auth = json.loads(ws.recv()) + if auth.get('type') != 'auth_ok': + raise HAError('Authentification WebSocket Home Assistant refusée.') + cmd = {'id':1, 'type':'calendar/event/delete','entity_id':entity_id,'uid':uid} + if recurrence_id: cmd['recurrence_id'] = recurrence_id + ws.send(json.dumps(cmd)) + result = json.loads(ws.recv()) + if result.get('id') != 1 or not result.get('success'): + error = result.get('error') or {} + raise HAError('Suppression refusée par Home Assistant : '+str(error.get('message') or error)) + return {'ok':True, 'message':'Événement supprimé.'} + finally: + ws.close() + except (OSError,TimeoutError) as exc: + raise HAError('Impossible de joindre le WebSocket Home Assistant.') from exc + + +def get_temperatures(): + data=[] + for item in states(): + entity=item.get('entity_id',''); attrs=item.get('attributes') or {} + if (entity.startswith('sensor.') and attrs.get('device_class') == 'temperature') or entity.startswith('climate.'): + raw = attrs.get('current_temperature') if entity.startswith('climate.') else item.get('state') + try: value = float(raw) + except (TypeError,ValueError):continue + if not -50 <= value <= 90:continue + data.append({'name':attrs.get('friendly_name',entity), 'value':value, + 'unit':attrs.get('unit_of_measurement') or '°C','entity_id':entity}) + return sorted(data, key=lambda x:x['name'].lower())[:60] + + +def list_cameras(): + allow = {s.strip() for s in os.getenv('HA_CAMERAS','').split(',') if s.strip()} + return [{'entity_id':item['entity_id'], 'name':item.get('attributes',{}).get('friendly_name') or item['entity_id']} + for item in states() + if item.get('entity_id','').startswith('camera.') and + (not allow or item['entity_id'] in allow)][:30] + + +def camera_snapshot(entity_id): + allowed = {item['entity_id'] for item in list_cameras()} + if entity_id not in allowed: + raise HAError('Caméra non autorisée.') + base, token = _cfg() + try: + r=requests.get(base+'/api/camera_proxy/'+quote(entity_id,safe=''), + headers={'Authorization':'Bearer '+token},timeout=15) + r.raise_for_status() + if not r.headers.get('Content-Type','').startswith('image/'): + raise HAError('La caméra ne fournit pas une image JPEG/PNG.') + return r.content,r.headers['Content-Type'] + except requests.RequestException as exc: + raise HAError('Impossible de récupérer l’image de la caméra.') from exc + + +def temperature_history(entity_id, hours=24): + hours=max(1,min(int(hours),72)) + if entity_id not in {e['entity_id'] for e in get_temperatures()}: + raise HAError('Capteur non autorisé.') + end=datetime.now(TZ);start=end-timedelta(hours=hours) + raw=_request('GET','/api/history/period/'+quote(start.isoformat(),safe=''), + params={'filter_entity_id':entity_id,'end_time':end.isoformat(), + 'minimal_response':'true'},timeout=18) or [] + out=[] + for collection in raw: + for point in collection: + try: + value=float(point['state']) + moment=point.get('last_changed') or point.get('last_updated') + if -50 <= value <= 90 and moment: + out.append({'time':moment,'value':value}) + except (ValueError,TypeError,KeyError):continue + return out[-250:] diff --git a/alfred/app/services/ha_weather.py b/alfred/app/services/ha_weather.py new file mode 100644 index 0000000..b217e6b --- /dev/null +++ b/alfred/app/services/ha_weather.py @@ -0,0 +1,230 @@ +"""Read-only weather adapter for Alfred: use existing HA_URL / HA_TOKEN. + +Home Assistant stores current conditions in weather entities. Forecasts must be +retrieved separately with the weather.get_forecasts action (return_response). +""" +from __future__ import annotations + +import os +import re +from datetime import datetime, timedelta, time +from zoneinfo import ZoneInfo + +from . import ha_views as ha +from . import alfred_intents as intents + +TZ = ZoneInfo(os.getenv('ALFRED_TIMEZONE', 'Europe/Paris')) + +CONDITIONS = { + 'clear-night': 'ciel dégagé', 'cloudy': 'ciel couvert', + 'exceptional': 'conditions exceptionnelles', 'fog': 'brouillard', + 'hail': 'grêle', 'lightning': 'orages', 'lightning-rainy': 'orages et pluie', + 'partlycloudy': 'éclaircies', 'pouring': 'fortes pluies', + 'rainy': 'pluie', 'snowy': 'neige', 'snowy-rainy': 'pluie et neige', + 'sunny': 'ensoleillé', 'windy': 'vent', 'windy-variant': 'vent et nuages', +} +RAIN = {'rainy', 'pouring', 'lightning-rainy', 'snowy-rainy'} + + +def is_weather_request(text: str, previous_view: str | None = None) -> bool: + t = intents.simplify(str(text or '')) + if re.search(r'\b(meteo|previsions?|previsions? meteorologiques?|previsions? du temps|pluie|pleuvoir|pleuvra|pleut|averse|averses|orage|orages|enneigement|neige|neiger|soleil|ensoleille|ensoleillee|parapluie|quel temps|quelle meteo|beau temps|mauvais temps)\b', t): + return True + if re.search(r'\b(temperature|temperatures|combien de degres)\b.{0,24}\b(dehors|exterieur|exterieure|exterieures)\b', t): + return True + if re.search(r'\b(fait.il|fera.t.il|il fera|il fait)\b.{0,15}\b(beau|froid|chaud|dehors)\b', t): + return True + if re.search(r'\btemperature\b', t) and re.search(r'\b(demain|week.end|semaine|exterieure?|dehors|aujourd.hui)\b', t) and not re.search(r'\b(salon|chambre|cuisine|maison|bureau|salle de bain)\b',t): + return True + # Follow-ups are interpreted in the active view, not with old Hermes prompts. + if previous_view == 'weather' and len(t) < 42 and re.search(r'\b(demain|aujourd.hui|matin|apres.midi|soir|week.end|semaine|jours?|maintenant|actuellement)\b', t): + return True + return False + + +def _selected_weather_entity(): + entries = [s for s in ha.states() if str(s.get('entity_id', '')).startswith('weather.')] + configured = os.getenv('HA_WEATHER_ENTITY', '').strip() + if configured: + match = next((e for e in entries if e['entity_id'] == configured), None) + if not match: + raise ha.HAError(f"Entité météo {configured} introuvable dans Home Assistant.") + return match + if not entries: + raise ha.HAError('Aucune entité weather.* trouvée dans Home Assistant. Ajouter une intégration météo.') + # Prefer a conventional home weather entity, otherwise use the first available. + return next((e for e in entries if e.get('entity_id') in ('weather.home', 'weather.forecast_home')), entries[0]) + + +def _safe_number(value): + try: + if value is None or isinstance(value, bool): + return None + return round(float(value), 1) + except (ValueError, TypeError): + return None + + +def _forecast(entity_id: str, typ: str): + try: + raw = ha._request('POST', '/api/services/weather/get_forecasts?return_response', + payload={'entity_id': entity_id, 'type': typ}, timeout=16) + response = raw.get('service_response', raw) if isinstance(raw, dict) else {} + ent = response.get(entity_id, {}) if isinstance(response, dict) else {} + items = ent.get('forecast', []) if isinstance(ent, dict) else [] + if not isinstance(items, list): + return [], 'Format de prévision météo inattendu.' + normalized = [] + for item in items: + if not isinstance(item, dict): + continue + raw_dt = item.get('datetime') + if not raw_dt: + continue + try: + dt = datetime.fromisoformat(str(raw_dt).replace('Z', '+00:00')) + if dt.tzinfo is None: + dt = dt.replace(tzinfo=TZ) + dt = dt.astimezone(TZ) + except ValueError: + continue + normalized.append({ + 'datetime': dt.isoformat(), 'date': dt.date().isoformat(), + 'condition': str(item.get('condition') or 'unknown'), + 'description': CONDITIONS.get(str(item.get('condition')), 'conditions non précisées'), + 'temperature': _safe_number(item.get('temperature')), + 'templow': _safe_number(item.get('templow')), + 'precipitation_probability': _safe_number(item.get('precipitation_probability')), + 'precipitation': _safe_number(item.get('precipitation')), + 'wind_speed': _safe_number(item.get('wind_speed')), + }) + return normalized, None + except ha.HAError as exc: + # Some providers have daily only / hourly only forecasts. + return [], f'Prévisions {typ} indisponibles : {exc}' + + +def get_weather(text: str = "") -> dict: + item = _selected_weather_entity() + attrs = item.get('attributes') or {} + eid = item['entity_id'] + current = { + 'condition': str(item.get('state') or 'unknown'), + 'description': CONDITIONS.get(str(item.get('state')), 'conditions non précisées'), + 'temperature': _safe_number(attrs.get('temperature')), + 'apparent_temperature': _safe_number(attrs.get('apparent_temperature')), + 'humidity': _safe_number(attrs.get('humidity')), + 'wind_speed': _safe_number(attrs.get('wind_speed')), + 'pressure': _safe_number(attrs.get('pressure')), + } + daily, err_daily = _forecast(eid, 'daily') + hourly, err_hourly = _forecast(eid, 'hourly') + label, start, end, kind = _target(text, datetime.now(TZ)) if text else ('aujourd’hui', datetime.combine(datetime.now(TZ).date(), time.min, tzinfo=TZ), datetime.combine(datetime.now(TZ).date(), time.min, tzinfo=TZ) + timedelta(days=1), 'day') + focus_date = start.date().isoformat() + return { + 'type': 'weather', 'entity_id': eid, + 'name': str(attrs.get('friendly_name') or eid), + 'temperature_unit': str(attrs.get('temperature_unit') or '°C'), + 'wind_speed_unit': str(attrs.get('wind_speed_unit') or 'km/h'), + 'current': current, 'daily': daily, 'hourly': hourly, + 'errors': [e for e in (err_daily, err_hourly) if e], + 'source': 'home_assistant', 'updated_at': datetime.now(TZ).isoformat(), + 'focus_label': label, 'focus_date': focus_date, 'focus_kind': kind, + } + + +def _target(text: str, now: datetime) -> tuple[str, datetime, datetime, str]: + t = intents.simplify(text) + start = datetime.combine(now.date(), time.min, tzinfo=TZ) + label = 'actuellement' + days = re.search(r'\b(?:dans|sur|pour)\s+(\d{1,2})\s+jours?\b', t) + if days and 'dans ' in days.group(0): + d = min(int(days.group(1)), 14) + start += timedelta(days=d) + label = f'dans {d} jours' + elif 'week-end' in text.lower() or 'weekend' in t or 'week.end' in t: + offset = (5 - now.weekday()) % 7 + start += timedelta(days=offset) + label = 'ce week-end' + return label, start, start + timedelta(days=2), 'weekend' + else: + day = intents.parse_day(text, now) + if day is not None: + start = datetime.combine(day, time.min, tzinfo=TZ) + label = 'demain' if day == now.date()+timedelta(days=1) else ('aujourd’hui' if day == now.date() else day.strftime('le %d/%m')) + kind = 'day' + if 'ce matin' in t or 'demain matin' in t: + kind = 'hours';start = start.replace(hour=6);end = start.replace(hour=12) + elif 'apres-midi' in t: + kind = 'hours';start = start.replace(hour=12);end = start.replace(hour=18) + elif 'soir' in t: + kind = 'hours';start = start.replace(hour=18);end = start + timedelta(hours=6) + else: + end = start + timedelta(days=1) + return label, start, end, kind + + +def _rounded_temp(n, unit='°C'): + return f'{n:g} {unit}' if n is not None else 'température indisponible' + + +def describe(data: dict, text: str = '') -> str: + """Concise voice response based on actual forecast data, never invented.""" + now = datetime.now(TZ) + t = intents.simplify(text) + unit = data['temperature_unit'] + label, start, end, kind = _target(text, now) + weather_question = bool(re.search(r'\b(pleut|pleuvra|pleuvoir|pluie|averses|parapluie)\b', t)) + days_question = bool(re.search(r'\b(semaine|7 jours|sept jours)\b', t)) + now_question = (start.date() == now.date() and kind == 'day' + and not re.search(r'\b(demain|jour|mois|week|semaine)\b', t)) + is_now_explicit = bool(re.search(r'\b(maintenant|en ce moment|actuellement)\b',t)) + if is_now_explicit and weather_question: + c=data['current'] + raining = c['condition'] in RAIN + return ('Oui, les conditions actuelles indiquent de la pluie.' if raining + else f"Actuellement, {c['description']}.") + if now_question and not weather_question and not days_question: + c=data['current'] + return f"Actuellement, {c['description']}, {_rounded_temp(c['temperature'],unit)}." + if days_question: + if data['daily']: + return f"Voici les prévisions météorologiques des prochains jours, selon {data['name']}." + return 'Les prévisions journalières ne sont pas disponibles dans Home Assistant.' + candidates = [h for h in data['hourly'] if start <= datetime.fromisoformat(h['datetime']) < end] + forecasts = (candidates if kind == 'hours' and candidates else + [d for d in data['daily'] if start.date() <= datetime.fromisoformat(d['datetime']).date() + < (end.date() if end.time() == time.min else end.date() + timedelta(days=1))]) + # A missing hourly forecast must not masquer itself as a reliable part-of-day prediction. + approximate = kind == 'hours' and not candidates and bool(forecasts) + if not forecasts: + if label == 'actuellement': + return f"Actuellement, {data['current']['description']}, {_rounded_temp(data['current']['temperature'], unit)}." + return f"Je n'ai pas de prévision disponible pour {label}." + if weather_question: + prob = [f['precipitation_probability'] for f in forecasts if f['precipitation_probability'] is not None] + if prob: + peak = max(prob) + response = f"Pour {label}, la probabilité de précipitations atteint {peak:g} %." + elif any(f['condition'] in RAIN for f in forecasts): + response = f"La météo prévoit de la pluie pour {label}." + else: + response = f"Aucune pluie n'est indiquée par les prévisions disponibles pour {label}, mais le risque exact n'est pas connu." + else: + first = forecasts[0] + temps = [f['temperature'] for f in forecasts if f['temperature'] is not None] + lows = [f['templow'] for f in forecasts if f['templow'] is not None] + descriptor = first['description'] + if len(forecasts) > 1 and len({f['condition'] for f in forecasts}) > 1: + descriptor = 'des conditions variables' + if kind == 'hours' and candidates and temps: + response = f"Pour {label}, {descriptor}, entre {_rounded_temp(min(temps),unit)} et {_rounded_temp(max(temps),unit)}." + elif temps: + response = f"Pour {label}, {descriptor}, jusqu'à {_rounded_temp(max(temps),unit)}." + if lows: + response += f" Minimum {_rounded_temp(min(lows),unit)}." + else: + response = f"Pour {label}, {descriptor}, mais la température n'est pas disponible." + if approximate: + response += ' Cette indication est journalière, faute de prévision horaire.' + return response diff --git a/alfred/app/services/weather_outfits.py b/alfred/app/services/weather_outfits.py new file mode 100644 index 0000000..c75e321 --- /dev/null +++ b/alfred/app/services/weather_outfits.py @@ -0,0 +1,688 @@ +"""Outfit recommendations and ComfyUI illustration for Alfred weather view. + +V4 improvements: +- refresh really regenerates a new image (new random seeds, cache bypass); +- best-of generation with lightweight vision review; +- more reliable accessory rendering by limiting visible accessories in the image; +- still generates the child and adult separately, then assembles them in one image. +""" +from __future__ import annotations + +import base64 +import hashlib +import io +import json +import os +import threading +import time +import uuid +import random +import secrets +import logging +from datetime import datetime, date as Date +from collections import Counter +from pathlib import Path +from urllib.parse import urlencode + +import requests +from dotenv import load_dotenv, dotenv_values + +# Charger l'environnement avant de calculer BASE et WORKFLOWS_DIR. +# Flask exécute son load_dotenv() après l'import de ce module. +load_dotenv() +load_dotenv('/opt/hermes/MCP_comfyui/.env', override=False) +# Fichier de réglages météo indépendant de systemd et du MCP. +_SETTINGS_FILE = Path(__file__).resolve().parents[1] / 'config' / 'weather_outfits.env' +if _SETTINGS_FILE.is_file(): + load_dotenv(_SETTINGS_FILE, override=True) +_MCP_SETTINGS = dotenv_values('/opt/hermes/MCP_comfyui/.env') + +from . import ha_weather + +CACHE_DIR = Path(os.getenv('ALFRED_CACHE_DIR', str(Path(__file__).resolve().parents[1] / 'cache'))) +CACHE_DIR.mkdir(parents=True, exist_ok=True) +CACHE_FILE = CACHE_DIR / 'weather_outfit_cache.json' +BASE = (os.getenv('ALFRED_COMFYUI_URL') or _MCP_SETTINGS.get('COMFYUI_URL') or os.getenv('COMFYUI_URL') or 'http://192.168.1.13:8188').rstrip('/') +WORKFLOWS_DIR = Path(os.getenv('ALFRED_COMFYUI_WORKFLOWS_DIR') or os.getenv('WORKFLOWS_DIR') or '/opt/hermes/MCP_comfyui/workflows') +PROMPT_NODE = os.getenv('COMFY_PROMPT_NODE', '6') +TIMEOUT = int(os.getenv('COMFYUI_TIMEOUT', '300')) +TTL_HOURS = int(os.getenv('ALFRED_WEATHER_ILLUSTRATION_TTL_HOURS', '12')) +ENABLED = os.getenv('ALFRED_WEATHER_ILLUSTRATIONS', 'true').lower() in ('1', 'true', 'yes', 'on') +IMAGE_SIZE = min(1024, max(512, int(os.getenv('ALFRED_WEATHER_COMFY_SIZE', '768')))) +PROMPT_VERSION = 'outfits-v5-reference-20261025' +MAX_ATTEMPTS_PER_CHARACTER = max(1, min(3, int(os.getenv('ALFRED_WEATHER_ILLUSTRATION_ATTEMPTS', '2')))) +QUALITY_TARGET = float(os.getenv('ALFRED_WEATHER_ILLUSTRATION_TARGET_SCORE', '8.6')) +REVIEW_ENABLED = os.getenv('ALFRED_WEATHER_REVIEW', 'true').lower() in ('1','true','yes','on') +REFERENCE_MODE = os.getenv('ALFRED_WEATHER_REFERENCE_MODE', 'auto').strip().lower() +REFERENCE_DENOISE = min(.85, max(.25, float(os.getenv('ALFRED_WEATHER_REFERENCE_DENOISE', '0.56')))) +PRESERVE_FACE = os.getenv('ALFRED_WEATHER_PRESERVE_FACE', 'true').lower() in ('1','true','yes','on') +ACCESSORY_LIMIT = max(0, min(2, int(os.getenv('ALFRED_WEATHER_ACCESSORY_LIMIT', '1')))) +REFERENCE_DIR = Path(__file__).resolve().parents[1] / 'static' / 'assets' / 'weather' +UPLOADED_REFERENCES = {} + +# Un seul job génération météo à la fois : évite les doublons concurrents. +ILLUSTRATION_LOCK = threading.RLock() +IMAGE_DIR = CACHE_DIR / 'weather_outfit_images' +IMAGE_DIR.mkdir(parents=True, exist_ok=True) + +# Deux portraits distincts, réunis dans une seule image finale. +PORTRAIT_WIDTH = min(512, IMAGE_SIZE) +PORTRAIT_HEIGHT = IMAGE_SIZE +DISPLAY_SIZE = int(os.getenv('ALFRED_WEATHER_DISPLAY_SIZE', '350')) + + +def _load_cache() -> dict: + try: + return json.loads(CACHE_FILE.read_text(encoding='utf-8')) + except Exception: + return {} + + +def _save_cache(cache: dict) -> None: + CACHE_FILE.write_text(json.dumps(cache, ensure_ascii=False, indent=2), encoding='utf-8') + + +def _pick_day(data: dict, forced_date: str | None = None) -> dict: + target = str(forced_date or data.get('focus_date') or '').strip() + try: + requested = Date.fromisoformat(target) + except ValueError as exc: + raise ValueError('Date météo invalide : format AAAA-MM-JJ attendu.') from exc + + for day in data.get('daily', []): + if str(day.get('date')) == target: + return day + + matching = [h for h in data.get('hourly', []) if h.get('date') == target] + if matching: + def available(key): + return [float(h[key]) for h in matching if h.get(key) is not None] + temps = available('temperature') + chances = available('precipitation_probability') + winds = available('wind_speed') + conditions = [str(h['condition']) for h in matching if h.get('condition')] + condition = Counter(conditions).most_common(1)[0][0] if conditions else 'unknown' + return { + 'date': target, + 'condition': condition, + 'description': ha_weather.CONDITIONS.get(condition, 'conditions non précisées'), + 'temperature': max(temps) if temps else None, + 'templow': min(temps) if temps else None, + 'precipitation_probability': max(chances) if chances else None, + 'wind_speed': max(winds) if winds else None, + } + + if requested == datetime.now(ha_weather.TZ).date(): + current = data.get('current', {}) + return { + 'date': target, + 'condition': current.get('condition', 'unknown'), + 'description': current.get('description', 'conditions non précisées'), + 'temperature': current.get('temperature'), + 'templow': current.get('temperature'), + 'precipitation_probability': None, + 'wind_speed': current.get('wind_speed'), + } + raise ValueError(f'Aucune prévision Home Assistant disponible pour le {requested:%d/%m/%Y}. Aucune image ne sera générée avec les données d’un autre jour.') + + +def _temp_band(high: float | None, low: float | None) -> str: + ref = high if high is not None else low + if ref is None: + return 'mild' + if ref < 6: + return 'cold' + if ref < 14: + return 'cool' + if ref < 23: + return 'mild' + return 'warm' + + +def _recommend(day: dict) -> dict: + high = day.get('temperature') + low = day.get('templow') + pop = day.get('precipitation_probability') + wind = day.get('wind_speed') + cond = str(day.get('condition') or '') + band = _temp_band(high, low) + rainy = cond in {'rainy', 'pouring', 'lightning-rainy', 'snowy-rainy'} or (pop is not None and pop >= 45) + sunny = cond in {'sunny', 'partlycloudy'} + windy = wind is not None and wind >= 25 + + child = [] + adult = [] + child_acc = [] + adult_acc = [] + + if band == 'cold': + child += ['doudoune chaude', 'pantalon chaud', 'chaussures fermées'] + adult += ['manteau chaud', 'pull', 'pantalon', 'chaussures fermées'] + child_acc += ['bonnet', 'écharpe'] + adult_acc += ['écharpe'] + elif band == 'cool': + child += ['veste légère', 'pantalon', 'baskets'] + adult += ['veste mi-saison', 'pull fin', 'jean', 'chaussures fermées'] + elif band == 'mild': + child += ['sweat ou gilet', 'pantalon léger', 'baskets'] + adult += ['veste légère', 'haut confortable', 'jean ou chino', 'baskets'] + else: + child += ['t-shirt', 'short ou pantalon léger', 'baskets légères'] + adult += ['t-shirt ou polo', 'tenue légère', 'chaussures légères'] + child_acc += ['casquette'] + adult_acc += ['lunettes de soleil'] + + if rainy: + child_acc += ['imperméable', 'parapluie'] + adult_acc += ['parapluie'] + if 'chaussures fermées' not in child: child.append('chaussures fermées') + if 'chaussures fermées' not in adult: adult.append('chaussures fermées') + if windy: + child_acc += ['coupe-vent'] + adult_acc += ['coupe-vent'] + if sunny and band != 'cold': + if 'casquette' not in child_acc: child_acc += ['casquette'] + if 'lunettes de soleil' not in adult_acc: adult_acc += ['lunettes de soleil'] + + def uniq(seq): + seen=[] + for x in seq: + if x not in seen: seen.append(x) + return seen + + return { + 'summary': day.get('description', 'Conditions du jour'), + 'child': { + 'title': 'Fille · 8 ans', + 'clothes': uniq(child), + 'accessories': uniq(child_acc), + }, + 'adult': { + 'title': 'Homme · 42 ans', + 'clothes': uniq(adult), + 'accessories': uniq(adult_acc), + } + } + + +def _weather_signature(day: dict, outfits: dict) -> str: + raw = json.dumps({'version': PROMPT_VERSION, 'date': day.get('date'), + 'day': day, 'outfits': outfits, 'size': IMAGE_SIZE}, + ensure_ascii=False, sort_keys=True) + return hashlib.sha256(raw.encode('utf-8')).hexdigest() + + +def _extract_images(history: dict) -> list[dict]: + images = [] + for node_id, output in history.get('outputs', {}).items(): + for image in output.get('images', []): + filename = image.get('filename') + if not filename: + continue + subfolder = image.get('subfolder', '') + image_type = image.get('type', 'output') + query = urlencode({'filename': filename, 'subfolder': subfolder, 'type': image_type}) + images.append({'filename': filename, 'url': f'{BASE}/view?{query}', 'node_id': str(node_id)}) + return images + + +NEGATIVE_PROMPT = ( + 'more than one person, group photo, two people, extra person, background crowd, ' + 'mirror reflection, human silhouette in background, duplicate heads, cloned body, ' + 'deformed face, malformed face, mutated hands, extra limbs, extra fingers, ' + 'disfigured features, asymmetrical face, photorealistic, horror, blurry, ' + 'text, captions, logos, watermark, oversized accessory, floating accessory, ' + 'extra umbrella, duplicated bag, disconnected hands, duplicate limbs, ' + 'accessories not mentioned, extra props, toys, clothing mannequin' +) + + +def _random_seed() -> int: + return secrets.randbelow(2**32 - 1) + 1 + + +def _prepare_workflow(workflow: dict, prompt: str, force_new_seed: bool = False, reference_name: str | None = None) -> dict: + node = workflow.get(PROMPT_NODE) + if not isinstance(node, dict) or not isinstance(node.get('inputs'), dict) or 'text' not in node['inputs']: + raise ValueError(f'Nœud de prompt ComfyUI {PROMPT_NODE} introuvable ou incompatible.') + node['inputs']['text'] = prompt + + for item in workflow.values(): + inputs = item.get('inputs') or {} + if item.get('class_type') == 'CLIPTextEncode' and 'text' in inputs: + # negative prompt link target handled below when referenced by sampler. + pass + if force_new_seed: + for key in ('seed', 'noise_seed'): + if key in inputs and isinstance(inputs[key], (int, float)): + inputs[key] = _random_seed() + if item.get('class_type') in ('RandomNoise', 'Noise') and force_new_seed: + for key in ('noise_seed', 'seed'): + if key in inputs and isinstance(inputs[key], (int, float)): + inputs[key] = _random_seed() + + for sampler in workflow.values(): + if sampler.get('class_type') not in ('KSampler', 'KSamplerAdvanced'): + continue + inputs = sampler.get('inputs') or {} + if force_new_seed: + for key in ('seed', 'noise_seed'): + if key in inputs and isinstance(inputs[key], (int, float)): + inputs[key] = _random_seed() + negative_link = inputs.get('negative') + if not isinstance(negative_link, (list, tuple)) or not negative_link: + continue + neg = workflow.get(str(negative_link[0])) + if neg and neg.get('class_type') == 'CLIPTextEncode' and 'text' in neg.get('inputs', {}): + neg['inputs']['text'] = NEGATIVE_PROMPT + for latent in workflow.values(): + if latent.get('class_type') in ('EmptyLatentImage', 'EmptySD3LatentImage'): + inputs = latent.get('inputs') or {} + if 'width' in inputs and 'height' in inputs: + inputs['width'] = PORTRAIT_WIDTH + inputs['height'] = PORTRAIT_HEIGHT + # Conversion en image-vers-image : uniquement si le workflow expose des liens + # KSampler et VAEEncode compatibles. Le JSON source de Hermes est INCHANGÉ. + if reference_name: + _inject_reference_img2img(workflow, reference_name) + return workflow + + +def _new_node_id(workflow: dict) -> str: + return str(max([int(k) for k in workflow if str(k).isdigit()] + [0]) + 1) + + +def _inject_reference_img2img(workflow: dict, reference_name: str) -> None: + # Gestion prudente des workflows courants SD 1.5 / SDXL (KSampler). + sampler = next((n for n in workflow.values() + if n.get('class_type') == 'KSampler' and isinstance(n.get('inputs', {}).get('latent_image'), list)), None) + decoder = next((n for n in workflow.values() if n.get('class_type') == 'VAEDecode' + and isinstance(n.get('inputs', {}).get('vae'), list)), None) + if sampler is None or decoder is None: + raise ValueError('Workflow non compatible img2img automatique (KSampler/VAEDecode requis).') + vae_link = decoder['inputs']['vae'] + load_id = _new_node_id(workflow) + workflow[load_id] = {'class_type': 'LoadImage', 'inputs': {'image': reference_name}} + vae_id = _new_node_id(workflow) + workflow[vae_id] = {'class_type': 'VAEEncode', + 'inputs': {'pixels': [load_id, 0], 'vae': vae_link}} + sampler['inputs']['latent_image'] = [vae_id, 0] + sampler['inputs']['denoise'] = REFERENCE_DENOISE + + +def _upload_reference(path: Path) -> str: + # Téléverse l’image référence vers ComfyUI, retourne son nom LoadImage. + cache_key = (str(path), path.stat().st_mtime_ns) + if cache_key in UPLOADED_REFERENCES: + return UPLOADED_REFERENCES[cache_key] + with path.open('rb') as stream: + response = requests.post(f'{BASE}/upload/image', files={'image': (path.name, stream, 'image/png')}, + data={'overwrite': 'true'}, timeout=30) + response.raise_for_status() + item = response.json() + name = str(item.get('name') or '') + subfolder = str(item.get('subfolder') or '') + if not name or '/' in name or '\\' in name: + raise RuntimeError('Réponse upload ComfyUI invalide : nom de fichier de référence absent.') + result = (subfolder.strip('/') + '/' if subfolder else '') + name + UPLOADED_REFERENCES[cache_key] = result + return result + + +def _submit_workflow(prompt: str, force_new_seed: bool = True, + kind: str | None = None) -> tuple[str, bool]: + workflow_path = WORKFLOWS_DIR / 'text_to_image.json' + if not workflow_path.exists(): + raise RuntimeError(f'Workflow ComfyUI introuvable : {workflow_path}') + workflow = json.loads(workflow_path.read_text(encoding='utf-8')) + reference_applied = False + reference_name = None + if kind and REFERENCE_MODE != 'off': + reference_file = REFERENCE_DIR / f'{kind}-reference.png' + if reference_file.is_file(): + try: + # D'abord valider la capacité img2img, avant upload. + preview = _prepare_workflow(json.loads(workflow_path.read_text(encoding='utf-8')), + prompt, force_new_seed=False, reference_name='check.png') + del preview + reference_name = _upload_reference(reference_file) + reference_applied = True + except (ValueError, requests.RequestException, OSError) as exc: + if REFERENCE_MODE == 'required': + raise RuntimeError('Référence obligatoire indisponible : ' + str(exc)) from exc + print('[ALFRED METEO] Référence img2img non disponible ; repli texte : ' + str(exc), flush=True) + workflow = _prepare_workflow(workflow, prompt, force_new_seed=force_new_seed, + reference_name=reference_name) + response = requests.post(f'{BASE}/prompt', json={'prompt': workflow, 'client_id': str(uuid.uuid4())}, timeout=30) + if not response.ok and reference_applied and REFERENCE_MODE == 'auto': + print('[ALFRED METEO] Workflow img2img rejeté ; nouvel essai texte seul.', flush=True) + workflow = _prepare_workflow(json.loads(workflow_path.read_text(encoding='utf-8')), + prompt, force_new_seed=True, reference_name=None) + response = requests.post(f'{BASE}/prompt', json={'prompt': workflow, 'client_id': str(uuid.uuid4())}, timeout=30) + reference_applied = False + response.raise_for_status() + return response.json()['prompt_id'], reference_applied + + +def _wait_history(prompt_id: str) -> dict: + deadline = time.monotonic() + TIMEOUT + while time.monotonic() < deadline: + response = requests.get(f'{BASE}/history/{prompt_id}', timeout=15) + response.raise_for_status() + result = response.json() + if prompt_id in result: + return result[prompt_id] + time.sleep(1) + raise RuntimeError(f'ComfyUI timeout ({prompt_id})') + + +def _primary_accessories(kind: str, outfits: dict) -> list[str]: + accessories = list(outfits[kind]['accessories']) + ordered = [] + priorities = ['parapluie', 'imperméable', 'bonnet', 'écharpe', 'coupe-vent', 'casquette', 'lunettes de soleil'] + for wanted in priorities: + if wanted in accessories and wanted not in ordered: + ordered.append(wanted) + for acc in accessories: + if acc not in ordered: + ordered.append(acc) + return ordered[:ACCESSORY_LIMIT] + + +def _single_character_prompt(kind: str, day: dict, outfits: dict) -> str: + profile = outfits[kind] + if kind == 'child': + identity = ( + 'ONE young GIRL, eight years old, clearly a primary-school child, ' + 'small body proportions of a young child, friendly and natural face, ' + 'casual appropriate haircut, no grown-up features. ' + ) + else: + identity = ( + 'ONE adult MAN, forty-two years old, unmistakably masculine adult, ' + 'short neat hair, mature male face, optionally light stubble, ' + 'adult male shoulders and body proportions, not a teenager. ' + ) + weather = str(day.get('description') or 'daily weather') + primary_acc = _primary_accessories(kind, outfits) + image_acc = ', '.join(primary_acc) if primary_acc else 'none' + return ( + 'Single character fashion illustration for a family weather dashboard. ' + 'Exactly ONE character, standing ALONE. ' + + identity + + 'Full body visible from head to shoes, centrally positioned, front three-quarter view, ' + 'generous empty space around body, relaxed neutral standing pose, smiling gently. ' + 'Soft polished 2D digital illustration, smooth clean outlines, tasteful pastel colors, ' + 'slight dimensional shading, consistent editorial children-book illustration style, ' + 'simple light neutral plain studio background, no other people, no human reflections, ' + 'no poster, no photographs, no text. ' + f'Weather for {day.get("date", "selected day")}: {weather}. ' + f'Clothing: {", ".join(profile["clothes"])}. ' + f'Visible accessories to show clearly: {image_acc}. ' + 'Show at most ONE prop or wearable accessory, and only if specified; ' + 'no extra bags, umbrellas, pets, garments floating in the air or background props. ' + 'Keep exactly the same recognizable face, hair style and hair color as the input reference image when provided. ' + 'Correct age and correct body proportions. Clothing must be clearly visible. ' + 'High priority: one person only, clean natural face, no deformations.' + ) + + +def _render_image_from_url(url: str): + from PIL import Image + response = requests.get(url, timeout=45) + response.raise_for_status() + if len(response.content) > 18_000_000: + raise RuntimeError('Image ComfyUI trop volumineuse (limite 18 Mo).') + with Image.open(io.BytesIO(response.content)) as raw: + raw.load() + return raw.convert('RGB') + + +def _vision_review(kind: str, prompt_summary: str, image_url: str) -> dict: + """Évalue une image via VLM ComfyUI si le workflow vision le permet. + + IMPORTANT: LoadImage attend un *fichier d'entrée*, pas une URL HTTP /view. + """ + fallback = {'score': 0.0, 'ok': False, 'issues': ['review_unavailable'], 'reviewed': False} + if not REVIEW_ENABLED: + return fallback + workflow_path = WORKFLOWS_DIR / 'vision_analyze.json' + if not workflow_path.exists(): + return fallback + image_node = os.getenv('COMFY_VISION_IMAGE_NODE', '1') + prompt_node = os.getenv('COMFY_VISION_PROMPT_NODE', '2') + try: + workflow = json.loads(workflow_path.read_text(encoding='utf-8')) + node = workflow[str(image_node)] + if node.get('class_type') not in ('LoadImage', 'LoadImageMask'): + # Éviter un faux diagnostic sur un workflow VLM non standard. + return fallback + response_image = requests.get(image_url, timeout=45) + response_image.raise_for_status() + if len(response_image.content) > 18_000_000: + return fallback + filename = 'alfred-weather-review-' + uuid.uuid4().hex[:12] + '.png' + response_upload = requests.post(f'{BASE}/upload/image', + files={'image': (filename, io.BytesIO(response_image.content), 'image/png')}, + data={'overwrite': 'true'}, timeout=30) + response_upload.raise_for_status() + remote = response_upload.json() + name = str(remote.get('name') or filename) + folder = str(remote.get('subfolder') or '') + node['inputs']['image'] = ((folder.strip('/') + '/') if folder else '') + name + review_prompt = ( + 'Review a single-person illustrated weather outfit. ' + f'Expected: {prompt_summary}. ' + 'Answer only JSON with fields: people_count (integer), gender_age_ok (boolean), ' + 'face_ok (boolean), accessories_ok (boolean), deformation (boolean), ' + 'score (number 0 to 10), issues (list of short strings). ' + 'Score harshly for extra people, bad anatomy or wrong-age character.' + ) + workflow[str(prompt_node)]['inputs']['text'] = review_prompt + response = requests.post(f'{BASE}/prompt', json={'prompt': workflow, + 'client_id': str(uuid.uuid4())}, timeout=30) + response.raise_for_status() + prompt_id = response.json()['prompt_id'] + history = _wait_history(prompt_id) + raw_text = None + for output in history.get('outputs', {}).values(): + for key in ('text', 'texts', 'result', 'string'): + if key in output: + raw_text = output[key] + break + if raw_text is not None: + break + if isinstance(raw_text, list): + raw_text = raw_text[0] if raw_text else '' + if isinstance(raw_text, dict): + parsed = raw_text + else: + raw_text = str(raw_text or '') + begin, finish = raw_text.find('{'), raw_text.rfind('}') + if begin < 0 or finish <= begin: + return fallback + parsed = json.loads(raw_text[begin:finish + 1]) + score = max(0.0, min(10.0, float(parsed['score']))) + count = int(parsed['people_count']) + gender_age_ok = parsed.get('gender_age_ok') is True + face_ok = parsed.get('face_ok') is True + deformation = parsed.get('deformation') is True + accessories_ok = parsed.get('accessories_ok') is True + ok = count == 1 and gender_age_ok and face_ok and not deformation + return {'score': score, 'ok': ok, 'issues': parsed.get('issues', []), + 'reviewed': True, 'accessories_ok': accessories_ok, + 'people_count': count, 'gender_age_ok': gender_age_ok, + 'face_ok': face_ok, 'deformation': deformation} + except (ValueError, TypeError, KeyError, json.JSONDecodeError, requests.RequestException, + OSError, RuntimeError, TimeoutError) as exc: + print('[ALFRED METEO] Relecture VLM non disponible : ' + str(exc), flush=True) + return fallback + + +def _quick_quality(img) -> float: + """Indice technique non sémantique (non utilisé comme preuve de conformité).""" + from PIL import ImageFilter, ImageStat + gray = img.convert('L').resize((256, 256)) + edges = gray.filter(ImageFilter.FIND_EDGES) + score = ImageStat.Stat(edges).stddev[0] + return min(7.0, max(1.0, score / 3.0)) + + +def _lock_reference_face(img, kind: str, referenced: bool): + """Préserve la tête du portrait validé, pour un cadrage img2img identique. + + Uniquement appliqué si ComfyUI est parti du portrait de référence, afin + d'éviter de superposer la tête sur une pose générée sans contrainte. + """ + if not referenced or not PRESERVE_FACE: + return img + ref_file = REFERENCE_DIR / f'{kind}-reference.png' + if not ref_file.is_file(): + return img + from PIL import Image, ImageDraw, ImageFilter + with Image.open(ref_file) as reference: + reference = reference.convert('RGB').resize(img.size, Image.Resampling.LANCZOS) + # Coordonnées relatives aux portraits 512x768 fournis dans le ZIP. + region = ((.35, .08, .66, .36) if kind == 'child' else (.34, .06, .66, .34)) + box = tuple(round(v * (img.width if i % 2 == 0 else img.height)) for i,v in enumerate(region)) + mask = Image.new('L', img.size, 0) + drawer = ImageDraw.Draw(mask) + drawer.ellipse(box, fill=230) + mask = mask.filter(ImageFilter.GaussianBlur(max(8, round(img.width * .022)))) + return Image.composite(reference, img, mask) + + +def _render_best_one(kind: str, day: dict, outfits: dict, refresh: bool = False): + prompt = _single_character_prompt(kind, day, outfits) + subject = ('one 8 year old girl' if kind == 'child' else 'one adult 42 year old man') + summary = subject + ', clothes: ' + ', '.join(outfits[kind]['clothes']) + ', accessories: ' + ( + ', '.join(_primary_accessories(kind, outfits)) or 'none') + best = None + for attempt in range(1, MAX_ATTEMPTS_PER_CHARACTER + 1): + prompt_id, referenced = _submit_workflow(prompt, force_new_seed=True, kind=kind) + result = _wait_history(prompt_id) + images = _extract_images(result) + if not images: + raise RuntimeError(f'Aucune illustration ComfyUI générée pour {kind}.') + url = images[0]['url'] + image = _render_image_from_url(url) + review = _vision_review(kind, summary, url) + quick = _quick_quality(image) + score = review['score'] if review.get('reviewed') else quick + # Penalty stronger than score for invalid identity/count from a real review + if review.get('reviewed') and not review['ok']: + score -= 4.0 + candidate = {'image': image, 'prompt_id': prompt_id, + 'review': review, 'score': score, 'referenced': referenced} + if best is None or score > best['score']: + best = candidate + # Early stop only when a real review validates quality, not with fallback. + if review.get('reviewed') and review.get('ok') and review.get('score', 0) >= QUALITY_TARGET: + break + # Without a functioning VLM, avoid double generation on reasonable output. + if not review.get('reviewed') and quick >= 4.3: + break + final = _lock_reference_face(best['image'], kind, best['referenced']) + best['review']['reference_applied'] = best['referenced'] + return final, best['prompt_id'], best['review'] + + +def _compose_duo(child_img, adult_img, dest: Path): + """Assemble deux personnages distincts dans une vignette carrée 350×350.""" + from PIL import Image, ImageOps, ImageDraw + size = max(200, min(700, DISPLAY_SIZE)) + gap = max(4, round(size * .023)) + tile_w = (size - gap) // 2 + tile_h = size + canvas = Image.new('RGB', (size, size), (42, 50, 56)) + # contain au lieu de fit : ne coupe pas les bras, chaussures ou accessoires. + for image, x in ((child_img, 0), (adult_img, tile_w + gap)): + thumb = ImageOps.fit(image, (tile_w, tile_h), method=Image.Resampling.LANCZOS) + px = x + (tile_w - thumb.width)//2 + py = (size - thumb.height)//2 + canvas.paste(thumb, (px, py)) + draw = ImageDraw.Draw(canvas) + draw.rectangle((tile_w, 0, tile_w + gap - 1, size), fill=(42, 50, 56)) + dest.parent.mkdir(parents=True, exist_ok=True) + temp = dest.with_suffix('.tmp.jpg') + canvas.save(temp, format='JPEG', quality=92, optimize=True) + temp.replace(dest) + + +def _cache_url(file_path: Path) -> str: + return 'data:image/jpeg;base64,' + base64.b64encode(file_path.read_bytes()).decode('ascii') + + +def _generate_illustration(day: dict, outfits: dict, focus_label: str, refresh: bool = False) -> dict | None: + if not ENABLED: + return None + sig = _weather_signature(day, outfits) + image_path = IMAGE_DIR / (sig + '.jpg') + with ILLUSTRATION_LOCK: + cache = _load_cache() + entry = cache.get(sig) + now_ts = time.time() + if (entry and image_path.is_file() and not refresh + and (now_ts - float(entry.get('cached_at', 0))) < TTL_HOURS * 3600): + return { + 'url': _cache_url(image_path), + 'cached_at': entry.get('cached_at'), + 'prompt_ids': entry.get('prompt_ids', []), + 'reviews': entry.get('reviews', {}), + 'from_cache': True, + } + child, child_id, child_review = _render_best_one('child', day, outfits, refresh=refresh) + adult, adult_id, adult_review = _render_best_one('adult', day, outfits, refresh=refresh) + _compose_duo(child, adult, image_path) + entry = { + 'file': image_path.name, + 'prompt_ids': [child_id, adult_id], + 'reviews': {'child': child_review, 'adult': adult_review}, + 'cached_at': now_ts, + 'focus_label': focus_label, + } + cache[sig] = entry + _save_cache(cache) + return { + 'url': _cache_url(image_path), + 'prompt_ids': entry['prompt_ids'], + 'reviews': entry['reviews'], + 'cached_at': now_ts, + 'from_cache': False, + } + + +def build_payload(q: str = '', date: str = '', label: str = '', refresh: bool = False) -> dict: + data = ha_weather.get_weather(q) + if date: + try: + Date.fromisoformat(date) + except (ValueError, TypeError) as exc: + raise ValueError('Date invalide (AAAA-MM-JJ requis).') from exc + target = date or data.get('focus_date', '') + day = _pick_day(data, target) + focus_label = label or data.get('focus_label', 'aujourd’hui') + outfits = _recommend(day) + illustration = None + illustration_error = None + try: + illustration = _generate_illustration(day, outfits, focus_label, refresh=refresh) + except (OSError, ValueError, RuntimeError, TimeoutError, ImportError, requests.RequestException) as exc: + illustration_error = f'{type(exc).__name__} : {exc}' + print(f'[ALFRED METEO] Illustration indisponible : {illustration_error}', flush=True) + return { + 'type': 'weather_outfits', + 'focus_label': focus_label, + 'date': day.get('date'), + 'condition': day.get('condition'), + 'description': day.get('description'), + 'temperature': day.get('temperature'), + 'templow': day.get('templow'), + 'precipitation_probability': day.get('precipitation_probability'), + 'wind_speed': day.get('wind_speed'), + 'summary': outfits.get('summary'), + 'child': outfits['child'], + 'adult': outfits['adult'], + 'image': illustration, + 'illustration_error': illustration_error, + } diff --git a/alfred/app/services/weather_outfits.py.bak.20261025_234759 b/alfred/app/services/weather_outfits.py.bak.20261025_234759 new file mode 100644 index 0000000..15b8968 --- /dev/null +++ b/alfred/app/services/weather_outfits.py.bak.20261025_234759 @@ -0,0 +1,419 @@ +"""Outfit recommendations and ComfyUI illustration for Alfred weather view. + +This service reuses the same ComfyUI instance as Hermes, but calls ComfyUI +HTTP directly to avoid replacing Alfred's central view while generating the +illustration. +""" +from __future__ import annotations + +import base64 +import hashlib +import io +import json +import os +import threading +import time +import uuid +from datetime import datetime, date as Date +from collections import Counter +from pathlib import Path +from urllib.parse import urlencode + +import requests +from dotenv import load_dotenv, dotenv_values + +# Charger l'environnement avant de calculer BASE et WORKFLOWS_DIR. +# Flask exécute son load_dotenv() après l'import de ce module. +load_dotenv() +load_dotenv('/opt/hermes/MCP_comfyui/.env', override=False) +_MCP_SETTINGS = dotenv_values('/opt/hermes/MCP_comfyui/.env') + +from . import ha_weather + +CACHE_DIR = Path(os.getenv('ALFRED_CACHE_DIR', str(Path(__file__).resolve().parents[1] / 'cache'))) +CACHE_DIR.mkdir(parents=True, exist_ok=True) +CACHE_FILE = CACHE_DIR / 'weather_outfit_cache.json' +BASE = (os.getenv('ALFRED_COMFYUI_URL') or _MCP_SETTINGS.get('COMFYUI_URL') or os.getenv('COMFYUI_URL') or 'http://192.168.1.13:8188').rstrip('/') +WORKFLOWS_DIR = Path(os.getenv('ALFRED_COMFYUI_WORKFLOWS_DIR') or os.getenv('WORKFLOWS_DIR') or '/opt/hermes/MCP_comfyui/workflows') +PROMPT_NODE = os.getenv('COMFY_PROMPT_NODE', '6') +TIMEOUT = int(os.getenv('COMFYUI_TIMEOUT', '300')) +TTL_HOURS = int(os.getenv('ALFRED_WEATHER_ILLUSTRATION_TTL_HOURS', '12')) +ENABLED = os.getenv('ALFRED_WEATHER_ILLUSTRATIONS', 'true').lower() in ('1', 'true', 'yes', 'on') +IMAGE_SIZE = min(1024, max(512, int(os.getenv('ALFRED_WEATHER_COMFY_SIZE', '768')))) +PROMPT_VERSION = 'outfits-v3-duo-20261025' + +# Un seul job génération météo à la fois : évite les doublons concurrents. +ILLUSTRATION_LOCK = threading.RLock() +IMAGE_DIR = CACHE_DIR / 'weather_outfit_images' +IMAGE_DIR.mkdir(parents=True, exist_ok=True) + +# Deux portraits distincts, réunis dans une seule image finale. +PORTRAIT_WIDTH = min(512, IMAGE_SIZE) +PORTRAIT_HEIGHT = IMAGE_SIZE + + +def _load_cache() -> dict: + try: + return json.loads(CACHE_FILE.read_text(encoding='utf-8')) + except Exception: + return {} + + +def _save_cache(cache: dict) -> None: + CACHE_FILE.write_text(json.dumps(cache, ensure_ascii=False, indent=2), encoding='utf-8') + + +def _pick_day(data: dict, forced_date: str | None = None) -> dict: + """Retourne uniquement la météo du jour demandé ; jamais une autre journée.""" + target = str(forced_date or data.get('focus_date') or '').strip() + try: + requested = Date.fromisoformat(target) + except ValueError as exc: + raise ValueError('Date météo invalide : format AAAA-MM-JJ attendu.') from exc + + for day in data.get('daily', []): + if str(day.get('date')) == target: + return day + + # Certaines intégrations HA ne donnent que des prévisions horaires. + matching = [h for h in data.get('hourly', []) if h.get('date') == target] + if matching: + def available(key): + return [float(h[key]) for h in matching if h.get(key) is not None] + temps = available('temperature') + chances = available('precipitation_probability') + winds = available('wind_speed') + conditions = [str(h['condition']) for h in matching if h.get('condition')] + condition = Counter(conditions).most_common(1)[0][0] if conditions else 'unknown' + return { + 'date': target, + 'condition': condition, + 'description': ha_weather.CONDITIONS.get(condition, 'conditions non précisées'), + 'temperature': max(temps) if temps else None, + 'templow': min(temps) if temps else None, + 'precipitation_probability': max(chances) if chances else None, + 'wind_speed': max(winds) if winds else None, + } + + # Les observations actuelles ne doivent être utilisées QUE pour aujourd'hui. + if requested == datetime.now(ha_weather.TZ).date(): + current = data.get('current', {}) + return { + 'date': target, + 'condition': current.get('condition', 'unknown'), + 'description': current.get('description', 'conditions non précisées'), + 'temperature': current.get('temperature'), + 'templow': current.get('temperature'), + 'precipitation_probability': None, + 'wind_speed': current.get('wind_speed'), + } + raise ValueError(f'Aucune prévision Home Assistant disponible pour le {requested:%d/%m/%Y}. Aucune image ne sera générée avec les données d’un autre jour.') + + +def _temp_band(high: float | None, low: float | None) -> str: + ref = high if high is not None else low + if ref is None: + return 'mild' + if ref < 6: + return 'cold' + if ref < 14: + return 'cool' + if ref < 23: + return 'mild' + return 'warm' + + +def _recommend(day: dict) -> dict: + high = day.get('temperature') + low = day.get('templow') + pop = day.get('precipitation_probability') + wind = day.get('wind_speed') + cond = str(day.get('condition') or '') + band = _temp_band(high, low) + rainy = cond in {'rainy', 'pouring', 'lightning-rainy', 'snowy-rainy'} or (pop is not None and pop >= 45) + sunny = cond in {'sunny', 'partlycloudy'} + windy = wind is not None and wind >= 25 + cold = band in {'cold', 'cool'} + + child = [] + adult = [] + child_acc = [] + adult_acc = [] + + if band == 'cold': + child += ['doudoune chaude', 'pantalon chaud', 'chaussures fermées'] + adult += ['manteau chaud', 'pull', 'pantalon', 'chaussures fermées'] + child_acc += ['bonnet', 'écharpe'] + adult_acc += ['écharpe'] + elif band == 'cool': + child += ['veste légère', 'pantalon', 'baskets'] + adult += ['veste mi-saison', 'pull fin', 'jean', 'chaussures fermées'] + elif band == 'mild': + child += ['sweat ou gilet', 'pantalon léger', 'baskets'] + adult += ['veste légère', 'haut confortable', 'jean ou chino', 'baskets'] + else: + child += ['t-shirt', 'short ou pantalon léger', 'baskets légères'] + adult += ['t-shirt ou polo', 'tenue légère', 'chaussures légères'] + child_acc += ['casquette'] + adult_acc += ['lunettes de soleil'] + + if rainy: + child_acc += ['imperméable', 'parapluie'] + adult_acc += ['parapluie'] + if 'chaussures fermées' not in child: child.append('chaussures fermées') + if 'chaussures fermées' not in adult: adult.append('chaussures fermées') + if windy: + child_acc += ['coupe-vent'] + adult_acc += ['coupe-vent'] + if sunny and band != 'cold': + if 'casquette' not in child_acc: child_acc += ['casquette'] + if 'lunettes de soleil' not in adult_acc: adult_acc += ['lunettes de soleil'] + + def uniq(seq): + seen=[] + for x in seq: + if x not in seen: seen.append(x) + return seen + + return { + 'summary': day.get('description', 'Conditions du jour'), + 'child': { + 'title': 'Fille · 8 ans', + 'clothes': uniq(child), + 'accessories': uniq(child_acc), + }, + 'adult': { + 'title': 'Homme · 42 ans', + 'clothes': uniq(adult), + 'accessories': uniq(adult_acc), + } + } + + +def _weather_signature(day: dict, outfits: dict) -> str: + # Version explicite pour ne pas ressortir des images incorrectes de l'ancien cache. + raw = json.dumps({'version': PROMPT_VERSION, 'date': day.get('date'), + 'day': day, 'outfits': outfits, 'size': IMAGE_SIZE}, + ensure_ascii=False, sort_keys=True) + return hashlib.sha256(raw.encode('utf-8')).hexdigest() + + +def _extract_images(history: dict) -> list[dict]: + images = [] + for node_id, output in history.get('outputs', {}).items(): + for image in output.get('images', []): + filename = image.get('filename') + if not filename: + continue + subfolder = image.get('subfolder', '') + image_type = image.get('type', 'output') + query = urlencode({'filename': filename, 'subfolder': subfolder, 'type': image_type}) + images.append({'filename': filename, 'url': f'{BASE}/view?{query}', 'node_id': str(node_id)}) + return images + + +NEGATIVE_PROMPT = ( + 'more than one person, group photo, two people, extra person, background crowd, ' + 'mirror reflection, human silhouette in background, duplicate heads, cloned body, ' + 'deformed face, malformed face, mutated hands, extra limbs, extra fingers, ' + 'disfigured features, asymmetrical face, photorealistic, horror, blurry, ' + 'text, captions, logos, watermark' +) + + +def _prepare_workflow(workflow: dict, prompt: str) -> dict: + """Adapte le workflow ComfyUI utilisé par Hermes, sans toucher au fichier source.""" + node = workflow.get(PROMPT_NODE) + if not isinstance(node, dict) or not isinstance(node.get('inputs'), dict) or 'text' not in node['inputs']: + raise ValueError(f'Nœud de prompt ComfyUI {PROMPT_NODE} introuvable ou incompatible.') + node['inputs']['text'] = prompt + for sampler in workflow.values(): + if sampler.get('class_type') not in ('KSampler', 'KSamplerAdvanced'): + continue + negative_link = (sampler.get('inputs') or {}).get('negative') + if not isinstance(negative_link, (list, tuple)) or not negative_link: + continue + neg = workflow.get(str(negative_link[0])) + if neg and neg.get('class_type') == 'CLIPTextEncode' and 'text' in neg.get('inputs', {}): + neg['inputs']['text'] = NEGATIVE_PROMPT + for latent in workflow.values(): + if latent.get('class_type') in ('EmptyLatentImage', 'EmptySD3LatentImage'): + inputs = latent.get('inputs') or {} + if 'width' in inputs and 'height' in inputs: + inputs['width'] = PORTRAIT_WIDTH + inputs['height'] = PORTRAIT_HEIGHT + return workflow + + +def _submit_workflow(prompt: str) -> str: + workflow_path = WORKFLOWS_DIR / 'text_to_image.json' + if not workflow_path.exists(): + raise RuntimeError(f'Workflow ComfyUI introuvable : {workflow_path}') + workflow = _prepare_workflow(json.loads(workflow_path.read_text(encoding='utf-8')), prompt) + response = requests.post(f'{BASE}/prompt', json={'prompt': workflow, 'client_id': str(uuid.uuid4())}, timeout=30) + response.raise_for_status() + return response.json()['prompt_id'] + + +def _wait_history(prompt_id: str) -> dict: + deadline = time.monotonic() + TIMEOUT + while time.monotonic() < deadline: + response = requests.get(f'{BASE}/history/{prompt_id}', timeout=15) + response.raise_for_status() + result = response.json() + if prompt_id in result: + return result[prompt_id] + time.sleep(1) + raise RuntimeError(f'ComfyUI timeout ({prompt_id})') + + +def _single_character_prompt(kind: str, day: dict, outfits: dict) -> str: + """Un SEUL personnage par image, même style graphique pour les deux.""" + profile = outfits[kind] + if kind == 'child': + identity = ( + 'ONE young GIRL, eight years old, clearly a primary-school child, ' + 'small body proportions of a young child, friendly and natural face, ' + 'casual appropriate haircut, no grown-up features. ' + ) + else: + identity = ( + 'ONE adult MAN, forty-two years old, unmistakably masculine adult, ' + 'short neat hair, mature male face, optionally light stubble, ' + 'adult male shoulders and body proportions, not a teenager. ' + ) + weather = str(day.get('description') or 'daily weather') + return ( + 'Single character fashion illustration for a family weather dashboard. ' + 'Exactly ONE character, standing ALONE. ' + + identity + + 'Full body visible from head to shoes, centrally positioned, front three-quarter view, ' + 'generous empty space around body, relaxed neutral standing pose, smiling gently. ' + 'Soft polished 2D digital illustration, smooth clean outlines, tasteful pastel colors, ' + 'slight dimensional shading, consistent editorial children-book illustration style, ' + 'simple light neutral plain studio background, no other people, no human reflections, ' + 'no poster, no photographs, no text. ' + f'Weather for {day.get("date", "selected day")}: {weather}. ' + f'Clothing: {", ".join(profile["clothes"])}. ' + f'Accessories: {", ".join(profile["accessories"]) if profile["accessories"] else "none"}. ' + 'Correct age and correct body proportions. Clothing must be clearly visible.' + ) + + +def _render_one(kind: str, day: dict, outfits: dict): + """Génère et récupère un portrait individuel auprès du ComfyUI déjà installé.""" + prompt = _single_character_prompt(kind, day, outfits) + prompt_id = _submit_workflow(prompt) + result = _wait_history(prompt_id) + images = _extract_images(result) + if not images: + raise RuntimeError(f'Aucune illustration ComfyUI générée pour {kind}.') + result_image = requests.get(images[0]['url'], timeout=45) + result_image.raise_for_status() + if len(result_image.content) > 18_000_000: + raise RuntimeError('Image ComfyUI trop volumineuse (limite 18 Mo).') + from PIL import Image + with Image.open(io.BytesIO(result_image.content)) as raw: + raw.load() + img = raw.convert('RGB') + return img, prompt_id + + +def _compose_duo(child_img, adult_img, dest: Path): + """Assemble exactement deux portraits dans UN SEUL carré, comme la maquette.""" + from PIL import Image, ImageOps, ImageDraw + w = 700 + h = 700 + gap = 12 + tile_w = (w - gap) // 2 + tile_h = h + canvas = Image.new('RGB', (w, h), (42, 50, 56)) + # Recadrage centré : aucune troisième image/personne n'est ajoutée au montage. + left = ImageOps.fit(child_img, (tile_w, tile_h), method=Image.Resampling.LANCZOS, centering=(.5, .5)) + right = ImageOps.fit(adult_img, (tile_w, tile_h), method=Image.Resampling.LANCZOS, centering=(.5, .5)) + canvas.paste(left, (0, 0)) + canvas.paste(right, (tile_w + gap, 0)) + # Trait de séparation discret, les deux personnages forment une seule image. + draw = ImageDraw.Draw(canvas) + draw.rounded_rectangle((tile_w, 0, tile_w + gap - 1, h - 1), radius=2, fill=(42, 50, 56)) + dest.parent.mkdir(parents=True, exist_ok=True) + temp = dest.with_suffix('.tmp.jpg') + canvas.save(temp, format='JPEG', quality=88, optimize=True) + temp.replace(dest) + + +def _cache_url(file_path: Path) -> str: + """URL autonome sans nouvelle route Flask ; données gardées dans le cache Alfred.""" + return 'data:image/jpeg;base64,' + base64.b64encode(file_path.read_bytes()).decode('ascii') + + +def _generate_illustration(day: dict, outfits: dict, focus_label: str, refresh: bool = False) -> dict | None: + if not ENABLED: + return None + sig = _weather_signature(day, outfits) + image_path = IMAGE_DIR / (sig + '.jpg') + with ILLUSTRATION_LOCK: + cache = _load_cache() + entry = cache.get(sig) + now_ts = time.time() + if (entry and image_path.is_file() and not refresh + and (now_ts - float(entry.get('cached_at', 0))) < TTL_HOURS * 3600): + return { + 'url': _cache_url(image_path), + 'cached_at': entry.get('cached_at'), + 'prompt_ids': entry.get('prompt_ids', []), + 'from_cache': True, + } + child, child_id = _render_one('child', day, outfits) + adult, adult_id = _render_one('adult', day, outfits) + _compose_duo(child, adult, image_path) + entry = { + 'file': image_path.name, + 'prompt_ids': [child_id, adult_id], + 'cached_at': now_ts, + } + cache[sig] = entry + _save_cache(cache) + return { + 'url': _cache_url(image_path), + 'prompt_ids': entry['prompt_ids'], + 'cached_at': now_ts, + 'from_cache': False, + } + + +def build_payload(q: str = '', date: str = '', label: str = '', refresh: bool = False) -> dict: + data = ha_weather.get_weather(q) + if date: + try: + Date.fromisoformat(date) + except (ValueError, TypeError) as exc: + raise ValueError('Date invalide (AAAA-MM-JJ requis).') from exc + target = date or data.get('focus_date', '') + day = _pick_day(data, target) + focus_label = label or data.get('focus_label', 'aujourd’hui') + outfits = _recommend(day) + illustration = None + illustration_error = None + try: + illustration = _generate_illustration(day, outfits, focus_label, refresh=refresh) + except (OSError, ValueError, RuntimeError, TimeoutError, ImportError, requests.RequestException) as exc: + # La partie texte doit rester affichable si ComfyUI est indisponible. + illustration_error = f'{type(exc).__name__} : {exc}' + print(f'[ALFRED METEO] Illustration indisponible : {illustration_error}', flush=True) + return { + 'type': 'weather_outfits', + 'focus_label': focus_label, + 'date': day.get('date'), + 'condition': day.get('condition'), + 'description': day.get('description'), + 'temperature': day.get('temperature'), + 'templow': day.get('templow'), + 'precipitation_probability': day.get('precipitation_probability'), + 'wind_speed': day.get('wind_speed'), + 'summary': outfits.get('summary'), + 'child': outfits['child'], + 'adult': outfits['adult'], + 'image': illustration, + 'illustration_error': illustration_error, + } diff --git a/alfred/app/static/assets/weather/adult-reference.png b/alfred/app/static/assets/weather/adult-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..012bbed2040e0a61c59c82640e48ba99d525230b GIT binary patch literal 222870 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�~ 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

YC8)HAdY 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

24BOm5artde&)ncXxY7 zhn`w1)$7uA`rUS(#D%rBdc9F7mC2giSSjV_lKMGyf95_L!xtccF}V;>^quXk^=mKR zQc0GQJ=#5plA~3Yrq&p#Q2ScA znh}|i3N<_KXYZmTFIG!0Usyiqj_$Vx#<<|S0#V9wPQb*UvE;kp{ucnS zh%eyipEiMKw@#1Qp0hO0@$tiao(TIKk8#TL@SJZPpXdz8Krq<_>m?Lr*4M0<6_X&# zRvcFVQwj(GK;SVOG_?p|Mg&VhOvKjH?ouIG4m>9_@hC<@0FjxLJRO97Sa3(9;gfeh zc>LjgG^q1{S*LSngsCU>bt!ykc8q2FId%JsSn);qEm85C$bh^yqQ=AW<-prnK zLndD#LLofY^~2CB7mLf@HgnLZb=uv|VXNOCP@X$N`r7q<*KxHR>%v(hSx6-uB?Hev z5&#ki3=sn|ptUTdP!56=1cF&ir8k5Gfg&h}x)h{D{ZW6|IZT;#rMh)>xlpONCm9#dAa`PHSRcm2J`Su-}dLMViRqm(hmafWZO)7V%FNg9reCCK}I)p+QPfMs*()kOBfGno>Z(NQ}o#LOMPl z#AH%(vR_MN4YQP@6oj=PC@P`TJjyLHf)MEGpk4{9B@>Sh?>>6;)<=&&dg{5N5^7cU zL@5jkMZZ{(uIq<^R2oo7tw1ox5t&q=^;zoYJ+pV%WomD2RF-O`YBgS2>URe5a7eu5 zhk@%j&FA|`rBNxBHn%pLN6jdTe9spGna=oseP(?&ouA^#JsWH32q7HTMJaP@g_4z} zy5oC=YSj(H_F=2v?L|>CiX)XIS}R8@*U<&f2^=SIlybDxj$pwA)>?~%h$s~kGNOT}{Wq+@pm2A0uRV%dQQ~-x=Q>gdfe6g!$Y3xp>VX9S zEaD5H{!h1RC%a!~-ez;63;gL9nbmDP@1W!9^~a>nv27SJFee7ODGgI1iwa1i6@UcO zF;JfNK~H)Nh%_BxMFeC+Hn-$zcezqsE|m(7cC3|%maKGT=$8xY%PMeNPaYrMd62Y* zo>we2N-JyC^|k8SrRCCc6_hf@<^*X@)>zD#$z+MKIiY}D`~GsHYVw4s_w4aOy?puB z<=X1%(eneFCFy7whC!`fd(>)n9y}<%@WRH%+TD*o9`(92n|FA+*@-9XZ8OCa&&dHz zsxBTYKmd^xG6)KR>vLw}{xFFn1gzC-g;H_#!fL10YaTXR2S@#O&kSOL82DaB1p^ZM*;;3p_GscnHj8fwRC-yGVYIdpY8VIxUjjtar1g@d#g|` zBZIZpSVNOBTowu)p_QYEn5-dV$ePTs&`xQ6y|`RMYnIZyapmf@7aslKXFvbDx8J?@ zbZ?j>P%8R?r(@a1)z`x96N(EoQTJqaRtGdjbLMKmYa&>Hj2fv=Su(}VrEdn34Esuhzdc9JwSBIT`)a_}+TD|)5v%TK#Ug_qITB%eF!?xDPHv!MIQTVe`+PM;7 zqNmBMq$+q};CNstwNVcwm z`a(KVATlr$0SZ)N(!E9GW0Oi2075!pD7Di)Z0FdmrE1ZMNE@ zk?(t+?<=K<>CB+P98BT@4Hf{fh+n-LI9>&t)pnRyZ=P3V$1~*tpJ`dnaTupX-e(*h zCsGKQ5_B^j=EQ^=KqHLxEt!C5765o2dd5uv7D9kj#@IAXgKBl7TB-RySTcq{OXYhy z2#_&3YNf-bw8{EbW93T8SzhrsmfcDTKmmaz1R-^(Q7M!XPy!%ZYm+34M)6=6Mw$F2mn0olnY@o2$YmSgowE@Q5=a}NiC%m^+vT^F0QODHJh#dy@TdK zbI>2P zP3b#nnwT^LMifFxG3^wMmyls1_J>MqrIjUXjb)-dwLm0*1;Wxw;~Q%$ue|coH{Q7S zU%vNu-~HiFK78;fNirdn5E76P=k^JYJ3lk!(*gq)0I-N(_UJ$6em*7j#Iu~wu@W<% z=6vQaH@j)fQ-GNmW6j9_j;V*7abO@a3`7pIgCJ*9|I-z~DRP*`9w@mbPV<86U0zvP zDin~ImGtUWr&zF2GI+K-Y#$m(+^S!`vaU)ES*huIMFbvzWQ)uKg%nDHkSGKQF+QT% z7@KE7oTh1#W)X5q(a>F6?;jn-Cf$AXSfwnjKmx<9YFjv z^5VSls`KOyc*>3Vo~~93rBa}zw3G`WqyR>=mWT)u!BJYPQnjc&(PV91J{j>9ji*f)TL$a2I9G1D907jOx-hff+QwO0N^p|MACj9Od3!1Ej} zgb<(vAc8=Y2ueyJlKBwZHnok9Ut+d2W=H)5U;!q47oH zzW{(m{A#)Xn0-I3xywAS-hZmoi^ubbr^kkG=)+iM!#bV$GLP-4YT& z(K~7mdOcAtR<2*GUc2I!izZ9Sl0Y$D+XAAK*-*OXI>gn2qv3l#*xq{2CLO#A#kOV%uL3loo?^hb1nq+D_f;&m)zxL75K~m zpb!wmnB^h1L_BdToB+VYzL1&5=46vRoyZU|8AhQ>ORLFhe0cX+lpZ;butZWStsNny zu{LE!*Wpk7+s|SC=gKVpBtFg>YjbOHJcL8XM#%yMq7+C-Ipvwn^E9^x1!}FmAP7rg ziOLhp002ovPDHLkV1lmJus#3) literal 0 HcmV?d00001 diff --git a/alfred/app/static/css/alfred-controls.css b/alfred/app/static/css/alfred-controls.css index b35fd50..716de4e 100644 --- a/alfred/app/static/css/alfred-controls.css +++ b/alfred/app/static/css/alfred-controls.css @@ -12,3 +12,30 @@ .quiet-button[aria-pressed="true"] { color:#f7b5b5; background:#b65c5c26; border-color:#b65c5c66; } .settings-message { min-height:14px; margin:12px 0 0; font-size:11px; color:#e4c089; line-height:1.4; } @media(max-width:760px) { .audio-popover { top:65px; right:14px; } } + +/* Bouton mute microphone dans la barre supérieure. aria-pressed reflète l'état réel de l'API. */ +.mic-off-icon { display: none; } +#micMute[aria-pressed="true"] .mic-on-icon { display: none; } +#micMute[aria-pressed="true"] .mic-off-icon { display: block; } + +/* Onglet caméra dans le panneau déjà présent (pas de modification des vues calendrier). */ +.audio-popover { max-height:calc(100dvh - 100px); overflow-y:auto; overscroll-behavior:contain; } +.settings-tabs { display:flex; gap:5px; padding:4px; background:#111518; border-radius:11px; margin:0 0 16px; } +.settings-tab { flex:1; padding:10px; border:0; border-radius:8px; color:#a7b2ba; background:transparent; font:inherit; cursor:pointer; } +.settings-tab[aria-selected="true"] { background:#3b464b; color:#f6f8f9; font-weight:600; } +.settings-tab:focus-visible,.camera-settings-action:focus-visible,.camera-select:focus-visible,.camera-range:focus-visible { outline:2px solid #b9dbec; outline-offset:2px; } +.camera-settings-note { margin:10px 0 15px; color:#aab5be; font-size:12px; line-height:1.5; } +.camera-settings-items { display:block; } +.camera-group-title { color:#c2d6df; font-size:12px; text-transform:uppercase; letter-spacing:.08em; margin:23px 0 8px; font-weight:650; } +.camera-control { padding:0 0 8px; border-bottom:1px solid #ffffff10; } +.camera-control .setting-row { margin-top:11px; margin-bottom:9px; } +.camera-control label { font-size:12px; } +.camera-control .camera-control-value { color:#b8ced5; } +.camera-range, .camera-select { width:100%; min-height:28px; accent-color:#a8c7ce; } +.camera-select { color:#eff2f5; background:#30383d; border:1px solid #ffffff26; border-radius:8px; padding:7px 9px; font-size:13px; } +.camera-control.is-inactive { opacity:.44; } +.camera-disabled-hint { font-size:11px; color:#acb5bf; margin-top:4px; } +.camera-settings-actions { display:flex; gap:8px; margin-top:19px; } +.camera-settings-action { flex:1; padding:10px; background:#3b464c; border:1px solid #ffffff23; border-radius:9px; color:#f2f5f6; font-size:12px; cursor:pointer; } +.camera-settings-action.reset { background:#5b454026; color:#f0c4b9; } +@media(max-width:760px) { .audio-popover { max-height:calc(100dvh - 80px); } } diff --git a/alfred/app/static/css/alfred-views.css b/alfred/app/static/css/alfred-views.css new file mode 100644 index 0000000..fbf81bc --- /dev/null +++ b/alfred/app/static/css/alfred-views.css @@ -0,0 +1,102 @@ +/* Calendar and Home Assistant views — preserves original dark Alfred layout */ +#stage:has(.alfred-view) {padding:0 0 6px;justify-content:stretch;align-items:stretch} +#content.alfred-view {width:100%;height:100%;text-align:left;min-height:0;} +.view-pane {display:flex;flex-direction:column;gap:12px;max-height:100%;height:100%;min-height:0;padding:8px 1px 14px;} +.view-head {display:flex;justify-content:space-between;align-items:center;gap:12px;} +#content .view-title {font-size:clamp(1.2rem,1.7vw,1.85rem);margin:0;font-weight:500} +.view-close,.view-button,.view-pill {background:#363d42;color:#f3f7fa;border:1px solid #ffffff24;border-radius:10px;padding:9px 14px;cursor:pointer;font:inherit} +.view-close {font-size:1.4rem;min-width:40px;height:40px;padding:0} +.view-button:hover,.view-pill:hover,.view-close:hover {background:#455057} +.view-pill.active,.view-button.primary {background:#427789;border-color:#619aad} +.view-toolbar {display:flex;gap:9px;align-items:center;flex-wrap:wrap;} +.view-nav,.view-modes {display:flex;gap:5px;align-items:center} +.view-period {font-size:.85rem;min-width:215px;text-align:center;color:#d8e0e4} +.view-select {padding:9px;max-width:220px;border-radius:9px;background:#333a3e;color:#eff3f5;border:1px solid #ffffff2b} +.agenda-grid {display:grid;gap:5px;min-height:0;flex:1;overflow:auto;padding:2px;align-content:stretch;} +.agenda-seven,.agenda-month {grid-template-columns:repeat(7,minmax(0,1fr))} +.agenda-month {grid-template-rows:repeat(6,minmax(110px,1fr));} +.agenda-three {grid-template-columns:repeat(3,minmax(0,1fr))} +.agenda-one {grid-template-columns:1fr} +.agenda-day {border:1px solid #ffffff1c;border-radius:11px;min-width:0;min-height:120px;background:#ffffff08;padding:9px;display:flex;flex-direction:column;gap:6px;overflow-y:auto} +.agenda-day.is-today {border-color:#86bfda7a;background:#57839923} +.agenda-day.out-month {opacity:.42} +.agenda-date {font-size:.83rem;font-weight:600;color:#dce2e6;letter-spacing:.01em;padding-bottom:7px;border-bottom:1px solid #ffffff14} +.agenda-event {width:100%;text-align:left;background:color-mix(in srgb,var(--event-color) 17%,#2a2e32);color:#f1f3f5;border:0;border-left:3px solid var(--event-color);border-radius:5px;padding:7px 5px;font-size:clamp(.72rem,.85vw,.95rem);line-height:1.3;cursor:pointer;overflow-wrap:anywhere} +.agenda-event:hover {background:color-mix(in srgb,var(--event-color) 30%,#2a2e32)} +.agenda-empty {font-size:.8rem;color:#667781;margin:auto} +#content .view-footer {color:#9aa8b3;font-size:.76rem;margin:0} +#content .view-error {color:#f7adad;font-size:.85rem;margin:0} +#content .view-empty {font-size:1rem;color:#b8c2ca} +.agenda-form {display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-content:start;gap:14px;max-width:850px;width:100%;overflow-y:auto;margin:12px auto;} +.form-field {display:flex;flex-direction:column;gap:7px;color:#cfd6dd;font-size:.83rem} +.form-field input,.form-field select,.form-field textarea {padding:12px;border-radius:10px;color:#eff3f5;background:#2e3439;border:1px solid #ffffff32;font:inherit;min-width:0} +.form-field textarea {resize:vertical} +.form-check {display:flex;align-items:center;gap:10px} +.view-actions {display:flex;align-items:center;gap:10px;justify-content:flex-end;flex-wrap:wrap;} +.form-message {grid-column:1 / -1;} +.agenda-form .view-actions {grid-column:1 / -1} +.view-danger {color:#ffc4c4;background:#8f383850;border-color:#ed929255} +.detail-modal {position:fixed;inset:0;z-index:30;display:grid;place-items:center;background:#000b;padding:20px} +.detail-box {min-width:min(92vw,300px);max-width:540px;width:100%;border:1px solid #ffffff36;border-radius:19px;background:#292e33;box-shadow:0 25px 90px #000a;padding:24px;} +.detail-box h2 {font-size:1.4rem;margin:0 0 20px}.detail-box p {font-size:1rem;color:#d7dfe2;overflow-wrap:anywhere} +.delete-list {display:grid;gap:8px;overflow:auto}.delete-row {display:flex;justify-content:space-between;align-items:center;gap:10px;border:1px solid #ffffff1c;border-radius:9px;padding:12px} +.temperature-grid {display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px;overflow:auto;padding:4px;max-height:53%;} +.temperature-card {border:1px solid #ffffff22;background:#ffffff0a;border-radius:12px;padding:22px;color:#eaf1f3;white-space:pre-line;text-align:left;font:inherit;cursor:pointer;line-height:1.9} +.temperature-card:hover {background:#ffffff18} +.temperature-history {max-width:700px;text-align:center;margin:auto;width:100%;} +.temperature-history svg {width:100%;max-height:155px} +.temperature-history h2 {font-size:1.1rem;font-weight:450} +.ha-cameras {display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px;overflow:auto;} +.ha-camera-card {background:#ffffff0a;border-radius:13px;overflow:hidden;} +.ha-camera-card h2 {font-size:.95rem;padding:10px;font-weight:500;margin:0} +.ha-camera-card img {display:block;width:100%;aspect-ratio:16/9;object-fit:contain;background:#121619} +@media(max-width:1100px){.view-toolbar{gap:6px}.view-button,.view-pill {padding:7px;font-size:.77rem}.view-period{min-width:150px}.agenda-day{padding:5px}.agenda-event{padding:4px;font-size:.74rem}} +@media(max-width:760px){#stage:has(.alfred-view){min-height:460px}.agenda-grid{min-height:280px}.agenda-seven,.agenda-month{min-width:630px}.agenda-day{min-height:105px}.view-pane{max-height:600px}.view-head{padding:0 4px}.agenda-form{grid-template-columns:1fr}.header-actions{gap:3px}.header-actions .icon-button {width:38px;height:38px}.view-toolbar{overflow:auto}} + +/* Weather dynamic view — same dark UI as calendar. */ +.weather-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px} +.weather-now{display:flex;align-items:center;gap:22px;background:#293238;border:1px solid #ffffff16;border-radius:16px;padding:18px 24px;} +.weather-current-icon{font-size:clamp(44px,6vw,80px);line-height:1;color:#edcb82} +.weather-now-details h2{margin:0 0 8px;font-size:clamp(20px,2.1vw,32px);font-weight:500;color:#f5f6f7} +.weather-now-details p{margin:0;color:#b6c4ce;font-size:clamp(12px,1vw,17px)} +.weather-section-title{font-size:15px;letter-spacing:.02em;color:#bfd0da;margin:7px 0 -2px;font-weight:550} +.weather-days{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:7px} +.weather-day{min-width:0;display:flex;flex-direction:column;align-items:center;text-align:center;gap:5px;background:#293035;border-radius:12px;border:1px solid #ffffff12;padding:12px 5px 10px} +.weather-day p{margin:0} +.weather-day-name{font-size:12px;color:#d4e0e6} +.weather-day-icon{font-size:clamp(22px,2.8vw,40px);color:#eaca85} +.weather-day-condition{font-size:11px;color:#adbcc5;min-height:30px;line-height:1.3} +.weather-day-temp{font-size:21px;color:#f5f7f8;font-weight:600} +.weather-day-low,.weather-rain{font-size:11px;color:#a7b6c0} +.weather-rain{color:#8dc8e4 !important} +.weather-hours{display:flex;gap:7px;overflow-x:auto;padding:3px 0 10px;scrollbar-color:#637480 #2a3237} +.weather-hour{display:flex;flex:0 0 80px;align-items:center;flex-direction:column;gap:9px;padding:10px 6px;background:#293035;border:1px solid #ffffff16;border-radius:12px;color:#cbd7df;font-size:12px} +.weather-hour-icon{font-size:27px;color:#edd298} +@media(max-width:840px){.weather-days{grid-template-columns:repeat(4,minmax(0,1fr))}.weather-now{padding:14px;gap:12px}.weather-current-icon{font-size:48px}} +@media(max-width:470px){.weather-days{grid-template-columns:repeat(3,minmax(0,1fr))}} + + +/* Weather outfits + ComfyUI illustration */ +.weather-outfit-host{margin:8px 0 4px} +.weather-outfit-section{display:flex;flex-direction:column;gap:12px;background:#252d32;border:1px solid #ffffff16;border-radius:16px;padding:16px} +.weather-outfit-head{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap} +.weather-outfit-grid{display:grid;grid-template-columns:minmax(260px,360px) minmax(320px,1fr);gap:14px;align-items:stretch} +.weather-outfit-guide{display:grid;gap:12px} +.weather-person-card{background:#ffffff08;border:1px solid #ffffff16;border-radius:14px;padding:14px} +.weather-person-title{margin:0 0 6px;font-size:1.05rem;color:#f2f5f7} +.weather-person-subtitle{margin:10px 0 4px;color:#b8c7d1;font-size:.86rem} +.weather-outfit-list{margin:0;padding-left:18px;color:#e0e6ea;font-size:.95rem;line-height:1.55} +.weather-illustration-card{display:flex;flex-direction:column;justify-content:center;align-items:center;background:#ffffff07;border:1px solid #ffffff16;border-radius:14px;padding:12px;min-height:320px} +.weather-illustration-image{display:block;max-width:100%;max-height:430px;border-radius:12px;box-shadow:0 16px 40px #0005;object-fit:contain} +@media(max-width:980px){.weather-outfit-grid{grid-template-columns:1fr}.weather-illustration-card{min-height:220px}} + +/* ALFRED_METEO_TENUES_V2 */ +/* Fenêtre météo défilante sur l’écran tactile d’Alfred */ +#content.alfred-view .view-pane:has(.weather-outfit-host) {overflow-y:auto;overflow-x:hidden;scrollbar-color:#62727d #222b30;} +.weather-outfit-host{flex-shrink:0} +.weather-illustration-card{min-height:370px} +.weather-illustration-image{width:350px;height:350px;max-width:100%;max-height:none;object-fit:contain;} +.weather-day{cursor:pointer;font:inherit;color:inherit} +.weather-day.is-selected{border-color:#8bd1e4;background:#37647942;box-shadow:inset 0 0 0 1px #8bd1e465} +.weather-day:focus-visible{outline:2px solid #8bd1e4;outline-offset:2px} +@media(max-width:540px){.weather-illustration-image{width:min(350px,100%);height:auto;aspect-ratio:1/1}} diff --git a/alfred/app/static/js/alfred-camera-settings.js b/alfred/app/static/js/alfred-camera-settings.js new file mode 100644 index 0000000..8ba293e --- /dev/null +++ b/alfred/app/static/js/alfred-camera-settings.js @@ -0,0 +1,175 @@ +/* Réglages matériels de webcam : curseurs générés selon ce que V4L2 expose. */ +(() => { + 'use strict'; + const pane = document.getElementById('audioSettingsPanel'); + const tabAudio = document.getElementById('audioTab'); + const tabCamera = document.getElementById('cameraTab'); + const sectionAudio = document.getElementById('audioSettingsSection'); + const sectionCamera = document.getElementById('cameraSettingsSection'); + const items = document.getElementById('cameraSettingsItems'); + const message = document.getElementById('cameraSettingsMessage'); + const reload = document.getElementById('cameraSettingsReload'); + const reset = document.getElementById('cameraSettingsReset'); + if (!pane || !tabAudio || !tabCamera || !items) return; + + const labels = { + brightness: 'Luminosité', contrast: 'Contraste', saturation: 'Saturation', + hue: 'Teinte', gamma: 'Gamma', gain: 'Gain vidéo', sharpness: 'Netteté', + backlight_compensation: 'Compensation contre-jour', + power_line_frequency: 'Fréquence anti-scintillement', + white_balance_automatic: 'Balance des blancs automatique', + white_balance_temperature: 'Température des blancs (K)', + auto_exposure: 'Exposition', exposure_time_absolute: 'Temps d’exposition', + exposure_dynamic_framerate: 'Fréquence d’image dynamique' + }; + const groups = [ + {name: 'Image', keys: ['brightness','contrast','saturation','sharpness','hue','gamma','gain','backlight_compensation']}, + {name: 'Éclairage et couleurs', keys: ['white_balance_automatic','white_balance_temperature','power_line_frequency']}, + {name: 'Exposition', keys: ['auto_exposure','exposure_time_absolute','exposure_dynamic_framerate']} + ]; + const exposureChoices = {1: 'Manuelle', 3: 'Automatique'}; + const powerChoices = {0: 'Désactivé', 1: '50 Hz', 2: '60 Hz'}; + let working = false; + + function showTab(which) { + const camera = which === 'camera'; + tabCamera.setAttribute('aria-selected', String(camera)); + tabAudio.setAttribute('aria-selected', String(!camera)); + sectionCamera.hidden = !camera; + sectionAudio.hidden = camera; + if (camera) load(); + } + tabAudio.addEventListener('click', () => showTab('audio')); + tabCamera.addEventListener('click', () => showTab('camera')); + document.getElementById('audioSettingsButton')?.addEventListener('click', () => { + if (!pane.hidden) showTab('audio'); + }); + + async function api(path, payload) { + const opts = {cache: 'no-store'}; + if (payload !== undefined) { + opts.method = 'POST'; + opts.headers = {'Content-Type':'application/json', 'X-Alfred-Request':'1'}; + opts.body = JSON.stringify(payload); + } + const res = await fetch(path, opts); + const data = await res.json(); + if (!res.ok) throw new Error(data.error || `Erreur HTTP ${res.status}`); + return data; + } + + function elt(tag, klass, text) { + const e = document.createElement(tag); + if (klass) e.className = klass; + if (text !== undefined) e.textContent = text; + return e; + } + + function controlRow(name, meta) { + const row = elt('div', 'camera-control'); + if (meta.inactive) row.classList.add('is-inactive'); + const header = elt('div','setting-row'); + const label = elt('label', null, labels[name] || name); + const output = elt('output','camera-control-value',String(meta.value ?? '—')); + header.append(label, output); row.appendChild(header); + let input; + if (meta.kind === 'bool' || meta.kind === 'menu') { + input = elt('select','camera-select'); + let opts; + if (meta.kind === 'bool') opts = [{value:1,label:'Activé'},{value:0,label:'Désactivé'}]; + else if (name === 'auto_exposure') opts = [{value:3,label:'Automatique'},{value:1,label:'Manuelle'}]; + else if (name === 'power_line_frequency') opts = Object.entries(powerChoices).map(([value,label])=>({value:Number(value),label})); + else opts = meta.menu || []; + const allowed = meta.menu?.length && meta.kind === 'menu' ? new Set(meta.menu.map(i=>i.value)) : null; + for (const opt of opts) { + if (allowed && !allowed.has(opt.value)) continue; + const option = elt('option',null,opt.label); + option.value = String(opt.value); + input.appendChild(option); + } + input.value = String(meta.value); + const updateLabel = () => { + output.textContent = input.options[input.selectedIndex]?.text || String(input.value); + }; + updateLabel(); + input.addEventListener('change', () => save(name, Number(input.value))); + } else { + input = elt('input','camera-range'); + input.type = 'range'; + input.min = String(meta.min); input.max = String(meta.max); + input.step = String(meta.step || 1); + input.value = String(meta.value); + input.addEventListener('input', () => { output.textContent = input.value; }); + // Appliquer au relâchement : évite des centaines de commandes shell. + input.addEventListener('change', () => save(name, Number(input.value))); + } + input.id = `v4l-${name}`; + label.htmlFor = input.id; + input.disabled = Boolean(meta.inactive || working); + row.appendChild(input); + if (meta.inactive) row.appendChild(elt('div','camera-disabled-hint', 'Désactive le mode automatique pour régler cette valeur.')); + return row; + } + + function render(data) { + items.replaceChildren(); + const available = data.controls || {}; + for (const group of groups) { + const found = group.keys.filter(name => available[name]); + if (!found.length) continue; + items.appendChild(elt('h3','camera-group-title',group.name)); + for (const name of found) items.appendChild(controlRow(name,available[name])); + } + if (!items.children.length) items.appendChild(elt('p','camera-settings-note','Aucun réglage V4L2 disponible.')); + } + + async function load() { + if (working) return; + message.textContent = 'Lecture des réglages caméra…'; + try { + render(await api('/api/camera/settings')); + message.textContent = ''; + } catch (err) { + message.textContent = err.message; + items.replaceChildren(); + } + } + + async function save(control, value) { + if (working) return; + working = true; + message.textContent = 'Application…'; + try { + const result = await api('/api/camera/settings', {control, value}); + working = false; + render(result); + message.textContent = 'Réglage appliqué et mémorisé.'; + } catch (err) { + message.textContent = err.message; + working = false; + await load(); + return; + } + working = false; + } + + reload.addEventListener('click', load); + reset.addEventListener('click', async () => { + if (working || !window.confirm('Rétablir tous les réglages caméra d’origine ?')) return; + working = true; + message.textContent = 'Restauration en cours…'; + try { + const result = await api('/api/camera/reset', {}); + working = false; + render(result); + message.textContent = 'Valeurs d’usine restaurées.'; + } catch (err) { + message.textContent = err.message; + } finally { + working = false; + } + }); + window.alfredSocket?.on('camera_settings', data => { + if (!sectionCamera.hidden && !working) render(data); + }); +})(); diff --git a/alfred/app/static/js/alfred-controls.js b/alfred/app/static/js/alfred-controls.js index b94cfef..8f3ae69 100644 --- a/alfred/app/static/js/alfred-controls.js +++ b/alfred/app/static/js/alfred-controls.js @@ -1,4 +1,4 @@ -/* Contrôles audio depuis un pop-up + raccourci mute haut-parleur. */ +/* Réglages volume/gain dans la fenêtre, mute micro + haut-parleur dans la barre haute. */ (() => { 'use strict'; const panel = document.getElementById('audioSettingsPanel'); @@ -24,8 +24,8 @@ speaker.setAttribute('aria-label', settings.speaker_muted ? 'Réactiver le son' : 'Couper le son'); speaker.title = speaker.getAttribute('aria-label'); mic.setAttribute('aria-pressed', String(settings.mic_muted)); - mic.textContent = settings.mic_muted ? 'Coupé' : 'Actif'; - mic.title = settings.mic_muted ? 'Réactiver le microphone' : 'Couper le microphone'; + mic.setAttribute('aria-label', settings.mic_muted ? 'Réactiver le microphone' : 'Couper le microphone'); + mic.title = mic.getAttribute('aria-label'); } async function update(changes) { diff --git a/alfred/app/static/js/alfred-views.js b/alfred/app/static/js/alfred-views.js new file mode 100644 index 0000000..6566141 --- /dev/null +++ b/alfred/app/static/js/alfred-views.js @@ -0,0 +1,368 @@ +/* Alfred • visual views, powered by first-party Flask endpoints. No extra CDN. */ +(() => { + 'use strict'; + const root = document.getElementById('content'); + let current = null, camerasRefresh = null; + const DAYS = ['lun.','mar.','mer.','jeu.','ven.','sam.','dim.']; + const PALETTE = ['#77bcdf','#e5b677','#9dc99a','#d3a2d5','#8cb7c9','#ec9f95','#b6b5e1','#b9d78d']; + const el = (tag, cls, text) => { const x=document.createElement(tag); if(cls)x.className=cls; if(text!==undefined)x.textContent=String(text); return x; }; + const btn = (label, fn, cls='view-button') => { const b=el('button',cls,label); b.type='button'; b.addEventListener('click',fn);return b; }; + const api = async (url, options) => { + const r=await fetch(url,{cache:'no-store', ...options}); + const data=await r.json().catch(()=>({error:`Erreur HTTP ${r.status}`})); + if(!r.ok)throw new Error(data.error || `HTTP ${r.status}`); + return data; + }; + const post = (url,data) => api(url,{method:'POST',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},body:JSON.stringify(data)}); + const dateLabel = iso => new Date(iso).toLocaleDateString('fr-FR',{day:'numeric',month:'long',year:'numeric'}); + const timeLabel = iso => new Date(iso).toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'}); + const localKey = d => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; + const atDay = (d,h=0) => new Date(d.getFullYear(),d.getMonth(),d.getDate(),h); + const dayShift = (d,n) => new Date(d.getFullYear(),d.getMonth(),d.getDate()+n,d.getHours(),d.getMinutes()); + const eventOnDay=(ev,day)=> {const start=new Date(ev.start),end=new Date(ev.end);return startatDay(day);}; + const fmtRange=(a,b)=>`${dateLabel(a)} — ${dateLabel(new Date(new Date(b).getTime()-1000).toISOString())}`; + const wrap = (title) => { + clearInterval(camerasRefresh);camerasRefresh=null;root.replaceChildren(); + root.classList.add('alfred-view'); + const pane=el('div','view-pane'),head=el('header','view-head'); + head.append(el('h1','view-title',title)); + head.append(btn('×',goHome,'view-close')); + pane.append(head);root.append(pane);return {pane,head}; + }; + const showError=(err,pane)=>pane.append(el('p','view-error',err.message||String(err))); + const canRender = data=>['calendar','calendar_form','delete_candidates','temperatures','cameras','weather','home'].includes(data?.type); + function render(data){ + if(!canRender(data))return false; + current=data; + if(data.type==='calendar')drawCalendar(data); + if(data.type==='calendar_form')drawForm(data); + if(data.type==='delete_candidates')drawDeleteChoices(data); + if(data.type==='temperatures')drawTemps(data); + if(data.type==='weather')drawWeather(data); + if(data.type==='cameras')drawCameras(data); + if(data.type==='home'){root.classList.remove('alfred-view');root.replaceChildren(el('h1','', 'Bonjour.'), el('p','',data.text||'À ton écoute.')); clearInterval(camerasRefresh);camerasRefresh=null;} + return true; + } + const getSelected=()=>document.getElementById('calendar-source')?.value || ''; + async function loadCalendar(start,end,view,filter){ + const q=new URLSearchParams({start:new Date(start).toISOString(),end:new Date(end).toISOString()}); + if(filter)q.append('calendar_id',filter); + const data=await api('/api/calendar/events?'+q); + render({type:'calendar',view,start:new Date(start).toISOString(),end:new Date(end).toISOString(),...data,selected:filter||''}); + } + function moveCalendar(direction){ + if(!current || current.type!=='calendar')return; + const start=new Date(current.start),end=new Date(current.end),view=current.view; + let a,b; + if(view==='month'){ + a=new Date(start.getFullYear(),start.getMonth()+direction,1); + b=new Date(a.getFullYear(),a.getMonth()+1,1); + }else{ + const days=Math.round((end-start)/86400000) || 1; + a=dayShift(start,days*direction);b=dayShift(end,days*direction); + } + loadCalendar(a,b,view,getSelected()).catch(err=>{const {pane}=wrap('Agenda');showError(err,pane)}); + } + const details = (event) => { + const d=el('div','detail-modal'),box=el('section','detail-box'); + box.setAttribute('role','dialog');box.setAttribute('aria-modal','true');box.setAttribute('aria-label','Détails de l’événement'); + box.append(el('h2','',event.title)); + box.append(el('p','',`${dateLabel(event.start)}${event.all_day?' · Toute la journée':' · '+timeLabel(event.start)+'–'+timeLabel(event.end)}`)); + box.append(el('p','',event.calendar_name)); + if(event.location)box.append(el('p','',`Lieu : ${event.location}`)); + if(event.description)box.append(el('p','',event.description)); + const acts=el('div','view-actions');acts.append(btn('Fermer',()=>d.remove())); + if(event.can_delete) acts.append(btn('Supprimer…',async()=>{ + const recurrence=event.recurrence_id ? 'Cela supprimera cette occurrence.' : 'Pour une série récurrente, cela peut supprimer toute la série.'; + if(!confirm(`Supprimer « ${event.title} » du calendrier « ${event.calendar_name} » ?\n${recurrence}`))return; + try{await post('/api/calendar/delete',{calendar_id:event.calendar_id,uid:event.id,start:event.start,end:event.end,recurrence_id:event.recurrence_id||''}); d.remove();await loadCalendar(current.start,current.end,current.view,getSelected());} + catch(err){alert(err.message)} + },'view-danger')); + box.append(acts);d.append(box);document.body.append(d); + d.addEventListener('click',e=>{if(e.target===d)d.remove()}); + }; + function drawCalendar(data){ + const {pane,head}=wrap('Planning'); + const toolbar=el('div','view-toolbar'); + const first=el('div','view-nav'); + first.append(btn('‹',()=>moveCalendar(-1)),el('span','view-period',fmtRange(data.start,data.end)),btn('›',()=>moveCalendar(1))); + toolbar.append(first); + const modes=el('div','view-modes'); + [['Jour',1,'day'],['3 jours',3,'days'],['7 jours',7,'days'],['Semaine',7,'week'],['Mois',31,'month']].forEach(([label,n,v])=>{ + const b=btn(label,()=>{ + const now=new Date(),start=(v==='week')?dayShift(atDay(now),-((now.getDay()+6)%7)):v==='month'?new Date(now.getFullYear(),now.getMonth(),1):atDay(now); + const end=v==='month'?new Date(start.getFullYear(),start.getMonth()+1,1):dayShift(start,n); + loadCalendar(start,end,v,getSelected()).catch(err=>showError(err,pane)); + },'view-pill'+(data.view===v?' active':''));modes.append(b); + });toolbar.append(modes); + const src=el('select','view-select');src.id='calendar-source'; + src.append(new Option('Tous les calendriers','')); + (data.calendars||[]).forEach(c=>src.append(new Option(c.name,c.entity_id))); + src.value=data.selected||''; + src.addEventListener('change',()=>loadCalendar(data.start,data.end,data.view,src.value).catch(err=>showError(err,pane))); + toolbar.append(src,btn('+ Rendez-vous',()=>openForm())); + pane.append(toolbar); + if(data.errors?.length)pane.append(el('p','view-error','Calendriers partiellement indisponibles : '+data.errors.join(' · '))); + const grid=el('div','agenda-grid'); + const start=new Date(data.start),end=new Date(data.end); + let days=[],cursor=atDay(start); + const month=data.view==='month'; + if(month) cursor=dayShift(cursor,-((cursor.getDay()+6)%7)); + let limit=0; + while(cursor[c.entity_id, PALETTE[i%PALETTE.length]])); + days.forEach(d=>{ + const cell=el('section','agenda-day'); + if(month && d.getMonth()!==start.getMonth())cell.classList.add('out-month'); + if(localKey(d)===localKey(new Date()))cell.classList.add('is-today'); + cell.append(el('div','agenda-date',d.toLocaleDateString('fr-FR',{weekday:'short',day:'numeric',month:month?undefined:'short'}))); + const events=(data.events||[]).filter(ev=>eventOnDay(ev,d)); + if(!events.length)cell.append(el('div','agenda-empty','—')); + events.forEach(ev=>{ + const e=btn((ev.all_day?'':timeLabel(ev.start)+' ')+ev.title,()=>details(ev),'agenda-event'); + e.style.setProperty('--event-color',colors.get(ev.calendar_id)||'#7bbbe2'); + e.title=`${ev.title} · ${ev.calendar_name}`;cell.append(e); + }); + grid.append(cell); + }); + pane.append(grid); + pane.append(el('p','view-footer',`${data.events?.length||0} événement(s) · ${data.calendars?.length||0} calendrier(s) Home Assistant`)); + } + async function openForm(values){ + let calendars=[]; + try {const info=await api('/api/calendar/calendars');calendars=info.calendars||[];} + catch(err){const {pane}=wrap('Nouveau rendez-vous');showError(err,pane);return;} + render({type:'calendar_form',calendars,values:values||{}}); + } + function drawForm(data){ + const {pane}=wrap('Ajouter un événement'); + const v=data.values||{}; + const form=el('form','agenda-form');form.autocomplete='off'; + const field=(label,node)=>{const row=el('label','form-field');row.append(el('span','',label),node);form.append(row);return node}; + const makeInput=(type,value)=>{const i=el('input');i.type=type;i.value=value||'';i.required=true;return i}; + const title=field('Titre',makeInput('text',v.title));title.maxLength=200; + const src=el('select');src.required=true;src.append(new Option('Choisir un calendrier','')); + data.calendars.filter(c=>c.can_create).forEach(c=>src.append(new Option(c.name,c.entity_id))); + src.value=v.calendar_id||'';field('Calendrier',src); + const day=field('Date',makeInput('date',v.date||localKey(new Date()))); + const startTime=field('Début',makeInput('time',v.time||'09:00')); + const duration=field('Durée (minutes)',makeInput('number',v.duration_minutes||60));duration.min=15;duration.max=1440; + const location=field('Lieu (facultatif)',makeInput('text',''));location.required=false; + const description=field('Description (facultatif)',el('textarea'));description.required=false;description.rows=2; + const allDay=el('input');allDay.type='checkbox';allDay.required=false; + const allDayWrap=el('label','form-check');allDayWrap.append(allDay,el('span','','Toute la journée'));form.append(allDayWrap); + allDay.addEventListener('change',()=>{startTime.disabled=allDay.checked;duration.disabled=allDay.checked}); + const msg=el('p','form-message'); + const actions=el('div','view-actions');actions.append(btn('Annuler',goHome)); + const submit=el('button','view-button primary','Enregistrer');submit.type='submit';actions.append(submit); + form.append(actions,msg);pane.append(form); + form.addEventListener('submit',async e=>{ + e.preventDefault();submit.disabled=true;msg.textContent='Enregistrement…'; + try{ + const from=allDay.checked?new Date(day.value+'T00:00'):new Date(day.value+'T'+startTime.value+':00'); + if(Number.isNaN(from.getTime()))throw new Error('Date invalide.'); + const end=allDay.checked?dayShift(from,1):new Date(from.getTime()+Number(duration.value)*60000); + await post('/api/calendar/create',{calendar_id:src.value,title:title.value,start:from.toISOString(),end:end.toISOString(),all_day:allDay.checked,location:location.value,description:description.value}); + const first=atDay(from);await loadCalendar(first,dayShift(first,7),'days'); + }catch(err){msg.textContent=err.message;submit.disabled=false} + }); + } + function drawDeleteChoices(data){ + const {pane}=wrap('Choisir un événement à supprimer'); + const arr=el('div','delete-list'); + (data.events||[]).forEach(ev=>{ + const row=el('div','delete-row');row.append(el('span','',`${ev.title} • ${dateLabel(ev.start)} • ${ev.calendar_name}`)); + row.append(btn('Détails / Supprimer',()=>details(ev),'view-button'));arr.append(row); + });pane.append(arr); + if(data.awaiting_confirmation)pane.append(el('p','view-footer','Dis « confirme » pour supprimer, ou « annule ».')); + } + function drawTemps(data){ + const {pane}=wrap('Températures Home Assistant'); + if(!data.items?.length){pane.append(el('p','view-empty','Aucun capteur de température trouvé.'));return} + const grid=el('div','temperature-grid');pane.append(grid); + data.items.forEach(item=>{ + const c=btn(`${item.name}\n${item.value.toFixed(1)} ${item.unit}` ,async()=>{ + const q=new URLSearchParams({entity_id:item.entity_id,hours:'24'}); + try{const data=await api('/api/ha/temperature/history?'+q);showHistory(item,data.items||[],pane)} + catch(err){showError(err,pane)} + },'temperature-card');grid.append(c); + });pane.append(el('p','view-footer','Touchez un capteur pour afficher son historique sur 24 h.')); + } + function showHistory(item,points,pane){ + pane.querySelector('.temperature-history')?.remove(); + const section=el('section','temperature-history');section.append(el('h2','',item.name+' · 24 dernières heures')); + if(points.length<2){section.append(el('p','', 'Historique insuffisant.'));pane.append(section);return} + const vals=points.map(p=>p.value);const lo=Math.min(...vals)-0.4,hi=Math.max(...vals)+0.4; + const a=Date.parse(points[0].time),b=Date.parse(points[points.length-1].time); + const coords=points.map((p,i)=>`${30+560*((Date.parse(p.time)-a)/Math.max(1,b-a))},${140-110*((p.value-lo)/Math.max(0.1,hi-lo))}`).join(' '); + const ns='http://www.w3.org/2000/svg',svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 620 170');svg.setAttribute('role','img');svg.setAttribute('aria-label','Courbe de température'); + const poly=document.createElementNS(ns,'polyline');poly.setAttribute('points',coords);poly.setAttribute('fill','none');poly.setAttribute('stroke','#83c5cc');poly.setAttribute('stroke-width','3');svg.append(poly); + section.append(svg,el('p','',`Min ${Math.min(...vals).toFixed(1)}° · Max ${Math.max(...vals).toFixed(1)}°`));pane.append(section); + } + function drawCameras(data){ + const {pane}=wrap('Caméras Home Assistant'); + if(!data.items?.length){pane.append(el('p','view-empty','Aucune caméra Home Assistant disponible.'));return} + const grid=el('div','ha-cameras');pane.append(grid); + const imgs=[]; + data.items.forEach(item=>{ + const card=el('div','ha-camera-card');card.append(el('h2','',item.name)); + const img=el('img');img.alt='Instantané caméra '+item.name;img.loading='lazy';imgs.push([img,item.entity_id]);card.append(img);grid.append(card); + }); + const reload=()=>{if(document.hidden)return; imgs.forEach(([img,eid])=>img.src='/api/ha/camera/'+encodeURIComponent(eid)+'/snapshot?t='+Date.now())}; + reload();camerasRefresh=setInterval(reload,15000); + pane.append(el('p','view-footer','Instantanés réactualisés toutes les 15 s lorsque cette vue est ouverte.')); + } + + const WEATHER_ICONS = { + 'sunny':'☀', 'clear-night':'☾', 'cloudy':'☁', 'partlycloudy':'⛅', + 'fog':'≋', 'rainy':'🌧', 'pouring':'🌧', 'hail':'🌨', + 'lightning':'⛈', 'lightning-rainy':'⛈', 'snowy':'❄', + 'snowy-rainy':'🌨', 'windy':'〰', 'windy-variant':'〰' + }; + const weatherIcon = cond => WEATHER_ICONS[cond] || '◌'; + const tempText = (val, unit='°C') => typeof val === 'number' ? `${Math.round(val)}${unit}` : '—'; + function weatherList(items){ + const ul=el('ul','weather-outfit-list'); + (items||[]).forEach(text=>ul.append(el('li','',text))); + if(!ul.childNodes.length)ul.append(el('li','','Aucun accessoire particulier')); + return ul; + } + let outfitRequestId=0; + async function loadWeatherOutfits(container, options={}, refresh=false){ + const requestId=++outfitRequestId; + container.replaceChildren(); + const loading=el('p','view-footer', refresh ? 'Régénération de l’illustration…' : 'Préparation des tenues conseillées…'); + container.append(loading); + try{ + const q = new URLSearchParams(); + if(options.query)q.set('q',options.query); + if(options.date)q.set('date',options.date); + if(options.label)q.set('label',options.label); + if(refresh)q.set('refresh_illustration','1'); + const data = await api('/api/ha/weather/outfits' + (q.toString() ? '?' + q.toString() : '')); + if(requestId!==outfitRequestId)return; + const section=el('section','weather-outfit-section'); + const head=el('div','weather-outfit-head'); + head.append(el('h2','weather-section-title',`Tenues conseillées · ${data.focus_label || 'aujourd’hui'}`)); + head.append(btn('Régénérer l’illustration',()=>loadWeatherOutfits(container, options, true),'view-pill')); + section.append(head); + + const grid=el('div','weather-outfit-grid'); + const guide=el('div','weather-outfit-guide'); + [data.child, data.adult].forEach(person=>{ + const card=el('article','weather-person-card'); + card.append(el('h3','weather-person-title',person.title)); + card.append(el('p','weather-person-subtitle','Tenue recommandée')); + card.append(weatherList(person.clothes)); + card.append(el('p','weather-person-subtitle','Accessoires')); + card.append(weatherList(person.accessories)); + guide.append(card); + }); + grid.append(guide); + const art=el('div','weather-illustration-card'); + if(data.image?.url){ + const img=el('img','weather-illustration-image'); + img.src=data.image.url; + img.alt='Illustration des tenues conseillées selon la météo'; + art.append(img); + const foot=el('p','view-footer', data.image.from_cache ? 'Illustration mise en cache.' : 'Illustration générée par ComfyUI.'); + art.append(foot); + } else { + art.append(el('p','view-empty',data.illustration_error ? 'Illustration indisponible : '+data.illustration_error : 'Illustration désactivée.')); + } + grid.append(art); + section.append(grid); + if(data.summary){ section.append(el('p','view-footer',`Conseil général : ${data.summary}.`)); } + container.replaceChildren(section); + }catch(err){ + container.replaceChildren(el('p','view-error',`Tenues conseillées indisponibles : ${err.message || err}`)); + } + } + function drawWeather(data){ + const {pane}=wrap('Météo · '+(data.name||'Home Assistant')); + const nav=el('div','weather-toolbar'); + nav.append(btn('Actualiser',openWeather,'view-pill')); + nav.append(el('span','view-footer','Source : Home Assistant')); + pane.append(nav); + const forecast=(data.daily||[]).find(x=>x.date===data.focus_date); + const c=(data.focus_date && data.focus_date!==localKey(new Date()) && forecast) ? forecast : (data.current||{}),now=el('section','weather-now'); + const badge=el('div','weather-current-icon',weatherIcon(c.condition));badge.setAttribute('aria-hidden','true'); + const details=el('div','weather-now-details'); + details.append(el('h2','',`${tempText(c.temperature,data.temperature_unit)} · ${c.description||'Conditions inconnues'}`)); + const metrics=[]; + if(c.apparent_temperature!==null && c.apparent_temperature!==undefined)metrics.push(`Ressenti ${tempText(c.apparent_temperature,data.temperature_unit)}`); + if(c.humidity!==null && c.humidity!==undefined)metrics.push(`Humidité ${c.humidity}%`); + if(c.wind_speed!==null && c.wind_speed!==undefined)metrics.push(`Vent ${c.wind_speed} ${data.wind_speed_unit||'km/h'}`); + details.append(el('p','',metrics.join(' · ')||'Conditions actuelles'));now.append(badge,details);pane.append(now); + + const outfitHost=el('div','weather-outfit-host'); + pane.append(outfitHost); + loadWeatherOutfits(outfitHost, {date:data.focus_date || '', label:data.focus_label || 'aujourd’hui'}); + + const heading = text => pane.append(el('h2','weather-section-title',text)); + heading('Prévisions pour les prochains jours'); + if(data.daily?.length){ + const cards=el('div','weather-days'); + data.daily.slice(0,7).forEach(day=>{ + const d=new Date(day.datetime); + const box=el('button','weather-day'); + box.type='button'; + box.title='Voir les tenues pour ce jour'; + if(day.date===data.focus_date)box.classList.add('is-selected'); + box.addEventListener('click',()=>{ + cards.querySelectorAll('.weather-day').forEach(b=>b.classList.remove('is-selected')); + box.classList.add('is-selected'); + const label=d.toLocaleDateString('fr-FR',{weekday:'long',day:'numeric',month:'long'}); + loadWeatherOutfits(outfitHost, {date:day.date, label}); + }); + box.append(el('p','weather-day-name',d.toLocaleDateString('fr-FR',{weekday:'short',day:'numeric',month:'short'}))); + box.append(el('div','weather-day-icon',weatherIcon(day.condition))); + box.append(el('p','weather-day-condition',day.description)); + box.append(el('p','weather-day-temp',tempText(day.temperature,data.temperature_unit))); + if(day.templow!==null)box.append(el('p','weather-day-low','Min '+tempText(day.templow,data.temperature_unit))); + if(day.precipitation_probability!==null)box.append(el('p','weather-rain','Pluie '+day.precipitation_probability+' %')); + cards.append(box); + });pane.append(cards); + }else pane.append(el('p','view-empty','Prévisions journalières non disponibles.')); + heading('Prochaines heures'); + if(data.hourly?.length){ + const list=el('div','weather-hours'), nowMs=Date.now(); + data.hourly.filter(x=>new Date(x.datetime).getTime()>=nowMs-3600000).slice(0,12).forEach(h=>{ + const box=el('div','weather-hour'); + box.append(el('span','',new Date(h.datetime).toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'}))); + box.append(el('span','weather-hour-icon',weatherIcon(h.condition))); + box.append(el('strong','',tempText(h.temperature,data.temperature_unit))); + if(h.precipitation_probability!==null)box.append(el('span','weather-rain',h.precipitation_probability+' %')); + list.append(box); + });pane.append(list); + }else pane.append(el('p','view-empty','Prévisions horaires non disponibles.')); + if(data.errors?.length)pane.append(el('p','view-footer','Certaines prévisions ne sont pas fournies par cette source météo.')); + } + async function openWeather(){ + try{render(await api('/api/ha/weather'))} + catch(err){const {pane}=wrap('Météo');showError(err,pane)} + } + + async function goHome(){ + try{await post('/api/ui/home',{})}catch(err){render({type:'home'})} + } + async function openCalendar(){ + const d=atDay(new Date());try{await loadCalendar(d,dayShift(d,7),'days')}catch(err){const {pane}=wrap('Agenda');showError(err,pane)} + } + async function openTemperatures(){ + try{render({type:'temperatures',...await api('/api/ha/temperatures')})}catch(err){const {pane}=wrap('Températures');showError(err,pane)} + } + async function openCameras(){ + try{render({type:'cameras',...await api('/api/ha/cameras')})}catch(err){const {pane}=wrap('Caméras');showError(err,pane)} + } + document.getElementById('viewHome')?.addEventListener('click',goHome); + document.getElementById('viewCalendar')?.addEventListener('click',openCalendar); + document.getElementById('viewTemperatures')?.addEventListener('click',openTemperatures); + document.getElementById('viewWeather')?.addEventListener('click',openWeather); + document.getElementById('viewCameras')?.addEventListener('click',openCameras); + const socket=window.alfredSocket; + socket?.on('calendar_changed',()=>{if(current?.type==='calendar') loadCalendar(current.start,current.end,current.view,getSelected()).catch(console.warn)}); + window.AlfredViews={render,canRender,openCalendar,openTemperatures,openCameras,openWeather,goHome}; +})(); diff --git a/alfred/app/static/js/alfred.js b/alfred/app/static/js/alfred.js index 67111dd..b143532 100644 --- a/alfred/app/static/js/alfred.js +++ b/alfred/app/static/js/alfred.js @@ -57,6 +57,11 @@ const key = JSON.stringify(data); if (key === previousContent) return; previousContent = key; + if (window.AlfredViews && window.AlfredViews.canRender(data)) { + window.AlfredViews.render(data); + return; + } + content.classList.remove('alfred-view'); const fragment = document.createDocumentFragment(); if (data.type === 'image' && typeof data.url === 'string') { // Refuser les URL javascript: et les schémas non prévus. diff --git a/alfred/app/templates/index.html b/alfred/app/templates/index.html index 3b875e9..54bd6bc 100644 --- a/alfred/app/templates/index.html +++ b/alfred/app/templates/index.html @@ -7,6 +7,7 @@ {{ name }} + @@ -18,14 +19,30 @@ En attente -

@@ -97,7 +125,9 @@ + + diff --git a/alfred/backup_meteo_tenues_v2_20261025_232200/app.py b/alfred/backup_meteo_tenues_v2_20261025_232200/app.py new file mode 100644 index 0000000..0eb28a6 --- /dev/null +++ b/alfred/backup_meteo_tenues_v2_20261025_232200/app.py @@ -0,0 +1,422 @@ +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 +from services.audio import listen_once, speak_async, tts_backend_detect +from services.wakeword import WakeWordListener +from services.hermes_client import ask_hermes +from services import audio_settings # ALFRED_AUDIO_SETTINGS_V1 +from services.camera import video_stream +from services import ha_views, alfred_views_controller +from services import camera_settings, ha_weather, weather_outfits + +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":""} +STATE_LOCK = threading.RLock() +IMAGE_REVISION = 0 # Incrémenté à chaque image envoyée par ComfyUI. +conversation_lock=threading.Lock() +mic_stop_event=threading.Event() +audio_settings.initialize() +audio_settings.update_settings({"mic_gain": 30}) +# Recharge les réglages V4L2 enregistrés, sans interrompre Alfred si la webcam est absente. +camera_settings.restore_saved_settings() + +def emit_state(mode=None, **extra): + with STATE_LOCK: + if mode: STATE["mode"] = mode + STATE.update(extra) + socketio.emit("alfred_state", dict(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="", 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 + ) + + print(f"[ALFRED] Résultat STT : {r.text!r}, erreur : {r.error!r}", flush=True) + + if audio_settings.mic_muted(): + emit_state("idle", partial="") + return + + 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} + ) + + # Vue dynamique : commandes déterministes exécutées sans faire + # dépendre l'affichage des capacités ou sessions Hermes. + visual = None + try: + visual = alfred_views_controller.route(r.text, (STATE.get('content') or {}).get('type')) + except (ha_views.HAError, ValueError) as exc: + print(f"[ALFRED] Vue indisponible : {exc}", flush=True) + visual = ({"type":"text", "text":str(exc)}, str(exc)) + + if visual is not None: + content, speech = visual + with STATE_LOCK: + STATE["content"] = content + socketio.emit("alfred_render", content) + 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) + emit_state("idle", partial="") + return + + print("[ALFRED] Envoi à Hermes", flush=True) + # ComfyUI publie l'image pendant cet appel Hermes. + with STATE_LOCK: + image_revision_before = IMAGE_REVISION + 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} + + # Si le MCP ComfyUI a affiché une image pendant la requête, + # ne pas l'écraser par la réponse textuelle d'Hermes. + with STATE_LOCK: + if IMAGE_REVISION == image_revision_before: + STATE["content"] = display + socketio.emit("alfred_render", display) + else: + print("[ALFRED] Image ComfyUI conservée à l'écran", flush=True) + + 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) + + 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() + if not audio_settings.mic_muted(): + wake.resume() + print("[ALFRED] Wake word réactivé", flush=True) + + + +def on_wake(score): + if audio_settings.mic_muted(): + return + 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('/api/audio/settings') +def get_audio_settings(): + return jsonify(audio_settings.get_settings()) + +@app.post('/api/audio/settings') +def set_audio_settings(): + data = request.get_json(silent=True) + try: + old = audio_settings.get_settings() + new = audio_settings.update_settings(data) + except ValueError as exc: + return jsonify({'error': str(exc)}), 400 + except Exception as exc: + return jsonify({'error': f'ALSA : {exc}'}), 500 + if new['mic_muted'] != old['mic_muted']: + if new['mic_muted']: + mic_stop_event.set() + wake.pause() + else: + mic_stop_event.clear() + if not conversation_lock.locked(): + wake.resume() + socketio.emit('audio_settings', new) + return jsonify(new) + +@app.get('/api/camera/settings') +def get_camera_settings(): + try: + return jsonify(camera_settings.get_settings()) + except camera_settings.CameraSettingsError as exc: + return jsonify({'error': str(exc)}), 503 + +@app.post('/api/camera/settings') +def set_camera_settings(): + # Même garde-fou que les commandes calendrier existantes. + if not _same_origin_json(): + return jsonify({'error': 'Requête non autorisée.'}), 403 + data = request.get_json(silent=True) + if not isinstance(data, dict): + return jsonify({'error': 'JSON invalide.'}), 400 + try: + result = camera_settings.update_setting(data.get('control'), data.get('value')) + socketio.emit('camera_settings', result) + return jsonify(result) + except ValueError as exc: + return jsonify({'error': str(exc)}), 400 + except camera_settings.CameraSettingsError as exc: + return jsonify({'error': str(exc)}), 503 + +@app.post('/api/camera/reset') +def reset_camera_settings(): + if not _same_origin_json(): + return jsonify({'error': 'Requête non autorisée.'}), 403 + try: + result = camera_settings.reset_defaults() + socketio.emit('camera_settings', result) + return jsonify(result) + except camera_settings.CameraSettingsError as exc: + return jsonify({'error': str(exc)}), 503 + +@app.get('/api/camera/stream') +def camera_stream(): + # Flux MJPEG à la demande ; ne démarre jamais la webcam en arrière-plan. + try: + frames = video_stream(index=int(os.getenv('CAMERA_INDEX', '0'))) + except (ImportError, ValueError, RuntimeError, OSError) as exc: + return jsonify({'error': str(exc)}), 503 + return Response( + frames, + mimetype='multipart/x-mixed-replace; boundary=frame', + headers={'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff'} + ) + +@app.get("/") +def index(): return render_template("index.html",name=os.getenv("ALFRED_NAME","Alfred")) +@app.get("/api/state") +def state(): + with STATE_LOCK: + snapshot = dict(STATE) + return jsonify({**snapshot, "wake": wake.status(), "tts": tts_backend_detect()}) + +@app.post("/api/state") +def set_state(): + data = request.get_json(force=True) + with STATE_LOCK: + STATE.update(data) + socketio.emit("alfred_state", dict(STATE)) + return jsonify({"ok": True}) +@app.post("/api/render") +def render_payload(): + global IMAGE_REVISION + token = os.getenv("ALFRED_RENDER_TOKEN", "").strip() + if token and not hmac.compare_digest( + request.headers.get("X-Alfred-Render-Token", ""), token + ): + return jsonify({"ok": False, "error": "Non autorisé"}), 401 + + data = request.get_json(silent=True) + if not isinstance(data, dict): + return jsonify({"ok": False, "error": "JSON objet attendu"}), 400 + if data.get("type") == "image": + image_url = data.get("url") + if not isinstance(image_url, str) or not ( + image_url.startswith(("http://", "https://", "/")) + and not image_url.startswith("//") + ): + return jsonify({"ok": False, "error": "URL image invalide"}), 400 + + with STATE_LOCK: + if data.get("type") == "image": + IMAGE_REVISION += 1 + STATE["content"] = data + socketio.emit("alfred_render", data) + return jsonify({"ok": True, "image_revision": IMAGE_REVISION}) +# --- Agenda / capteurs / caméras Home Assistant --- +def _calendar_range(): + tz = ZoneInfo(os.getenv('ALFRED_TIMEZONE', 'Europe/Paris')) + now = datetime.now(tz) + def parse(name, default): + raw = request.args.get(name) + if not raw:return default + value = datetime.fromisoformat(raw.replace('Z','+00:00')) + if value.tzinfo is None: value = value.replace(tzinfo=tz) + return value.astimezone(tz) + start = parse('start',now.replace(hour=0,minute=0,second=0,microsecond=0)) + end = parse('end',start+timedelta(days=7)) + return start,end + +def _json_error(exc): + code = 400 if isinstance(exc,ValueError) else 502 + return jsonify({'error':str(exc)}),code + +def _writes_enabled(): + return os.getenv('ALFRED_ALLOW_CALENDAR_WRITES','false').lower() in ('1','true','yes') + +def _same_origin_json(): + # POSTs require a custom header and same-origin browser requests. + if request.headers.get('X-Alfred-Request') != '1':return False + origin=request.headers.get('Origin') + if origin and origin.rstrip('/') != request.host_url.rstrip('/'):return False + return True + +@app.get('/api/calendar/calendars') +def calendars_list(): + try:return jsonify({'calendars':ha_views.list_calendars(),'writes_enabled':_writes_enabled()}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/calendar/events') +def calendar_events(): + try: + start,end=_calendar_range() + selected=request.args.getlist('calendar_id') + return jsonify(ha_views.get_events(start,end,selected or None)) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.post('/api/calendar/create') +def calendar_create(): + if not _same_origin_json():return jsonify({'error':'Requête non autorisée.'}),403 + if not _writes_enabled():return jsonify({'error':'Écriture calendrier désactivée (ALFRED_ALLOW_CALENDAR_WRITES).'}),403 + data=request.get_json(silent=True) + if not isinstance(data,dict):return jsonify({'error':'JSON invalide.'}),400 + try: + tz=ZoneInfo(os.getenv('ALFRED_TIMEZONE','Europe/Paris')) + def read_dt(key): + dt=datetime.fromisoformat(str(data[key]).replace('Z','+00:00')) + return dt.replace(tzinfo=tz) if dt.tzinfo is None else dt.astimezone(tz) + result=ha_views.create_event(str(data.get('calendar_id','')),str(data.get('title','')), + read_dt('start'),read_dt('end'),str(data.get('description') or ''), + str(data.get('location') or ''),bool(data.get('all_day',False))) + from services import alfred_intents + alfred_intents.clear_create() + socketio.emit('calendar_changed', {'type':'created'}) + return jsonify(result) + except (ha_views.HAError,ValueError,KeyError) as exc:return _json_error(exc) + +@app.post('/api/calendar/delete') +def calendar_delete(): + if not _same_origin_json():return jsonify({'error':'Requête non autorisée.'}),403 + if not _writes_enabled():return jsonify({'error':'Écriture calendrier désactivée.'}),403 + data=request.get_json(silent=True) + if not isinstance(data,dict):return jsonify({'error':'JSON invalide.'}),400 + try: + eid=str(data.get('calendar_id',''));uid=str(data.get('uid','')) + start=datetime.fromisoformat(str(data['start'])) + end=datetime.fromisoformat(str(data['end'])) + if start.tzinfo is None or end.tzinfo is None:raise ValueError('Date avec fuseau requise.') + matches=ha_views.get_events(start-timedelta(days=1),end+timedelta(days=1),[eid])['events'] + rid=str(data.get('recurrence_id') or '') + if not any(e['id']==uid and e['start']==start.isoformat() and e['recurrence_id']==rid for e in matches): + return jsonify({'error':'Événement introuvable ou modifié ; actualise le planning.'}),409 + result=ha_views.delete_event(eid,uid,rid or None) + from services import alfred_intents + alfred_intents.clear_delete() + socketio.emit('calendar_changed',{'type':'deleted'}) + return jsonify(result) + except (ha_views.HAError,ValueError,KeyError) as exc:return _json_error(exc) + +@app.get('/api/ha/weather') +def ha_weather_data(): + # Read-only route, token always stays on the Alfred server. + try: + q = request.args.get('q', '') + return jsonify(ha_weather.get_weather(q)) + except (ha_views.HAError, ValueError) as exc: + return _json_error(exc) + +@app.get('/api/ha/weather/outfits') +def ha_weather_outfits_data(): + try: + q = request.args.get('q', '') + refresh = request.args.get('refresh_illustration', '').lower() in ('1','true','yes','on') + return jsonify(weather_outfits.build_payload(q=q, refresh=refresh)) + except (ha_views.HAError, ValueError, RuntimeError) as exc: + return _json_error(exc) + +@app.get('/api/ha/temperatures') +def ha_temperatures(): + try:return jsonify({'items':ha_views.get_temperatures()}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/ha/temperature/history') +def ha_temperature_history(): + try: + eid=request.args.get('entity_id','') + hours=int(request.args.get('hours','24')) + return jsonify({'items':ha_views.temperature_history(eid,hours)}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/ha/cameras') +def ha_cameras(): + try:return jsonify({'items':ha_views.list_cameras()}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/ha/camera//snapshot') +def ha_camera_snapshot(entity_id): + try: + binary,mime=ha_views.camera_snapshot(entity_id) + return Response(binary,mimetype=mime,headers={'Cache-Control':'no-store','X-Content-Type-Options':'nosniff'}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.post('/api/ui/home') +def ui_home(): + with STATE_LOCK: + STATE['content']={'type':'home','text':'À ton écoute.'} + socketio.emit('alfred_render',STATE['content']) + 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") 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) + diff --git a/alfred/backup_meteo_tenues_v2_20261025_232200/services/alfred_views_controller.py b/alfred/backup_meteo_tenues_v2_20261025_232200/services/alfred_views_controller.py new file mode 100644 index 0000000..8984a8f --- /dev/null +++ b/alfred/backup_meteo_tenues_v2_20261025_232200/services/alfred_views_controller.py @@ -0,0 +1,132 @@ +"""Fast voice routing for visual actions; Hermes still handles general tasks.""" +from __future__ import annotations + +import os +from datetime import datetime, timedelta, time +from zoneinfo import ZoneInfo + +from . import ha_views as ha +from . import alfred_intents as intents +from . import ha_weather + +TZ = ZoneInfo(os.getenv('ALFRED_TIMEZONE','Europe/Paris')) + + +def _label(s,e): + return f"du {s:%d/%m} au {(e-timedelta(days=1)):%d/%m}" + + +def _event_summary(events, question): + count=len(events) + if count == 0:return "Non, je n'ai trouvé aucun événement pour cette période." if question else 'Aucun événement prévu sur cette période.' + names=', '.join(ev['title'] for ev in events[:3]) + if question: + return f"Oui, {count} événement{'s' if count>1 else ''} prévu{'s' if count>1 else ''}. {names}." if count<=3 else f"Oui, {count} événements sont prévus. Notamment : {names}." + return f"Voici ton planning avec {count} événement{'s' if count>1 else ''}." + + +def _calendar(text, question=False): + start,end,view=intents.period(text) + payload=ha.get_events(start,end) + content={'type':'calendar','view':view,'title':'Planning','start':start.isoformat(),'end':end.isoformat(), + 'events':payload['events'],'calendars':payload['calendars'],'errors':payload['errors']} + speech=_event_summary(payload['events'],question) + if payload['errors']: speech+=' Certains calendriers sont indisponibles.' + return content,speech + + +def _create(text): + calendars=ha.list_calendars() + details, complete=intents.extract_create(text,calendars) + if not complete: + return {'type':'calendar_form','values':details,'calendars':calendars},'Je prépare le rendez-vous. Complète ou vérifie les informations à l’écran avant de l’enregistrer.' + # No calendar mutation until a second explicit confirmation. + intents.set_create(details) + pretty=datetime.fromisoformat(details['date']).strftime('%d/%m') + speech=f"Je vais ajouter {details['title']} le {pretty} à {details['time']}. Dis confirme pour enregistrer, ou annule." + return {'type':'calendar_form','values':details,'calendars':calendars,'awaiting_confirmation':True},speech + + +def _delete(text): + now=datetime.now(TZ) + day=intents.parse_day(text,now) + if day: + start=datetime.combine(day,time.min,TZ); end=start+timedelta(days=1) + else: + start=datetime.combine(now.date(),time.min,TZ);end=start+timedelta(days=45) + data=ha.get_events(start,end) + needle=intents.extract_search(text) + events=[e for e in data['events'] if intents.simplify(needle) in intents.simplify(e['title'])] if needle else data['events'] + if not events: + return {'type':'calendar','view':'days','start':start.isoformat(),'end':end.isoformat(),**data},'Je ne trouve pas cet événement. Aucun événement n’a été supprimé.' + if len(events)!=1: + return {'type':'delete_candidates','events':events[:30]},f"J'ai trouvé {len(events)} événements correspondants. Choisis celui à supprimer sur l'écran." + ev=events[0] + if not ev['can_delete']: + return {'type':'delete_candidates','events':[ev]},'Ce calendrier ne permet pas la suppression de cet événement depuis Alfred.' + intents.set_delete(ev) + warning=' Attention, si cet événement est récurrent, cela peut supprimer toute la série.' if not ev['recurrence_id'] else '' + return {'type':'delete_candidates','events':[ev], 'awaiting_confirmation':True},f"Confirme la suppression de {ev['title']}, ou dis annule.{warning}" + + +def _confirm_delete(): + e=intents.current_delete() + if not e:return {'type':'text','text':'Confirmation expirée.'},'La demande de suppression a expiré.' + # Recheck existence in HA to avoid deleting an event that changed meanwhile. + start=datetime.fromisoformat(e['start'])-timedelta(days=1) + end=datetime.fromisoformat(e['end'])+timedelta(days=1) + found=ha.get_events(start,end,[e['calendar_id']])['events'] + if not any(x['id']==e['id'] and x['start']==e['start'] and x['recurrence_id']==e['recurrence_id'] for x in found): + intents.clear_delete() + return {'type':'text','text':'Cet événement a changé. Suppression annulée.'},'Cet événement a changé. Rien n’a été supprimé.' + ha.delete_event(e['calendar_id'],e['id'],e['recurrence_id'] or None) + intents.clear_delete() + content,speech=_calendar('planning sur 7 jours') + return content,'Événement supprimé. '+speech + + +def _confirm_create(): + data=intents.current_create() + if not data:return {'type':'text','text':'Confirmation expirée.'},'La demande de création a expiré.' + if os.getenv('ALFRED_ALLOW_CALENDAR_WRITES','false').lower() not in ('1','true','yes'): + return {'type':'calendar_form','values':data,'calendars':ha.list_calendars()},'L’écriture est désactivée. Active ALFRED_ALLOW_CALENDAR_WRITES dans la configuration.' + start=datetime.fromisoformat(data['date']+'T'+data['time']).replace(tzinfo=TZ) + end=start+timedelta(minutes=int(data.get('duration_minutes',60))) + ha.create_event(data['calendar_id'],data['title'],start,end) + intents.clear_create() + content,speech=_calendar('planning sur 7 jours') + return content,'Événement ajouté. '+speech + + +def route(text, last_view=None): + # Weather is a read-only dynamic view; avoid sending routine forecasts to Hermes. + if ha_weather.is_weather_request(text, last_view): + data = ha_weather.get_weather() + return data, ha_weather.describe(data, text) + intent=intents.classify(text) + # Short temporal follow-ups to a displayed calendar: 'Et demain ?', 'Ce matin ?' + if intent is None and last_view == 'calendar' and len(text.strip()) < 45: + t=intents.simplify(text) + if any(word in t for word in ('demain','matin','apres-midi','soir','journee','semaine','mois','jours','novembre','decembre','janvier','fevrier','mars','avril','mai','juin','juillet','aout','septembre','octobre')): + intent={'kind':'calendar','question':True} + if intent is None:return None + kind=intent['kind'] + if kind=='home':return {'type':'home','title':'Alfred','text':'À ton écoute.'},'Je reviens à l’accueil.' + if kind=='calendar':return _calendar(text, intent.get('question',False)) + if kind=='create':return _create(text) + if kind=='delete':return _delete(text) + if kind=='confirm_delete': + if os.getenv('ALFRED_ALLOW_CALENDAR_WRITES','false').lower() not in ('1','true','yes'): + return {'type':'text','text':'Écriture calendrier désactivée.'},'La suppression est désactivée dans la configuration.' + return _confirm_delete() + if kind=='confirm_create':return _confirm_create() + if kind=='cancel_pending': + intents.cancel_pending() + return {'type':'home','text':'Demande annulée.'},'D’accord, aucune modification effectuée.' + if kind=='temperatures': + temps=ha.get_temperatures() + return {'type':'temperatures','items':temps},f"Voici {len(temps)} mesures de température provenant de Home Assistant." + if kind=='cameras': + cameras=ha.list_cameras() + return {'type':'cameras','items':cameras},f"Voici {len(cameras)} caméras Home Assistant disponibles." + return None diff --git a/alfred/backup_meteo_tenues_v2_20261025_232200/services/weather_outfits.py b/alfred/backup_meteo_tenues_v2_20261025_232200/services/weather_outfits.py new file mode 100644 index 0000000..b96142f --- /dev/null +++ b/alfred/backup_meteo_tenues_v2_20261025_232200/services/weather_outfits.py @@ -0,0 +1,277 @@ +"""Outfit recommendations and ComfyUI illustration for Alfred weather view. + +This service reuses the same ComfyUI instance as Hermes, but calls ComfyUI +HTTP directly to avoid replacing Alfred's central view while generating the +illustration. +""" +from __future__ import annotations +from dotenv import load_dotenv +import hashlib +import json +import os +import time +import uuid +from datetime import datetime +from pathlib import Path +from urllib.parse import urlencode + +import requests + +from . import ha_weather + +load_dotenv("/opt/hermes/MCP_comfyui/.env") + +BASE = os.getenv( + "COMFYUI_URL", + "http://192.168.1.13:8188" +).rstrip("/") + +WORKFLOWS_DIR = Path( + os.getenv( + "WORKFLOWS_DIR", + "/opt/hermes/MCP_comfyui/workflows" + ) +) + + +CACHE_DIR = Path(os.getenv('ALFRED_CACHE_DIR', str(Path(__file__).resolve().parents[1] / 'cache'))) +CACHE_DIR.mkdir(parents=True, exist_ok=True) +CACHE_FILE = CACHE_DIR / 'weather_outfit_cache.json' +BASE = os.getenv('COMFYUI_URL', 'http://127.0.0.1:8188').rstrip('/') +WORKFLOWS_DIR = Path(os.getenv('WORKFLOWS_DIR', '/opt/hermes/MCP_comfyui/workflows')) +#WORKFLOWS_DIR = Path(os.getenv('WORKFLOWS_DIR', str(Path(__file__).parent / 'workflows'))) +PROMPT_NODE = os.getenv('COMFY_PROMPT_NODE', '6') +TIMEOUT = int(os.getenv('COMFYUI_TIMEOUT', '300')) +TTL_HOURS = int(os.getenv('ALFRED_WEATHER_ILLUSTRATION_TTL_HOURS', '12')) +ENABLED = os.getenv('ALFRED_WEATHER_ILLUSTRATIONS', 'true').lower() in ('1', 'true', 'yes', 'on') + + +def _load_cache() -> dict: + try: + return json.loads(CACHE_FILE.read_text(encoding='utf-8')) + except Exception: + return {} + + +def _save_cache(cache: dict) -> None: + CACHE_FILE.write_text(json.dumps(cache, ensure_ascii=False, indent=2), encoding='utf-8') + + +def _pick_day(data: dict) -> dict: + target = str(data.get('focus_date') or '') + for day in data.get('daily', []): + if str(day.get('date')) == target: + return day + if data.get('daily'): + return data['daily'][0] + current = data.get('current', {}) + return { + 'date': target or datetime.now().date().isoformat(), + 'condition': current.get('condition', 'unknown'), + 'description': current.get('description', 'conditions non précisées'), + 'temperature': current.get('temperature'), + 'templow': current.get('temperature'), + 'precipitation_probability': None, + 'wind_speed': current.get('wind_speed'), + } + + +def _temp_band(high: float | None, low: float | None) -> str: + ref = high if high is not None else low + if ref is None: + return 'mild' + if ref < 6: + return 'cold' + if ref < 14: + return 'cool' + if ref < 23: + return 'mild' + return 'warm' + + +def _recommend(day: dict) -> dict: + high = day.get('temperature') + low = day.get('templow') + pop = day.get('precipitation_probability') + wind = day.get('wind_speed') + cond = str(day.get('condition') or '') + band = _temp_band(high, low) + rainy = cond in {'rainy', 'pouring', 'lightning-rainy', 'snowy-rainy'} or (pop is not None and pop >= 45) + sunny = cond in {'sunny', 'partlycloudy'} + windy = wind is not None and wind >= 25 + cold = band in {'cold', 'cool'} + + child = [] + adult = [] + child_acc = [] + adult_acc = [] + + if band == 'cold': + child += ['doudoune chaude', 'pantalon chaud', 'chaussures fermées'] + adult += ['manteau chaud', 'pull', 'pantalon', 'chaussures fermées'] + child_acc += ['bonnet', 'écharpe'] + adult_acc += ['écharpe'] + elif band == 'cool': + child += ['veste légère', 'pantalon', 'baskets'] + adult += ['veste mi-saison', 'pull fin', 'jean', 'chaussures fermées'] + elif band == 'mild': + child += ['sweat ou gilet', 'pantalon léger', 'baskets'] + adult += ['veste légère', 'haut confortable', 'jean ou chino', 'baskets'] + else: + child += ['t-shirt', 'short ou pantalon léger', 'baskets légères'] + adult += ['t-shirt ou polo', 'tenue légère', 'chaussures légères'] + child_acc += ['casquette'] + adult_acc += ['lunettes de soleil'] + + if rainy: + child_acc += ['imperméable', 'parapluie'] + adult_acc += ['parapluie'] + if 'chaussures fermées' not in child: child.append('chaussures fermées') + if 'chaussures fermées' not in adult: adult.append('chaussures fermées') + if windy: + child_acc += ['coupe-vent'] + adult_acc += ['coupe-vent'] + if sunny and band != 'cold': + if 'casquette' not in child_acc: child_acc += ['casquette'] + if 'lunettes de soleil' not in adult_acc: adult_acc += ['lunettes de soleil'] + + def uniq(seq): + seen=[] + for x in seq: + if x not in seen: seen.append(x) + return seen + + return { + 'summary': day.get('description', 'Conditions du jour'), + 'child': { + 'title': 'Fille · 8 ans', + 'clothes': uniq(child), + 'accessories': uniq(child_acc), + }, + 'adult': { + 'title': 'Homme · 42 ans', + 'clothes': uniq(adult), + 'accessories': uniq(adult_acc), + } + } + + +def _weather_signature(day: dict, outfits: dict) -> str: + raw = json.dumps({'day': day, 'outfits': outfits}, ensure_ascii=False, sort_keys=True) + return hashlib.sha256(raw.encode('utf-8')).hexdigest() + + +def _extract_images(history: dict) -> list[dict]: + images = [] + for node_id, output in history.get('outputs', {}).items(): + for image in output.get('images', []): + filename = image.get('filename') + if not filename: + continue + subfolder = image.get('subfolder', '') + image_type = image.get('type', 'output') + query = urlencode({'filename': filename, 'subfolder': subfolder, 'type': image_type}) + images.append({'filename': filename, 'url': f'{BASE}/view?{query}', 'node_id': str(node_id)}) + return images + + +def _submit_workflow(prompt: str) -> str: + workflow_path = WORKFLOWS_DIR / 'text_to_image.json' + if not workflow_path.exists(): + raise RuntimeError(f'Workflow ComfyUI introuvable : {workflow_path}') + workflow = json.loads(workflow_path.read_text(encoding='utf-8')) + workflow[PROMPT_NODE]['inputs']['text'] = prompt + response = requests.post(f'{BASE}/prompt', json={'prompt': workflow, 'client_id': str(uuid.uuid4())}, timeout=30) + response.raise_for_status() + return response.json()['prompt_id'] + + +def _wait_history(prompt_id: str) -> dict: + deadline = time.monotonic() + TIMEOUT + while time.monotonic() < deadline: + response = requests.get(f'{BASE}/history/{prompt_id}', timeout=15) + response.raise_for_status() + result = response.json() + if prompt_id in result: + return result[prompt_id] + time.sleep(1) + raise RuntimeError(f'ComfyUI timeout ({prompt_id})') + + +def _illustration_prompt(day: dict, outfits: dict, focus_label: str) -> str: + high = day.get('temperature') + low = day.get('templow') + pop = day.get('precipitation_probability') + wind = day.get('wind_speed') + weather_bits = [day.get('description', 'conditions du jour')] + if high is not None: + weather_bits.append(f'température maximale {high}°C') + if low is not None: + weather_bits.append(f'température minimale {low}°C') + if pop is not None: + weather_bits.append(f'probabilité de pluie {pop}%') + if wind is not None: + weather_bits.append(f'vent {wind} km/h') + return ( + 'Crée une illustration verticale propre pour un tableau météo d’un assistant domestique. ' + 'Montre en pied deux personnages côte à côte, style illustration moderne semi-réaliste douce, lisible, familiale, fond simple et discret. ' + 'À gauche : une fille de 8 ans. À droite : un homme adulte de 42 ans. ' + f'Contexte météo pour {focus_label}: ' + ', '.join(weather_bits) + '. ' + 'Habille les personnages selon les recommandations suivantes. ' + f"Fille 8 ans: vêtements {', '.join(outfits['child']['clothes'])}; accessoires {', '.join(outfits['child']['accessories']) or 'aucun accessoire particulier'}. " + f"Homme 42 ans: vêtements {', '.join(outfits['adult']['clothes'])}; accessoires {', '.join(outfits['adult']['accessories']) or 'aucun accessoire particulier'}. " + 'Aucun texte dans l’image. Montrer clairement les tenues et accessoires conseillés. ' + 'Composition propre, adaptée à une interface d’assistant, haute qualité.' + ) + + +def _generate_illustration(day: dict, outfits: dict, focus_label: str, refresh: bool = False) -> dict | None: + if not ENABLED: + return None + sig = _weather_signature(day, outfits) + cache = _load_cache() + entry = cache.get(sig) + now_ts = time.time() + if entry and not refresh and (now_ts - float(entry.get('cached_at', 0))) < TTL_HOURS * 3600: + entry['from_cache'] = True + return entry + + prompt = _illustration_prompt(day, outfits, focus_label) + prompt_id = _submit_workflow(prompt) + history = _wait_history(prompt_id) + images = _extract_images(history) + if not images: + raise RuntimeError('ComfyUI a terminé sans image de sortie pour l’illustration météo.') + entry = { + 'url': images[0]['url'], + 'filename': images[0]['filename'], + 'prompt_id': prompt_id, + 'prompt': prompt, + 'cached_at': now_ts, + 'from_cache': False, + } + cache[sig] = entry + _save_cache(cache) + return entry + + +def build_payload(q: str = '', refresh: bool = False) -> dict: + data = ha_weather.get_weather(q) + day = _pick_day(data) + outfits = _recommend(day) + illustration = _generate_illustration(day, outfits, data.get('focus_label', 'aujourd’hui'), refresh=refresh) + return { + 'type': 'weather_outfits', + 'focus_label': data.get('focus_label', 'aujourd’hui'), + 'date': day.get('date'), + 'condition': day.get('condition'), + 'description': day.get('description'), + 'temperature': day.get('temperature'), + 'templow': day.get('templow'), + 'precipitation_probability': day.get('precipitation_probability'), + 'wind_speed': day.get('wind_speed'), + 'summary': outfits.get('summary'), + 'child': outfits['child'], + 'adult': outfits['adult'], + 'image': illustration, + } diff --git a/alfred/backup_meteo_tenues_v2_20261025_232200/static/css/alfred-views.css b/alfred/backup_meteo_tenues_v2_20261025_232200/static/css/alfred-views.css new file mode 100644 index 0000000..254f55c --- /dev/null +++ b/alfred/backup_meteo_tenues_v2_20261025_232200/static/css/alfred-views.css @@ -0,0 +1,91 @@ +/* Calendar and Home Assistant views — preserves original dark Alfred layout */ +#stage:has(.alfred-view) {padding:0 0 6px;justify-content:stretch;align-items:stretch} +#content.alfred-view {width:100%;height:100%;text-align:left;min-height:0;} +.view-pane {display:flex;flex-direction:column;gap:12px;max-height:100%;height:100%;min-height:0;padding:8px 1px 14px;} +.view-head {display:flex;justify-content:space-between;align-items:center;gap:12px;} +#content .view-title {font-size:clamp(1.2rem,1.7vw,1.85rem);margin:0;font-weight:500} +.view-close,.view-button,.view-pill {background:#363d42;color:#f3f7fa;border:1px solid #ffffff24;border-radius:10px;padding:9px 14px;cursor:pointer;font:inherit} +.view-close {font-size:1.4rem;min-width:40px;height:40px;padding:0} +.view-button:hover,.view-pill:hover,.view-close:hover {background:#455057} +.view-pill.active,.view-button.primary {background:#427789;border-color:#619aad} +.view-toolbar {display:flex;gap:9px;align-items:center;flex-wrap:wrap;} +.view-nav,.view-modes {display:flex;gap:5px;align-items:center} +.view-period {font-size:.85rem;min-width:215px;text-align:center;color:#d8e0e4} +.view-select {padding:9px;max-width:220px;border-radius:9px;background:#333a3e;color:#eff3f5;border:1px solid #ffffff2b} +.agenda-grid {display:grid;gap:5px;min-height:0;flex:1;overflow:auto;padding:2px;align-content:stretch;} +.agenda-seven,.agenda-month {grid-template-columns:repeat(7,minmax(0,1fr))} +.agenda-month {grid-template-rows:repeat(6,minmax(110px,1fr));} +.agenda-three {grid-template-columns:repeat(3,minmax(0,1fr))} +.agenda-one {grid-template-columns:1fr} +.agenda-day {border:1px solid #ffffff1c;border-radius:11px;min-width:0;min-height:120px;background:#ffffff08;padding:9px;display:flex;flex-direction:column;gap:6px;overflow-y:auto} +.agenda-day.is-today {border-color:#86bfda7a;background:#57839923} +.agenda-day.out-month {opacity:.42} +.agenda-date {font-size:.83rem;font-weight:600;color:#dce2e6;letter-spacing:.01em;padding-bottom:7px;border-bottom:1px solid #ffffff14} +.agenda-event {width:100%;text-align:left;background:color-mix(in srgb,var(--event-color) 17%,#2a2e32);color:#f1f3f5;border:0;border-left:3px solid var(--event-color);border-radius:5px;padding:7px 5px;font-size:clamp(.72rem,.85vw,.95rem);line-height:1.3;cursor:pointer;overflow-wrap:anywhere} +.agenda-event:hover {background:color-mix(in srgb,var(--event-color) 30%,#2a2e32)} +.agenda-empty {font-size:.8rem;color:#667781;margin:auto} +#content .view-footer {color:#9aa8b3;font-size:.76rem;margin:0} +#content .view-error {color:#f7adad;font-size:.85rem;margin:0} +#content .view-empty {font-size:1rem;color:#b8c2ca} +.agenda-form {display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-content:start;gap:14px;max-width:850px;width:100%;overflow-y:auto;margin:12px auto;} +.form-field {display:flex;flex-direction:column;gap:7px;color:#cfd6dd;font-size:.83rem} +.form-field input,.form-field select,.form-field textarea {padding:12px;border-radius:10px;color:#eff3f5;background:#2e3439;border:1px solid #ffffff32;font:inherit;min-width:0} +.form-field textarea {resize:vertical} +.form-check {display:flex;align-items:center;gap:10px} +.view-actions {display:flex;align-items:center;gap:10px;justify-content:flex-end;flex-wrap:wrap;} +.form-message {grid-column:1 / -1;} +.agenda-form .view-actions {grid-column:1 / -1} +.view-danger {color:#ffc4c4;background:#8f383850;border-color:#ed929255} +.detail-modal {position:fixed;inset:0;z-index:30;display:grid;place-items:center;background:#000b;padding:20px} +.detail-box {min-width:min(92vw,300px);max-width:540px;width:100%;border:1px solid #ffffff36;border-radius:19px;background:#292e33;box-shadow:0 25px 90px #000a;padding:24px;} +.detail-box h2 {font-size:1.4rem;margin:0 0 20px}.detail-box p {font-size:1rem;color:#d7dfe2;overflow-wrap:anywhere} +.delete-list {display:grid;gap:8px;overflow:auto}.delete-row {display:flex;justify-content:space-between;align-items:center;gap:10px;border:1px solid #ffffff1c;border-radius:9px;padding:12px} +.temperature-grid {display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px;overflow:auto;padding:4px;max-height:53%;} +.temperature-card {border:1px solid #ffffff22;background:#ffffff0a;border-radius:12px;padding:22px;color:#eaf1f3;white-space:pre-line;text-align:left;font:inherit;cursor:pointer;line-height:1.9} +.temperature-card:hover {background:#ffffff18} +.temperature-history {max-width:700px;text-align:center;margin:auto;width:100%;} +.temperature-history svg {width:100%;max-height:155px} +.temperature-history h2 {font-size:1.1rem;font-weight:450} +.ha-cameras {display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px;overflow:auto;} +.ha-camera-card {background:#ffffff0a;border-radius:13px;overflow:hidden;} +.ha-camera-card h2 {font-size:.95rem;padding:10px;font-weight:500;margin:0} +.ha-camera-card img {display:block;width:100%;aspect-ratio:16/9;object-fit:contain;background:#121619} +@media(max-width:1100px){.view-toolbar{gap:6px}.view-button,.view-pill {padding:7px;font-size:.77rem}.view-period{min-width:150px}.agenda-day{padding:5px}.agenda-event{padding:4px;font-size:.74rem}} +@media(max-width:760px){#stage:has(.alfred-view){min-height:460px}.agenda-grid{min-height:280px}.agenda-seven,.agenda-month{min-width:630px}.agenda-day{min-height:105px}.view-pane{max-height:600px}.view-head{padding:0 4px}.agenda-form{grid-template-columns:1fr}.header-actions{gap:3px}.header-actions .icon-button {width:38px;height:38px}.view-toolbar{overflow:auto}} + +/* Weather dynamic view — same dark UI as calendar. */ +.weather-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px} +.weather-now{display:flex;align-items:center;gap:22px;background:#293238;border:1px solid #ffffff16;border-radius:16px;padding:18px 24px;} +.weather-current-icon{font-size:clamp(44px,6vw,80px);line-height:1;color:#edcb82} +.weather-now-details h2{margin:0 0 8px;font-size:clamp(20px,2.1vw,32px);font-weight:500;color:#f5f6f7} +.weather-now-details p{margin:0;color:#b6c4ce;font-size:clamp(12px,1vw,17px)} +.weather-section-title{font-size:15px;letter-spacing:.02em;color:#bfd0da;margin:7px 0 -2px;font-weight:550} +.weather-days{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:7px} +.weather-day{min-width:0;display:flex;flex-direction:column;align-items:center;text-align:center;gap:5px;background:#293035;border-radius:12px;border:1px solid #ffffff12;padding:12px 5px 10px} +.weather-day p{margin:0} +.weather-day-name{font-size:12px;color:#d4e0e6} +.weather-day-icon{font-size:clamp(22px,2.8vw,40px);color:#eaca85} +.weather-day-condition{font-size:11px;color:#adbcc5;min-height:30px;line-height:1.3} +.weather-day-temp{font-size:21px;color:#f5f7f8;font-weight:600} +.weather-day-low,.weather-rain{font-size:11px;color:#a7b6c0} +.weather-rain{color:#8dc8e4 !important} +.weather-hours{display:flex;gap:7px;overflow-x:auto;padding:3px 0 10px;scrollbar-color:#637480 #2a3237} +.weather-hour{display:flex;flex:0 0 80px;align-items:center;flex-direction:column;gap:9px;padding:10px 6px;background:#293035;border:1px solid #ffffff16;border-radius:12px;color:#cbd7df;font-size:12px} +.weather-hour-icon{font-size:27px;color:#edd298} +@media(max-width:840px){.weather-days{grid-template-columns:repeat(4,minmax(0,1fr))}.weather-now{padding:14px;gap:12px}.weather-current-icon{font-size:48px}} +@media(max-width:470px){.weather-days{grid-template-columns:repeat(3,minmax(0,1fr))}} + + +/* Weather outfits + ComfyUI illustration */ +.weather-outfit-host{margin:8px 0 4px} +.weather-outfit-section{display:flex;flex-direction:column;gap:12px;background:#252d32;border:1px solid #ffffff16;border-radius:16px;padding:16px} +.weather-outfit-head{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap} +.weather-outfit-grid{display:grid;grid-template-columns:minmax(260px,360px) minmax(320px,1fr);gap:14px;align-items:stretch} +.weather-outfit-guide{display:grid;gap:12px} +.weather-person-card{background:#ffffff08;border:1px solid #ffffff16;border-radius:14px;padding:14px} +.weather-person-title{margin:0 0 6px;font-size:1.05rem;color:#f2f5f7} +.weather-person-subtitle{margin:10px 0 4px;color:#b8c7d1;font-size:.86rem} +.weather-outfit-list{margin:0;padding-left:18px;color:#e0e6ea;font-size:.95rem;line-height:1.55} +.weather-illustration-card{display:flex;flex-direction:column;justify-content:center;align-items:center;background:#ffffff07;border:1px solid #ffffff16;border-radius:14px;padding:12px;min-height:320px} +.weather-illustration-image{display:block;max-width:100%;max-height:430px;border-radius:12px;box-shadow:0 16px 40px #0005;object-fit:contain} +@media(max-width:980px){.weather-outfit-grid{grid-template-columns:1fr}.weather-illustration-card{min-height:220px}} diff --git a/alfred/backup_meteo_tenues_v2_20261025_232200/static/js/alfred-views.js b/alfred/backup_meteo_tenues_v2_20261025_232200/static/js/alfred-views.js new file mode 100644 index 0000000..c95dcf7 --- /dev/null +++ b/alfred/backup_meteo_tenues_v2_20261025_232200/static/js/alfred-views.js @@ -0,0 +1,353 @@ +/* Alfred • visual views, powered by first-party Flask endpoints. No extra CDN. */ +(() => { + 'use strict'; + const root = document.getElementById('content'); + let current = null, camerasRefresh = null; + const DAYS = ['lun.','mar.','mer.','jeu.','ven.','sam.','dim.']; + const PALETTE = ['#77bcdf','#e5b677','#9dc99a','#d3a2d5','#8cb7c9','#ec9f95','#b6b5e1','#b9d78d']; + const el = (tag, cls, text) => { const x=document.createElement(tag); if(cls)x.className=cls; if(text!==undefined)x.textContent=String(text); return x; }; + const btn = (label, fn, cls='view-button') => { const b=el('button',cls,label); b.type='button'; b.addEventListener('click',fn);return b; }; + const api = async (url, options) => { + const r=await fetch(url,{cache:'no-store', ...options}); + const data=await r.json().catch(()=>({error:`Erreur HTTP ${r.status}`})); + if(!r.ok)throw new Error(data.error || `HTTP ${r.status}`); + return data; + }; + const post = (url,data) => api(url,{method:'POST',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},body:JSON.stringify(data)}); + const dateLabel = iso => new Date(iso).toLocaleDateString('fr-FR',{day:'numeric',month:'long',year:'numeric'}); + const timeLabel = iso => new Date(iso).toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'}); + const localKey = d => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; + const atDay = (d,h=0) => new Date(d.getFullYear(),d.getMonth(),d.getDate(),h); + const dayShift = (d,n) => new Date(d.getFullYear(),d.getMonth(),d.getDate()+n,d.getHours(),d.getMinutes()); + const eventOnDay=(ev,day)=> {const start=new Date(ev.start),end=new Date(ev.end);return startatDay(day);}; + const fmtRange=(a,b)=>`${dateLabel(a)} — ${dateLabel(new Date(new Date(b).getTime()-1000).toISOString())}`; + const wrap = (title) => { + clearInterval(camerasRefresh);camerasRefresh=null;root.replaceChildren(); + root.classList.add('alfred-view'); + const pane=el('div','view-pane'),head=el('header','view-head'); + head.append(el('h1','view-title',title)); + head.append(btn('×',goHome,'view-close')); + pane.append(head);root.append(pane);return {pane,head}; + }; + const showError=(err,pane)=>pane.append(el('p','view-error',err.message||String(err))); + const canRender = data=>['calendar','calendar_form','delete_candidates','temperatures','cameras','weather','home'].includes(data?.type); + function render(data){ + if(!canRender(data))return false; + current=data; + if(data.type==='calendar')drawCalendar(data); + if(data.type==='calendar_form')drawForm(data); + if(data.type==='delete_candidates')drawDeleteChoices(data); + if(data.type==='temperatures')drawTemps(data); + if(data.type==='weather')drawWeather(data); + if(data.type==='cameras')drawCameras(data); + if(data.type==='home'){root.classList.remove('alfred-view');root.replaceChildren(el('h1','', 'Bonjour.'), el('p','',data.text||'À ton écoute.')); clearInterval(camerasRefresh);camerasRefresh=null;} + return true; + } + const getSelected=()=>document.getElementById('calendar-source')?.value || ''; + async function loadCalendar(start,end,view,filter){ + const q=new URLSearchParams({start:new Date(start).toISOString(),end:new Date(end).toISOString()}); + if(filter)q.append('calendar_id',filter); + const data=await api('/api/calendar/events?'+q); + render({type:'calendar',view,start:new Date(start).toISOString(),end:new Date(end).toISOString(),...data,selected:filter||''}); + } + function moveCalendar(direction){ + if(!current || current.type!=='calendar')return; + const start=new Date(current.start),end=new Date(current.end),view=current.view; + let a,b; + if(view==='month'){ + a=new Date(start.getFullYear(),start.getMonth()+direction,1); + b=new Date(a.getFullYear(),a.getMonth()+1,1); + }else{ + const days=Math.round((end-start)/86400000) || 1; + a=dayShift(start,days*direction);b=dayShift(end,days*direction); + } + loadCalendar(a,b,view,getSelected()).catch(err=>{const {pane}=wrap('Agenda');showError(err,pane)}); + } + const details = (event) => { + const d=el('div','detail-modal'),box=el('section','detail-box'); + box.setAttribute('role','dialog');box.setAttribute('aria-modal','true');box.setAttribute('aria-label','Détails de l’événement'); + box.append(el('h2','',event.title)); + box.append(el('p','',`${dateLabel(event.start)}${event.all_day?' · Toute la journée':' · '+timeLabel(event.start)+'–'+timeLabel(event.end)}`)); + box.append(el('p','',event.calendar_name)); + if(event.location)box.append(el('p','',`Lieu : ${event.location}`)); + if(event.description)box.append(el('p','',event.description)); + const acts=el('div','view-actions');acts.append(btn('Fermer',()=>d.remove())); + if(event.can_delete) acts.append(btn('Supprimer…',async()=>{ + const recurrence=event.recurrence_id ? 'Cela supprimera cette occurrence.' : 'Pour une série récurrente, cela peut supprimer toute la série.'; + if(!confirm(`Supprimer « ${event.title} » du calendrier « ${event.calendar_name} » ?\n${recurrence}`))return; + try{await post('/api/calendar/delete',{calendar_id:event.calendar_id,uid:event.id,start:event.start,end:event.end,recurrence_id:event.recurrence_id||''}); d.remove();await loadCalendar(current.start,current.end,current.view,getSelected());} + catch(err){alert(err.message)} + },'view-danger')); + box.append(acts);d.append(box);document.body.append(d); + d.addEventListener('click',e=>{if(e.target===d)d.remove()}); + }; + function drawCalendar(data){ + const {pane,head}=wrap('Planning'); + const toolbar=el('div','view-toolbar'); + const first=el('div','view-nav'); + first.append(btn('‹',()=>moveCalendar(-1)),el('span','view-period',fmtRange(data.start,data.end)),btn('›',()=>moveCalendar(1))); + toolbar.append(first); + const modes=el('div','view-modes'); + [['Jour',1,'day'],['3 jours',3,'days'],['7 jours',7,'days'],['Semaine',7,'week'],['Mois',31,'month']].forEach(([label,n,v])=>{ + const b=btn(label,()=>{ + const now=new Date(),start=(v==='week')?dayShift(atDay(now),-((now.getDay()+6)%7)):v==='month'?new Date(now.getFullYear(),now.getMonth(),1):atDay(now); + const end=v==='month'?new Date(start.getFullYear(),start.getMonth()+1,1):dayShift(start,n); + loadCalendar(start,end,v,getSelected()).catch(err=>showError(err,pane)); + },'view-pill'+(data.view===v?' active':''));modes.append(b); + });toolbar.append(modes); + const src=el('select','view-select');src.id='calendar-source'; + src.append(new Option('Tous les calendriers','')); + (data.calendars||[]).forEach(c=>src.append(new Option(c.name,c.entity_id))); + src.value=data.selected||''; + src.addEventListener('change',()=>loadCalendar(data.start,data.end,data.view,src.value).catch(err=>showError(err,pane))); + toolbar.append(src,btn('+ Rendez-vous',()=>openForm())); + pane.append(toolbar); + if(data.errors?.length)pane.append(el('p','view-error','Calendriers partiellement indisponibles : '+data.errors.join(' · '))); + const grid=el('div','agenda-grid'); + const start=new Date(data.start),end=new Date(data.end); + let days=[],cursor=atDay(start); + const month=data.view==='month'; + if(month) cursor=dayShift(cursor,-((cursor.getDay()+6)%7)); + let limit=0; + while(cursor[c.entity_id, PALETTE[i%PALETTE.length]])); + days.forEach(d=>{ + const cell=el('section','agenda-day'); + if(month && d.getMonth()!==start.getMonth())cell.classList.add('out-month'); + if(localKey(d)===localKey(new Date()))cell.classList.add('is-today'); + cell.append(el('div','agenda-date',d.toLocaleDateString('fr-FR',{weekday:'short',day:'numeric',month:month?undefined:'short'}))); + const events=(data.events||[]).filter(ev=>eventOnDay(ev,d)); + if(!events.length)cell.append(el('div','agenda-empty','—')); + events.forEach(ev=>{ + const e=btn((ev.all_day?'':timeLabel(ev.start)+' ')+ev.title,()=>details(ev),'agenda-event'); + e.style.setProperty('--event-color',colors.get(ev.calendar_id)||'#7bbbe2'); + e.title=`${ev.title} · ${ev.calendar_name}`;cell.append(e); + }); + grid.append(cell); + }); + pane.append(grid); + pane.append(el('p','view-footer',`${data.events?.length||0} événement(s) · ${data.calendars?.length||0} calendrier(s) Home Assistant`)); + } + async function openForm(values){ + let calendars=[]; + try {const info=await api('/api/calendar/calendars');calendars=info.calendars||[];} + catch(err){const {pane}=wrap('Nouveau rendez-vous');showError(err,pane);return;} + render({type:'calendar_form',calendars,values:values||{}}); + } + function drawForm(data){ + const {pane}=wrap('Ajouter un événement'); + const v=data.values||{}; + const form=el('form','agenda-form');form.autocomplete='off'; + const field=(label,node)=>{const row=el('label','form-field');row.append(el('span','',label),node);form.append(row);return node}; + const makeInput=(type,value)=>{const i=el('input');i.type=type;i.value=value||'';i.required=true;return i}; + const title=field('Titre',makeInput('text',v.title));title.maxLength=200; + const src=el('select');src.required=true;src.append(new Option('Choisir un calendrier','')); + data.calendars.filter(c=>c.can_create).forEach(c=>src.append(new Option(c.name,c.entity_id))); + src.value=v.calendar_id||'';field('Calendrier',src); + const day=field('Date',makeInput('date',v.date||localKey(new Date()))); + const startTime=field('Début',makeInput('time',v.time||'09:00')); + const duration=field('Durée (minutes)',makeInput('number',v.duration_minutes||60));duration.min=15;duration.max=1440; + const location=field('Lieu (facultatif)',makeInput('text',''));location.required=false; + const description=field('Description (facultatif)',el('textarea'));description.required=false;description.rows=2; + const allDay=el('input');allDay.type='checkbox';allDay.required=false; + const allDayWrap=el('label','form-check');allDayWrap.append(allDay,el('span','','Toute la journée'));form.append(allDayWrap); + allDay.addEventListener('change',()=>{startTime.disabled=allDay.checked;duration.disabled=allDay.checked}); + const msg=el('p','form-message'); + const actions=el('div','view-actions');actions.append(btn('Annuler',goHome)); + const submit=el('button','view-button primary','Enregistrer');submit.type='submit';actions.append(submit); + form.append(actions,msg);pane.append(form); + form.addEventListener('submit',async e=>{ + e.preventDefault();submit.disabled=true;msg.textContent='Enregistrement…'; + try{ + const from=allDay.checked?new Date(day.value+'T00:00'):new Date(day.value+'T'+startTime.value+':00'); + if(Number.isNaN(from.getTime()))throw new Error('Date invalide.'); + const end=allDay.checked?dayShift(from,1):new Date(from.getTime()+Number(duration.value)*60000); + await post('/api/calendar/create',{calendar_id:src.value,title:title.value,start:from.toISOString(),end:end.toISOString(),all_day:allDay.checked,location:location.value,description:description.value}); + const first=atDay(from);await loadCalendar(first,dayShift(first,7),'days'); + }catch(err){msg.textContent=err.message;submit.disabled=false} + }); + } + function drawDeleteChoices(data){ + const {pane}=wrap('Choisir un événement à supprimer'); + const arr=el('div','delete-list'); + (data.events||[]).forEach(ev=>{ + const row=el('div','delete-row');row.append(el('span','',`${ev.title} • ${dateLabel(ev.start)} • ${ev.calendar_name}`)); + row.append(btn('Détails / Supprimer',()=>details(ev),'view-button'));arr.append(row); + });pane.append(arr); + if(data.awaiting_confirmation)pane.append(el('p','view-footer','Dis « confirme » pour supprimer, ou « annule ».')); + } + function drawTemps(data){ + const {pane}=wrap('Températures Home Assistant'); + if(!data.items?.length){pane.append(el('p','view-empty','Aucun capteur de température trouvé.'));return} + const grid=el('div','temperature-grid');pane.append(grid); + data.items.forEach(item=>{ + const c=btn(`${item.name}\n${item.value.toFixed(1)} ${item.unit}` ,async()=>{ + const q=new URLSearchParams({entity_id:item.entity_id,hours:'24'}); + try{const data=await api('/api/ha/temperature/history?'+q);showHistory(item,data.items||[],pane)} + catch(err){showError(err,pane)} + },'temperature-card');grid.append(c); + });pane.append(el('p','view-footer','Touchez un capteur pour afficher son historique sur 24 h.')); + } + function showHistory(item,points,pane){ + pane.querySelector('.temperature-history')?.remove(); + const section=el('section','temperature-history');section.append(el('h2','',item.name+' · 24 dernières heures')); + if(points.length<2){section.append(el('p','', 'Historique insuffisant.'));pane.append(section);return} + const vals=points.map(p=>p.value);const lo=Math.min(...vals)-0.4,hi=Math.max(...vals)+0.4; + const a=Date.parse(points[0].time),b=Date.parse(points[points.length-1].time); + const coords=points.map((p,i)=>`${30+560*((Date.parse(p.time)-a)/Math.max(1,b-a))},${140-110*((p.value-lo)/Math.max(0.1,hi-lo))}`).join(' '); + const ns='http://www.w3.org/2000/svg',svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 620 170');svg.setAttribute('role','img');svg.setAttribute('aria-label','Courbe de température'); + const poly=document.createElementNS(ns,'polyline');poly.setAttribute('points',coords);poly.setAttribute('fill','none');poly.setAttribute('stroke','#83c5cc');poly.setAttribute('stroke-width','3');svg.append(poly); + section.append(svg,el('p','',`Min ${Math.min(...vals).toFixed(1)}° · Max ${Math.max(...vals).toFixed(1)}°`));pane.append(section); + } + function drawCameras(data){ + const {pane}=wrap('Caméras Home Assistant'); + if(!data.items?.length){pane.append(el('p','view-empty','Aucune caméra Home Assistant disponible.'));return} + const grid=el('div','ha-cameras');pane.append(grid); + const imgs=[]; + data.items.forEach(item=>{ + const card=el('div','ha-camera-card');card.append(el('h2','',item.name)); + const img=el('img');img.alt='Instantané caméra '+item.name;img.loading='lazy';imgs.push([img,item.entity_id]);card.append(img);grid.append(card); + }); + const reload=()=>{if(document.hidden)return; imgs.forEach(([img,eid])=>img.src='/api/ha/camera/'+encodeURIComponent(eid)+'/snapshot?t='+Date.now())}; + reload();camerasRefresh=setInterval(reload,15000); + pane.append(el('p','view-footer','Instantanés réactualisés toutes les 15 s lorsque cette vue est ouverte.')); + } + + const WEATHER_ICONS = { + 'sunny':'☀', 'clear-night':'☾', 'cloudy':'☁', 'partlycloudy':'⛅', + 'fog':'≋', 'rainy':'🌧', 'pouring':'🌧', 'hail':'🌨', + 'lightning':'⛈', 'lightning-rainy':'⛈', 'snowy':'❄', + 'snowy-rainy':'🌨', 'windy':'〰', 'windy-variant':'〰' + }; + const weatherIcon = cond => WEATHER_ICONS[cond] || '◌'; + const tempText = (val, unit='°C') => typeof val === 'number' ? `${Math.round(val)}${unit}` : '—'; + function weatherList(items){ + const ul=el('ul','weather-outfit-list'); + (items||[]).forEach(text=>ul.append(el('li','',text))); + if(!ul.childNodes.length)ul.append(el('li','','Aucun accessoire particulier')); + return ul; + } + async function loadWeatherOutfits(container, query='', refresh=false){ + container.replaceChildren(); + const loading=el('p','view-footer', refresh ? 'Régénération de l’illustration…' : 'Préparation des tenues conseillées…'); + container.append(loading); + try{ + const q = new URLSearchParams(); + if(query)q.set('q',query); + if(refresh)q.set('refresh_illustration','1'); + const data = await api('/api/ha/weather/outfits' + (q.toString() ? '?' + q.toString() : '')); + const section=el('section','weather-outfit-section'); + const head=el('div','weather-outfit-head'); + head.append(el('h2','weather-section-title',`Tenues conseillées · ${data.focus_label || 'aujourd’hui'}`)); + head.append(btn('Régénérer l’illustration',()=>loadWeatherOutfits(container, query, true),'view-pill')); + section.append(head); + + const grid=el('div','weather-outfit-grid'); + const guide=el('div','weather-outfit-guide'); + [data.child, data.adult].forEach(person=>{ + const card=el('article','weather-person-card'); + card.append(el('h3','weather-person-title',person.title)); + card.append(el('p','weather-person-subtitle','Tenue recommandée')); + card.append(weatherList(person.clothes)); + card.append(el('p','weather-person-subtitle','Accessoires')); + card.append(weatherList(person.accessories)); + guide.append(card); + }); + grid.append(guide); + const art=el('div','weather-illustration-card'); + if(data.image?.url){ + const img=el('img','weather-illustration-image'); + img.src=data.image.url; + img.alt='Illustration des tenues conseillées selon la météo'; + art.append(img); + const foot=el('p','view-footer', data.image.from_cache ? 'Illustration mise en cache.' : 'Illustration générée par ComfyUI.'); + art.append(foot); + } else { + art.append(el('p','view-empty','Illustration indisponible pour le moment.')); + } + grid.append(art); + section.append(grid); + if(data.summary){ section.append(el('p','view-footer',`Conseil général : ${data.summary}.`)); } + container.replaceChildren(section); + }catch(err){ + container.replaceChildren(el('p','view-error',`Tenues conseillées indisponibles : ${err.message || err}`)); + } + } + function drawWeather(data){ + const {pane}=wrap('Météo · '+(data.name||'Home Assistant')); + const nav=el('div','weather-toolbar'); + nav.append(btn('Actualiser',openWeather,'view-pill')); + nav.append(el('span','view-footer','Source : Home Assistant')); + pane.append(nav); + const c=data.current||{},now=el('section','weather-now'); + const badge=el('div','weather-current-icon',weatherIcon(c.condition));badge.setAttribute('aria-hidden','true'); + const details=el('div','weather-now-details'); + details.append(el('h2','',`${tempText(c.temperature,data.temperature_unit)} · ${c.description||'Conditions inconnues'}`)); + const metrics=[]; + if(c.apparent_temperature!==null && c.apparent_temperature!==undefined)metrics.push(`Ressenti ${tempText(c.apparent_temperature,data.temperature_unit)}`); + if(c.humidity!==null && c.humidity!==undefined)metrics.push(`Humidité ${c.humidity}%`); + if(c.wind_speed!==null && c.wind_speed!==undefined)metrics.push(`Vent ${c.wind_speed} ${data.wind_speed_unit||'km/h'}`); + details.append(el('p','',metrics.join(' · ')||'Conditions actuelles'));now.append(badge,details);pane.append(now); + + const outfitHost=el('div','weather-outfit-host'); + pane.append(outfitHost); + loadWeatherOutfits(outfitHost, data.focus_label || ''); + + const heading = text => pane.append(el('h2','weather-section-title',text)); + heading('Prévisions pour les prochains jours'); + if(data.daily?.length){ + const cards=el('div','weather-days'); + data.daily.slice(0,7).forEach(day=>{ + const d=new Date(day.datetime); + const box=el('article','weather-day'); + box.append(el('p','weather-day-name',d.toLocaleDateString('fr-FR',{weekday:'short',day:'numeric',month:'short'}))); + box.append(el('div','weather-day-icon',weatherIcon(day.condition))); + box.append(el('p','weather-day-condition',day.description)); + box.append(el('p','weather-day-temp',tempText(day.temperature,data.temperature_unit))); + if(day.templow!==null)box.append(el('p','weather-day-low','Min '+tempText(day.templow,data.temperature_unit))); + if(day.precipitation_probability!==null)box.append(el('p','weather-rain','Pluie '+day.precipitation_probability+' %')); + cards.append(box); + });pane.append(cards); + }else pane.append(el('p','view-empty','Prévisions journalières non disponibles.')); + heading('Prochaines heures'); + if(data.hourly?.length){ + const list=el('div','weather-hours'), nowMs=Date.now(); + data.hourly.filter(x=>new Date(x.datetime).getTime()>=nowMs-3600000).slice(0,12).forEach(h=>{ + const box=el('div','weather-hour'); + box.append(el('span','',new Date(h.datetime).toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'}))); + box.append(el('span','weather-hour-icon',weatherIcon(h.condition))); + box.append(el('strong','',tempText(h.temperature,data.temperature_unit))); + if(h.precipitation_probability!==null)box.append(el('span','weather-rain',h.precipitation_probability+' %')); + list.append(box); + });pane.append(list); + }else pane.append(el('p','view-empty','Prévisions horaires non disponibles.')); + if(data.errors?.length)pane.append(el('p','view-footer','Certaines prévisions ne sont pas fournies par cette source météo.')); + } + async function openWeather(){ + try{render(await api('/api/ha/weather'))} + catch(err){const {pane}=wrap('Météo');showError(err,pane)} + } + + async function goHome(){ + try{await post('/api/ui/home',{})}catch(err){render({type:'home'})} + } + async function openCalendar(){ + const d=atDay(new Date());try{await loadCalendar(d,dayShift(d,7),'days')}catch(err){const {pane}=wrap('Agenda');showError(err,pane)} + } + async function openTemperatures(){ + try{render({type:'temperatures',...await api('/api/ha/temperatures')})}catch(err){const {pane}=wrap('Températures');showError(err,pane)} + } + async function openCameras(){ + try{render({type:'cameras',...await api('/api/ha/cameras')})}catch(err){const {pane}=wrap('Caméras');showError(err,pane)} + } + document.getElementById('viewHome')?.addEventListener('click',goHome); + document.getElementById('viewCalendar')?.addEventListener('click',openCalendar); + document.getElementById('viewTemperatures')?.addEventListener('click',openTemperatures); + document.getElementById('viewWeather')?.addEventListener('click',openWeather); + document.getElementById('viewCameras')?.addEventListener('click',openCameras); + const socket=window.alfredSocket; + socket?.on('calendar_changed',()=>{if(current?.type==='calendar') loadCalendar(current.start,current.end,current.view,getSelected()).catch(console.warn)}); + window.AlfredViews={render,canRender,openCalendar,openTemperatures,openCameras,openWeather,goHome}; +})(); diff --git a/alfred/backup_meteo_tenues_v3_20261025_233732/services/weather_outfits.py b/alfred/backup_meteo_tenues_v3_20261025_233732/services/weather_outfits.py new file mode 100644 index 0000000..3b93dd3 --- /dev/null +++ b/alfred/backup_meteo_tenues_v3_20261025_233732/services/weather_outfits.py @@ -0,0 +1,355 @@ +"""Outfit recommendations and ComfyUI illustration for Alfred weather view. + +This service reuses the same ComfyUI instance as Hermes, but calls ComfyUI +HTTP directly to avoid replacing Alfred's central view while generating the +illustration. +""" +from __future__ import annotations + +import hashlib +import json +import os +import time +import uuid +from datetime import datetime, date as Date +from collections import Counter +from pathlib import Path +from urllib.parse import urlencode + +import requests +from dotenv import load_dotenv, dotenv_values + +# Charger l'environnement avant de calculer BASE et WORKFLOWS_DIR. +# Flask exécute son load_dotenv() après l'import de ce module. +load_dotenv() +load_dotenv('/opt/hermes/MCP_comfyui/.env', override=False) +_MCP_SETTINGS = dotenv_values('/opt/hermes/MCP_comfyui/.env') + +from . import ha_weather + +CACHE_DIR = Path(os.getenv('ALFRED_CACHE_DIR', str(Path(__file__).resolve().parents[1] / 'cache'))) +CACHE_DIR.mkdir(parents=True, exist_ok=True) +CACHE_FILE = CACHE_DIR / 'weather_outfit_cache.json' +BASE = (os.getenv('ALFRED_COMFYUI_URL') or _MCP_SETTINGS.get('COMFYUI_URL') or os.getenv('COMFYUI_URL') or 'http://192.168.1.13:8188').rstrip('/') +WORKFLOWS_DIR = Path(os.getenv('ALFRED_COMFYUI_WORKFLOWS_DIR') or os.getenv('WORKFLOWS_DIR') or '/opt/hermes/MCP_comfyui/workflows') +PROMPT_NODE = os.getenv('COMFY_PROMPT_NODE', '6') +TIMEOUT = int(os.getenv('COMFYUI_TIMEOUT', '300')) +TTL_HOURS = int(os.getenv('ALFRED_WEATHER_ILLUSTRATION_TTL_HOURS', '12')) +ENABLED = os.getenv('ALFRED_WEATHER_ILLUSTRATIONS', 'true').lower() in ('1', 'true', 'yes', 'on') +IMAGE_SIZE = min(1024, max(512, int(os.getenv('ALFRED_WEATHER_COMFY_SIZE', '768')))) +PROMPT_VERSION = 'outfits-v2-20261025' + + +def _load_cache() -> dict: + try: + return json.loads(CACHE_FILE.read_text(encoding='utf-8')) + except Exception: + return {} + + +def _save_cache(cache: dict) -> None: + CACHE_FILE.write_text(json.dumps(cache, ensure_ascii=False, indent=2), encoding='utf-8') + + +def _pick_day(data: dict, forced_date: str | None = None) -> dict: + """Retourne uniquement la météo du jour demandé ; jamais une autre journée.""" + target = str(forced_date or data.get('focus_date') or '').strip() + try: + requested = Date.fromisoformat(target) + except ValueError as exc: + raise ValueError('Date météo invalide : format AAAA-MM-JJ attendu.') from exc + + for day in data.get('daily', []): + if str(day.get('date')) == target: + return day + + # Certaines intégrations HA ne donnent que des prévisions horaires. + matching = [h for h in data.get('hourly', []) if h.get('date') == target] + if matching: + def available(key): + return [float(h[key]) for h in matching if h.get(key) is not None] + temps = available('temperature') + chances = available('precipitation_probability') + winds = available('wind_speed') + conditions = [str(h['condition']) for h in matching if h.get('condition')] + condition = Counter(conditions).most_common(1)[0][0] if conditions else 'unknown' + return { + 'date': target, + 'condition': condition, + 'description': ha_weather.CONDITIONS.get(condition, 'conditions non précisées'), + 'temperature': max(temps) if temps else None, + 'templow': min(temps) if temps else None, + 'precipitation_probability': max(chances) if chances else None, + 'wind_speed': max(winds) if winds else None, + } + + # Les observations actuelles ne doivent être utilisées QUE pour aujourd'hui. + if requested == datetime.now(ha_weather.TZ).date(): + current = data.get('current', {}) + return { + 'date': target, + 'condition': current.get('condition', 'unknown'), + 'description': current.get('description', 'conditions non précisées'), + 'temperature': current.get('temperature'), + 'templow': current.get('temperature'), + 'precipitation_probability': None, + 'wind_speed': current.get('wind_speed'), + } + raise ValueError(f'Aucune prévision Home Assistant disponible pour le {requested:%d/%m/%Y}. Aucune image ne sera générée avec les données d’un autre jour.') + + +def _temp_band(high: float | None, low: float | None) -> str: + ref = high if high is not None else low + if ref is None: + return 'mild' + if ref < 6: + return 'cold' + if ref < 14: + return 'cool' + if ref < 23: + return 'mild' + return 'warm' + + +def _recommend(day: dict) -> dict: + high = day.get('temperature') + low = day.get('templow') + pop = day.get('precipitation_probability') + wind = day.get('wind_speed') + cond = str(day.get('condition') or '') + band = _temp_band(high, low) + rainy = cond in {'rainy', 'pouring', 'lightning-rainy', 'snowy-rainy'} or (pop is not None and pop >= 45) + sunny = cond in {'sunny', 'partlycloudy'} + windy = wind is not None and wind >= 25 + cold = band in {'cold', 'cool'} + + child = [] + adult = [] + child_acc = [] + adult_acc = [] + + if band == 'cold': + child += ['doudoune chaude', 'pantalon chaud', 'chaussures fermées'] + adult += ['manteau chaud', 'pull', 'pantalon', 'chaussures fermées'] + child_acc += ['bonnet', 'écharpe'] + adult_acc += ['écharpe'] + elif band == 'cool': + child += ['veste légère', 'pantalon', 'baskets'] + adult += ['veste mi-saison', 'pull fin', 'jean', 'chaussures fermées'] + elif band == 'mild': + child += ['sweat ou gilet', 'pantalon léger', 'baskets'] + adult += ['veste légère', 'haut confortable', 'jean ou chino', 'baskets'] + else: + child += ['t-shirt', 'short ou pantalon léger', 'baskets légères'] + adult += ['t-shirt ou polo', 'tenue légère', 'chaussures légères'] + child_acc += ['casquette'] + adult_acc += ['lunettes de soleil'] + + if rainy: + child_acc += ['imperméable', 'parapluie'] + adult_acc += ['parapluie'] + if 'chaussures fermées' not in child: child.append('chaussures fermées') + if 'chaussures fermées' not in adult: adult.append('chaussures fermées') + if windy: + child_acc += ['coupe-vent'] + adult_acc += ['coupe-vent'] + if sunny and band != 'cold': + if 'casquette' not in child_acc: child_acc += ['casquette'] + if 'lunettes de soleil' not in adult_acc: adult_acc += ['lunettes de soleil'] + + def uniq(seq): + seen=[] + for x in seq: + if x not in seen: seen.append(x) + return seen + + return { + 'summary': day.get('description', 'Conditions du jour'), + 'child': { + 'title': 'Fille · 8 ans', + 'clothes': uniq(child), + 'accessories': uniq(child_acc), + }, + 'adult': { + 'title': 'Homme · 42 ans', + 'clothes': uniq(adult), + 'accessories': uniq(adult_acc), + } + } + + +def _weather_signature(day: dict, outfits: dict) -> str: + # Version explicite pour ne pas ressortir des images incorrectes de l'ancien cache. + raw = json.dumps({'version': PROMPT_VERSION, 'date': day.get('date'), + 'day': day, 'outfits': outfits, 'size': IMAGE_SIZE}, + ensure_ascii=False, sort_keys=True) + return hashlib.sha256(raw.encode('utf-8')).hexdigest() + + +def _extract_images(history: dict) -> list[dict]: + images = [] + for node_id, output in history.get('outputs', {}).items(): + for image in output.get('images', []): + filename = image.get('filename') + if not filename: + continue + subfolder = image.get('subfolder', '') + image_type = image.get('type', 'output') + query = urlencode({'filename': filename, 'subfolder': subfolder, 'type': image_type}) + images.append({'filename': filename, 'url': f'{BASE}/view?{query}', 'node_id': str(node_id)}) + return images + + +NEGATIVE_PROMPT = ( + 'deformed face, distorted face, disfigured features, asymmetric eyes, extra eyes, ' + 'extra fingers, missing fingers, malformed hands, bad anatomy, extra limbs, ' + 'extra people, multiple faces on one person, two adults, two teenage girls, ' + 'adult woman, teenage woman, teenager, old child, blurry, out of focus, ' + 'photorealistic uncanny face, text, watermark, logos' +) + + +def _prepare_workflow(workflow: dict, prompt: str) -> dict: + """Modifie seulement les noeuds reconnus du workflow API existant d'Hermes.""" + node = workflow.get(PROMPT_NODE) + if not isinstance(node, dict) or not isinstance(node.get('inputs'), dict) or 'text' not in node['inputs']: + raise ValueError(f'Noeud prompt ComfyUI {PROMPT_NODE} introuvable ou incompatible.') + node['inputs']['text'] = prompt + for sampler in workflow.values(): + if sampler.get('class_type') not in ('KSampler', 'KSamplerAdvanced'): + continue + negative_link = (sampler.get('inputs') or {}).get('negative') + if not isinstance(negative_link, (list, tuple)) or len(negative_link) < 1: + continue + neg = workflow.get(str(negative_link[0])) + if neg and neg.get('class_type') == 'CLIPTextEncode' and 'text' in neg.get('inputs', {}): + neg['inputs']['text'] = NEGATIVE_PROMPT + # Taille carrée: uniquement les noeuds latents natifs compatibles. + for latent in workflow.values(): + if latent.get('class_type') in ('EmptyLatentImage', 'EmptySD3LatentImage'): + inputs = latent.get('inputs') or {} + if 'width' in inputs and 'height' in inputs: + inputs['width'] = IMAGE_SIZE + inputs['height'] = IMAGE_SIZE + return workflow + + +def _submit_workflow(prompt: str) -> str: + workflow_path = WORKFLOWS_DIR / 'text_to_image.json' + if not workflow_path.exists(): + raise RuntimeError(f'Workflow ComfyUI introuvable : {workflow_path}') + workflow = _prepare_workflow(json.loads(workflow_path.read_text(encoding='utf-8')), prompt) + response = requests.post(f'{BASE}/prompt', json={'prompt': workflow, 'client_id': str(uuid.uuid4())}, timeout=30) + response.raise_for_status() + return response.json()['prompt_id'] + + +def _wait_history(prompt_id: str) -> dict: + deadline = time.monotonic() + TIMEOUT + while time.monotonic() < deadline: + response = requests.get(f'{BASE}/history/{prompt_id}', timeout=15) + response.raise_for_status() + result = response.json() + if prompt_id in result: + return result[prompt_id] + time.sleep(1) + raise RuntimeError(f'ComfyUI timeout ({prompt_id})') + + +def _illustration_prompt(day: dict, outfits: dict, focus_label: str) -> str: + """Descriptions en anglais pour les checkpoints d'illustration les plus courants.""" + high, low = day.get('temperature'), day.get('templow') + pop, wind = day.get('precipitation_probability'), day.get('wind_speed') + bits = [str(day.get('description') or 'daily weather')] + if high is not None: bits.append(f'maximum temperature {high} Celsius') + if low is not None: bits.append(f'minimum temperature {low} Celsius') + if pop is not None: bits.append(f'precipitation probability {pop}%') + if wind is not None: bits.append(f'wind speed {wind} km/h') + return ( + 'Professional clean flat 2D digital illustration, like a high quality family clothing lookbook, ' + 'square composition, TWO human characters EXACTLY, side by side, full body, comfortable spacing, ' + 'simple plain studio background, no text, no captions, no logos. ' + 'LEFT CHARACTER: one little GIRL, 8 YEARS OLD, unmistakably a primary school CHILD, ' + 'shorter and much smaller than the adult, innocent age-appropriate appearance, child body proportions, ' + 'simple pleasant face, standing naturally. ' + 'RIGHT CHARACTER: one ADULT MAN, AGE 42, unmistakably a mature masculine man, ' + 'adult height and mature adult male body proportions, short hair, subtly defined adult male jaw, ' + 'neatly trimmed beard optional, clearly NOT a teenager, NOT a woman. ' + 'Precisely two people: ONE small child girl and ONE adult man. ' + 'Symmetrical well-defined faces, two natural eyes per face, normal hands, five fingers per hand, ' + 'no distorted features, tidy linework, modest friendly cartoon styling instead of photorealism. ' + f'Weather context for {day.get("date", focus_label)}: ' + ', '.join(bits) + '. ' + 'Outfits must match the following French clothing specifications. ' + f'LEFT child GIRL (8): clothing: {", ".join(outfits["child"]["clothes"])}; ' + f'accessories: {", ".join(outfits["child"]["accessories"]) or "none"}. ' + f'RIGHT ADULT MAN (42): clothing: {", ".join(outfits["adult"]["clothes"])}; ' + f'accessories: {", ".join(outfits["adult"]["accessories"]) or "none"}. ' + 'Show clothing clearly, accurate head-to-toe outfits. Exactly one child and one adult male.' + ) + + +def _generate_illustration(day: dict, outfits: dict, focus_label: str, refresh: bool = False) -> dict | None: + if not ENABLED: + return None + sig = _weather_signature(day, outfits) + cache = _load_cache() + entry = cache.get(sig) + now_ts = time.time() + if entry and not refresh and (now_ts - float(entry.get('cached_at', 0))) < TTL_HOURS * 3600: + entry['from_cache'] = True + return entry + + prompt = _illustration_prompt(day, outfits, focus_label) + prompt_id = _submit_workflow(prompt) + history = _wait_history(prompt_id) + images = _extract_images(history) + if not images: + raise RuntimeError('ComfyUI a terminé sans image de sortie pour l’illustration météo.') + entry = { + 'url': images[0]['url'], + 'filename': images[0]['filename'], + 'prompt_id': prompt_id, + 'prompt': prompt, + 'cached_at': now_ts, + 'from_cache': False, + } + cache[sig] = entry + _save_cache(cache) + return entry + + +def build_payload(q: str = '', date: str = '', label: str = '', refresh: bool = False) -> dict: + data = ha_weather.get_weather(q) + if date: + try: + Date.fromisoformat(date) + except (ValueError, TypeError) as exc: + raise ValueError('Date invalide (AAAA-MM-JJ requis).') from exc + target = date or data.get('focus_date', '') + day = _pick_day(data, target) + focus_label = label or data.get('focus_label', 'aujourd’hui') + outfits = _recommend(day) + illustration = None + illustration_error = None + try: + illustration = _generate_illustration(day, outfits, focus_label, refresh=refresh) + except (OSError, ValueError, RuntimeError, TimeoutError, requests.RequestException) as exc: + # La partie texte doit rester affichable si ComfyUI est indisponible. + illustration_error = f'{type(exc).__name__} : {exc}' + print(f'[ALFRED METEO] Illustration indisponible : {illustration_error}', flush=True) + return { + 'type': 'weather_outfits', + 'focus_label': focus_label, + 'date': day.get('date'), + 'condition': day.get('condition'), + 'description': day.get('description'), + 'temperature': day.get('temperature'), + 'templow': day.get('templow'), + 'precipitation_probability': day.get('precipitation_probability'), + 'wind_speed': day.get('wind_speed'), + 'summary': outfits.get('summary'), + 'child': outfits['child'], + 'adult': outfits['adult'], + 'image': illustration, + 'illustration_error': illustration_error, + } diff --git a/alfred/backup_meteo_tenues_v5_20261026_000215/services/weather_outfits.py b/alfred/backup_meteo_tenues_v5_20261026_000215/services/weather_outfits.py new file mode 100644 index 0000000..d4f7e54 --- /dev/null +++ b/alfred/backup_meteo_tenues_v5_20261026_000215/services/weather_outfits.py @@ -0,0 +1,534 @@ +"""Outfit recommendations and ComfyUI illustration for Alfred weather view. + +V4 improvements: +- refresh really regenerates a new image (new random seeds, cache bypass); +- best-of generation with lightweight vision review; +- more reliable accessory rendering by limiting visible accessories in the image; +- still generates the child and adult separately, then assembles them in one image. +""" +from __future__ import annotations + +import base64 +import hashlib +import io +import json +import os +import threading +import time +import uuid +import random +from datetime import datetime, date as Date +from collections import Counter +from pathlib import Path +from urllib.parse import urlencode + +import requests +from dotenv import load_dotenv, dotenv_values + +# Charger l'environnement avant de calculer BASE et WORKFLOWS_DIR. +# Flask exécute son load_dotenv() après l'import de ce module. +load_dotenv() +load_dotenv('/opt/hermes/MCP_comfyui/.env', override=False) +_MCP_SETTINGS = dotenv_values('/opt/hermes/MCP_comfyui/.env') + +from . import ha_weather + +CACHE_DIR = Path(os.getenv('ALFRED_CACHE_DIR', str(Path(__file__).resolve().parents[1] / 'cache'))) +CACHE_DIR.mkdir(parents=True, exist_ok=True) +CACHE_FILE = CACHE_DIR / 'weather_outfit_cache.json' +BASE = (os.getenv('ALFRED_COMFYUI_URL') or _MCP_SETTINGS.get('COMFYUI_URL') or os.getenv('COMFYUI_URL') or 'http://192.168.1.13:8188').rstrip('/') +WORKFLOWS_DIR = Path(os.getenv('ALFRED_COMFYUI_WORKFLOWS_DIR') or os.getenv('WORKFLOWS_DIR') or '/opt/hermes/MCP_comfyui/workflows') +PROMPT_NODE = os.getenv('COMFY_PROMPT_NODE', '6') +TIMEOUT = int(os.getenv('COMFYUI_TIMEOUT', '300')) +TTL_HOURS = int(os.getenv('ALFRED_WEATHER_ILLUSTRATION_TTL_HOURS', '12')) +ENABLED = os.getenv('ALFRED_WEATHER_ILLUSTRATIONS', 'true').lower() in ('1', 'true', 'yes', 'on') +IMAGE_SIZE = min(1024, max(512, int(os.getenv('ALFRED_WEATHER_COMFY_SIZE', '768')))) +PROMPT_VERSION = 'outfits-v4-bestof-20261025' +MAX_ATTEMPTS_PER_CHARACTER = max(1, min(3, int(os.getenv('ALFRED_WEATHER_ILLUSTRATION_ATTEMPTS', '2')))) +QUALITY_TARGET = float(os.getenv('ALFRED_WEATHER_ILLUSTRATION_TARGET_SCORE', '8.6')) +REVIEW_ENABLED = os.getenv('ALFRED_WEATHER_REVIEW', 'true').lower() in ('1','true','yes','on') + +# Un seul job génération météo à la fois : évite les doublons concurrents. +ILLUSTRATION_LOCK = threading.RLock() +IMAGE_DIR = CACHE_DIR / 'weather_outfit_images' +IMAGE_DIR.mkdir(parents=True, exist_ok=True) + +# Deux portraits distincts, réunis dans une seule image finale. +PORTRAIT_WIDTH = min(512, IMAGE_SIZE) +PORTRAIT_HEIGHT = IMAGE_SIZE +DISPLAY_SIZE = int(os.getenv('ALFRED_WEATHER_DISPLAY_SIZE', '350')) + + +def _load_cache() -> dict: + try: + return json.loads(CACHE_FILE.read_text(encoding='utf-8')) + except Exception: + return {} + + +def _save_cache(cache: dict) -> None: + CACHE_FILE.write_text(json.dumps(cache, ensure_ascii=False, indent=2), encoding='utf-8') + + +def _pick_day(data: dict, forced_date: str | None = None) -> dict: + target = str(forced_date or data.get('focus_date') or '').strip() + try: + requested = Date.fromisoformat(target) + except ValueError as exc: + raise ValueError('Date météo invalide : format AAAA-MM-JJ attendu.') from exc + + for day in data.get('daily', []): + if str(day.get('date')) == target: + return day + + matching = [h for h in data.get('hourly', []) if h.get('date') == target] + if matching: + def available(key): + return [float(h[key]) for h in matching if h.get(key) is not None] + temps = available('temperature') + chances = available('precipitation_probability') + winds = available('wind_speed') + conditions = [str(h['condition']) for h in matching if h.get('condition')] + condition = Counter(conditions).most_common(1)[0][0] if conditions else 'unknown' + return { + 'date': target, + 'condition': condition, + 'description': ha_weather.CONDITIONS.get(condition, 'conditions non précisées'), + 'temperature': max(temps) if temps else None, + 'templow': min(temps) if temps else None, + 'precipitation_probability': max(chances) if chances else None, + 'wind_speed': max(winds) if winds else None, + } + + if requested == datetime.now(ha_weather.TZ).date(): + current = data.get('current', {}) + return { + 'date': target, + 'condition': current.get('condition', 'unknown'), + 'description': current.get('description', 'conditions non précisées'), + 'temperature': current.get('temperature'), + 'templow': current.get('temperature'), + 'precipitation_probability': None, + 'wind_speed': current.get('wind_speed'), + } + raise ValueError(f'Aucune prévision Home Assistant disponible pour le {requested:%d/%m/%Y}. Aucune image ne sera générée avec les données d’un autre jour.') + + +def _temp_band(high: float | None, low: float | None) -> str: + ref = high if high is not None else low + if ref is None: + return 'mild' + if ref < 6: + return 'cold' + if ref < 14: + return 'cool' + if ref < 23: + return 'mild' + return 'warm' + + +def _recommend(day: dict) -> dict: + high = day.get('temperature') + low = day.get('templow') + pop = day.get('precipitation_probability') + wind = day.get('wind_speed') + cond = str(day.get('condition') or '') + band = _temp_band(high, low) + rainy = cond in {'rainy', 'pouring', 'lightning-rainy', 'snowy-rainy'} or (pop is not None and pop >= 45) + sunny = cond in {'sunny', 'partlycloudy'} + windy = wind is not None and wind >= 25 + + child = [] + adult = [] + child_acc = [] + adult_acc = [] + + if band == 'cold': + child += ['doudoune chaude', 'pantalon chaud', 'chaussures fermées'] + adult += ['manteau chaud', 'pull', 'pantalon', 'chaussures fermées'] + child_acc += ['bonnet', 'écharpe'] + adult_acc += ['écharpe'] + elif band == 'cool': + child += ['veste légère', 'pantalon', 'baskets'] + adult += ['veste mi-saison', 'pull fin', 'jean', 'chaussures fermées'] + elif band == 'mild': + child += ['sweat ou gilet', 'pantalon léger', 'baskets'] + adult += ['veste légère', 'haut confortable', 'jean ou chino', 'baskets'] + else: + child += ['t-shirt', 'short ou pantalon léger', 'baskets légères'] + adult += ['t-shirt ou polo', 'tenue légère', 'chaussures légères'] + child_acc += ['casquette'] + adult_acc += ['lunettes de soleil'] + + if rainy: + child_acc += ['imperméable', 'parapluie'] + adult_acc += ['parapluie'] + if 'chaussures fermées' not in child: child.append('chaussures fermées') + if 'chaussures fermées' not in adult: adult.append('chaussures fermées') + if windy: + child_acc += ['coupe-vent'] + adult_acc += ['coupe-vent'] + if sunny and band != 'cold': + if 'casquette' not in child_acc: child_acc += ['casquette'] + if 'lunettes de soleil' not in adult_acc: adult_acc += ['lunettes de soleil'] + + def uniq(seq): + seen=[] + for x in seq: + if x not in seen: seen.append(x) + return seen + + return { + 'summary': day.get('description', 'Conditions du jour'), + 'child': { + 'title': 'Fille · 8 ans', + 'clothes': uniq(child), + 'accessories': uniq(child_acc), + }, + 'adult': { + 'title': 'Homme · 42 ans', + 'clothes': uniq(adult), + 'accessories': uniq(adult_acc), + } + } + + +def _weather_signature(day: dict, outfits: dict) -> str: + raw = json.dumps({'version': PROMPT_VERSION, 'date': day.get('date'), + 'day': day, 'outfits': outfits, 'size': IMAGE_SIZE}, + ensure_ascii=False, sort_keys=True) + return hashlib.sha256(raw.encode('utf-8')).hexdigest() + + +def _extract_images(history: dict) -> list[dict]: + images = [] + for node_id, output in history.get('outputs', {}).items(): + for image in output.get('images', []): + filename = image.get('filename') + if not filename: + continue + subfolder = image.get('subfolder', '') + image_type = image.get('type', 'output') + query = urlencode({'filename': filename, 'subfolder': subfolder, 'type': image_type}) + images.append({'filename': filename, 'url': f'{BASE}/view?{query}', 'node_id': str(node_id)}) + return images + + +NEGATIVE_PROMPT = ( + 'more than one person, group photo, two people, extra person, background crowd, ' + 'mirror reflection, human silhouette in background, duplicate heads, cloned body, ' + 'deformed face, malformed face, mutated hands, extra limbs, extra fingers, ' + 'disfigured features, asymmetrical face, photorealistic, horror, blurry, ' + 'text, captions, logos, watermark, oversized accessory, floating accessory' +) + + +def _random_seed() -> int: + return random.randint(1, 2**31 - 1) + + +def _prepare_workflow(workflow: dict, prompt: str, force_new_seed: bool = False) -> dict: + node = workflow.get(PROMPT_NODE) + if not isinstance(node, dict) or not isinstance(node.get('inputs'), dict) or 'text' not in node['inputs']: + raise ValueError(f'Nœud de prompt ComfyUI {PROMPT_NODE} introuvable ou incompatible.') + node['inputs']['text'] = prompt + + for item in workflow.values(): + inputs = item.get('inputs') or {} + if item.get('class_type') == 'CLIPTextEncode' and 'text' in inputs: + # negative prompt link target handled below when referenced by sampler. + pass + if force_new_seed: + for key in ('seed', 'noise_seed'): + if key in inputs and isinstance(inputs[key], (int, float)): + inputs[key] = _random_seed() + if item.get('class_type') in ('RandomNoise', 'Noise') and force_new_seed: + for key in ('noise_seed', 'seed'): + if key in inputs and isinstance(inputs[key], (int, float)): + inputs[key] = _random_seed() + + for sampler in workflow.values(): + if sampler.get('class_type') not in ('KSampler', 'KSamplerAdvanced'): + continue + inputs = sampler.get('inputs') or {} + if force_new_seed: + for key in ('seed', 'noise_seed'): + if key in inputs and isinstance(inputs[key], (int, float)): + inputs[key] = _random_seed() + negative_link = inputs.get('negative') + if not isinstance(negative_link, (list, tuple)) or not negative_link: + continue + neg = workflow.get(str(negative_link[0])) + if neg and neg.get('class_type') == 'CLIPTextEncode' and 'text' in neg.get('inputs', {}): + neg['inputs']['text'] = NEGATIVE_PROMPT + for latent in workflow.values(): + if latent.get('class_type') in ('EmptyLatentImage', 'EmptySD3LatentImage'): + inputs = latent.get('inputs') or {} + if 'width' in inputs and 'height' in inputs: + inputs['width'] = PORTRAIT_WIDTH + inputs['height'] = PORTRAIT_HEIGHT + return workflow + + +def _submit_workflow(prompt: str, force_new_seed: bool = False) -> str: + workflow_path = WORKFLOWS_DIR / 'text_to_image.json' + if not workflow_path.exists(): + raise RuntimeError(f'Workflow ComfyUI introuvable : {workflow_path}') + workflow = _prepare_workflow(json.loads(workflow_path.read_text(encoding='utf-8')), prompt, force_new_seed=force_new_seed) + response = requests.post(f'{BASE}/prompt', json={'prompt': workflow, 'client_id': str(uuid.uuid4())}, timeout=30) + response.raise_for_status() + return response.json()['prompt_id'] + + +def _wait_history(prompt_id: str) -> dict: + deadline = time.monotonic() + TIMEOUT + while time.monotonic() < deadline: + response = requests.get(f'{BASE}/history/{prompt_id}', timeout=15) + response.raise_for_status() + result = response.json() + if prompt_id in result: + return result[prompt_id] + time.sleep(1) + raise RuntimeError(f'ComfyUI timeout ({prompt_id})') + + +def _primary_accessories(kind: str, outfits: dict) -> list[str]: + accessories = list(outfits[kind]['accessories']) + ordered = [] + priorities = ['parapluie', 'imperméable', 'bonnet', 'écharpe', 'coupe-vent', 'casquette', 'lunettes de soleil'] + for wanted in priorities: + if wanted in accessories and wanted not in ordered: + ordered.append(wanted) + for acc in accessories: + if acc not in ordered: + ordered.append(acc) + return ordered[:2] + + +def _single_character_prompt(kind: str, day: dict, outfits: dict) -> str: + profile = outfits[kind] + if kind == 'child': + identity = ( + 'ONE young GIRL, eight years old, clearly a primary-school child, ' + 'small body proportions of a young child, friendly and natural face, ' + 'casual appropriate haircut, no grown-up features. ' + ) + else: + identity = ( + 'ONE adult MAN, forty-two years old, unmistakably masculine adult, ' + 'short neat hair, mature male face, optionally light stubble, ' + 'adult male shoulders and body proportions, not a teenager. ' + ) + weather = str(day.get('description') or 'daily weather') + primary_acc = _primary_accessories(kind, outfits) + image_acc = ', '.join(primary_acc) if primary_acc else 'none' + return ( + 'Single character fashion illustration for a family weather dashboard. ' + 'Exactly ONE character, standing ALONE. ' + + identity + + 'Full body visible from head to shoes, centrally positioned, front three-quarter view, ' + 'generous empty space around body, relaxed neutral standing pose, smiling gently. ' + 'Soft polished 2D digital illustration, smooth clean outlines, tasteful pastel colors, ' + 'slight dimensional shading, consistent editorial children-book illustration style, ' + 'simple light neutral plain studio background, no other people, no human reflections, ' + 'no poster, no photographs, no text. ' + f'Weather for {day.get("date", "selected day")}: {weather}. ' + f'Clothing: {", ".join(profile["clothes"])}. ' + f'Visible accessories to show clearly: {image_acc}. ' + 'Show at most two accessories, and only if they are natural to wear or hold. ' + 'Correct age and correct body proportions. Clothing must be clearly visible. ' + 'High priority: one person only, clean natural face, no deformations.' + ) + + +def _render_image_from_url(url: str): + from PIL import Image + response = requests.get(url, timeout=45) + response.raise_for_status() + if len(response.content) > 18_000_000: + raise RuntimeError('Image ComfyUI trop volumineuse (limite 18 Mo).') + with Image.open(io.BytesIO(response.content)) as raw: + raw.load() + return raw.convert('RGB') + + +def _vision_review(kind: str, prompt_summary: str, image_url: str) -> dict: + """Lightweight review using the existing ComfyUI vision workflow when available.""" + default = {'score': 7.0, 'ok': True, 'issues': []} + if not REVIEW_ENABLED: + return default + workflow_path = WORKFLOWS_DIR / 'vision_analyze.json' + if not workflow_path.exists(): + return default + image_node = os.getenv('COMFY_VISION_IMAGE_NODE', '1') + prompt_node = os.getenv('COMFY_VISION_PROMPT_NODE', '2') + try: + workflow = json.loads(workflow_path.read_text(encoding='utf-8')) + workflow[str(image_node)]['inputs']['image'] = image_url + review_prompt = ( + 'Review a fashion illustration candidate for a weather dashboard. ' + f'Expected subject: {prompt_summary}. ' + 'Answer ONLY with compact JSON object with keys: ' + 'people_count (int), gender_age_ok (bool), face_ok (bool), accessories_ok (bool), ' + 'deformation (bool), score (number 0-10), issues (array of strings).' + ) + workflow[str(prompt_node)]['inputs']['text'] = review_prompt + response = requests.post(f'{BASE}/prompt', json={'prompt': workflow, 'client_id': str(uuid.uuid4())}, timeout=30) + response.raise_for_status() + prompt_id = response.json()['prompt_id'] + history = _wait_history(prompt_id) + raw_text = None + for _, output in history.get('outputs', {}).items(): + for key in ('text', 'result', 'string'): + if key in output: + raw_text = output[key] + break + if 'texts' in output and output['texts']: + raw_text = output['texts'][0] + break + if isinstance(raw_text, list): + raw_text = raw_text[0] if raw_text else '' + if isinstance(raw_text, dict): + parsed = raw_text + else: + text = str(raw_text or '').strip() + start = text.find('{') + end = text.rfind('}') + if start >= 0 and end > start: + text = text[start:end+1] + parsed = json.loads(text) + score = float(parsed.get('score', 0) or 0) + people_count = int(parsed.get('people_count', 1) or 1) + gender_age_ok = bool(parsed.get('gender_age_ok', True)) + face_ok = bool(parsed.get('face_ok', True)) + accessories_ok = bool(parsed.get('accessories_ok', True)) + deformation = bool(parsed.get('deformation', False)) + issues = list(parsed.get('issues', [])) + ok = (people_count == 1 and gender_age_ok and face_ok and not deformation) + return {'score': score, 'ok': ok, 'issues': issues, 'people_count': people_count, + 'gender_age_ok': gender_age_ok, 'face_ok': face_ok, 'accessories_ok': accessories_ok, + 'deformation': deformation} + except Exception as exc: + return {'score': 7.0, 'ok': True, 'issues': [f'review_fallback:{exc}']} + + +def _render_best_one(kind: str, day: dict, outfits: dict, refresh: bool = False): + prompt = _single_character_prompt(kind, day, outfits) + summary = f'{kind} with clothes {", ".join(outfits[kind]["clothes"])} and accessories {", ".join(_primary_accessories(kind, outfits)) or "none"}' + best = None + best_review = None + for attempt in range(1, MAX_ATTEMPTS_PER_CHARACTER + 1): + prompt_id = _submit_workflow(prompt, force_new_seed=True if (refresh or attempt > 1) else True) + result = _wait_history(prompt_id) + images = _extract_images(result) + if not images: + raise RuntimeError(f'Aucune illustration ComfyUI générée pour {kind}.') + url = images[0]['url'] + image = _render_image_from_url(url) + review = _vision_review(kind, summary, url) + candidate = {'image': image, 'prompt_id': prompt_id, 'review': review} + if best is None or review.get('score', 0) > best_review.get('score', 0): + best = candidate + best_review = review + if review.get('ok') and review.get('score', 0) >= QUALITY_TARGET: + break + return best['image'], best['prompt_id'], best_review + + +def _compose_duo(child_img, adult_img, dest: Path): + from PIL import Image, ImageOps, ImageDraw + w = DISPLAY_SIZE * 2 + 12 + h = DISPLAY_SIZE + gap = 12 + tile_w = DISPLAY_SIZE + tile_h = DISPLAY_SIZE + canvas = Image.new('RGB', (w, h), (42, 50, 56)) + left = ImageOps.fit(child_img, (tile_w, tile_h), method=Image.Resampling.LANCZOS, centering=(.5, .5)) + right = ImageOps.fit(adult_img, (tile_w, tile_h), method=Image.Resampling.LANCZOS, centering=(.5, .5)) + canvas.paste(left, (0, 0)) + canvas.paste(right, (tile_w + gap, 0)) + draw = ImageDraw.Draw(canvas) + draw.rounded_rectangle((tile_w, 0, tile_w + gap - 1, h - 1), radius=2, fill=(42, 50, 56)) + dest.parent.mkdir(parents=True, exist_ok=True) + temp = dest.with_suffix('.tmp.jpg') + canvas.save(temp, format='JPEG', quality=90, optimize=True) + temp.replace(dest) + + +def _cache_url(file_path: Path) -> str: + return 'data:image/jpeg;base64,' + base64.b64encode(file_path.read_bytes()).decode('ascii') + + +def _generate_illustration(day: dict, outfits: dict, focus_label: str, refresh: bool = False) -> dict | None: + if not ENABLED: + return None + sig = _weather_signature(day, outfits) + image_path = IMAGE_DIR / (sig + '.jpg') + with ILLUSTRATION_LOCK: + cache = _load_cache() + entry = cache.get(sig) + now_ts = time.time() + if (entry and image_path.is_file() and not refresh + and (now_ts - float(entry.get('cached_at', 0))) < TTL_HOURS * 3600): + return { + 'url': _cache_url(image_path), + 'cached_at': entry.get('cached_at'), + 'prompt_ids': entry.get('prompt_ids', []), + 'reviews': entry.get('reviews', {}), + 'from_cache': True, + } + child, child_id, child_review = _render_best_one('child', day, outfits, refresh=refresh) + adult, adult_id, adult_review = _render_best_one('adult', day, outfits, refresh=refresh) + _compose_duo(child, adult, image_path) + entry = { + 'file': image_path.name, + 'prompt_ids': [child_id, adult_id], + 'reviews': {'child': child_review, 'adult': adult_review}, + 'cached_at': now_ts, + 'focus_label': focus_label, + } + cache[sig] = entry + _save_cache(cache) + return { + 'url': _cache_url(image_path), + 'prompt_ids': entry['prompt_ids'], + 'reviews': entry['reviews'], + 'cached_at': now_ts, + 'from_cache': False, + } + + +def build_payload(q: str = '', date: str = '', label: str = '', refresh: bool = False) -> dict: + data = ha_weather.get_weather(q) + if date: + try: + Date.fromisoformat(date) + except (ValueError, TypeError) as exc: + raise ValueError('Date invalide (AAAA-MM-JJ requis).') from exc + target = date or data.get('focus_date', '') + day = _pick_day(data, target) + focus_label = label or data.get('focus_label', 'aujourd’hui') + outfits = _recommend(day) + illustration = None + illustration_error = None + try: + illustration = _generate_illustration(day, outfits, focus_label, refresh=refresh) + except (OSError, ValueError, RuntimeError, TimeoutError, ImportError, requests.RequestException) as exc: + illustration_error = f'{type(exc).__name__} : {exc}' + print(f'[ALFRED METEO] Illustration indisponible : {illustration_error}', flush=True) + return { + 'type': 'weather_outfits', + 'focus_label': focus_label, + 'date': day.get('date'), + 'condition': day.get('condition'), + 'description': day.get('description'), + 'temperature': day.get('temperature'), + 'templow': day.get('templow'), + 'precipitation_probability': day.get('precipitation_probability'), + 'wind_speed': day.get('wind_speed'), + 'summary': outfits.get('summary'), + 'child': outfits['child'], + 'adult': outfits['adult'], + 'image': illustration, + 'illustration_error': illustration_error, + } diff --git a/alfred/requirements_calendar.txt b/alfred/requirements_calendar.txt new file mode 100644 index 0000000..315ee33 --- /dev/null +++ b/alfred/requirements_calendar.txt @@ -0,0 +1,2 @@ +requests>=2.31 +websocket-client>=1.6 diff --git a/alfred/tests/test_calendar.py b/alfred/tests/test_calendar.py new file mode 100644 index 0000000..b67c44b --- /dev/null +++ b/alfred/tests/test_calendar.py @@ -0,0 +1,88 @@ +"""Run: PYTHONPATH=app pytest -q tests/test_calendar.py""" +from datetime import date, datetime, timedelta +from zoneinfo import ZoneInfo +import pytest +from services import ha_views, alfred_intents as intents + +PARIS=ZoneInfo('Europe/Paris') +NOW=datetime(2026,10,25,18,0,tzinfo=PARIS) + +@pytest.mark.parametrize('utterance,start,end',[ + ('J’ai quelque chose de prévu demain ?',date(2026,10,26),date(2026,10,27)), + ('Montre mon planning sur 3 jours',date(2026,10,25),date(2026,10,28)), + ('Que j’ai le 8 novembre ?',date(2026,11,8),date(2026,11,9)), + ('Je veux mon agenda du mois',date(2026,10,1),date(2026,11,1)), + ('Qu’est-ce que j’ai ce matin ?',date(2026,10,25),date(2026,10,25)), +]) +def test_voice_period(utterance,start,end): + a,b,view=intents.period(utterance,NOW) + assert a.date()==start + if 'matin' not in utterance:assert b.date()==end + else:assert (a.hour,b.hour)==(6,12) + +@pytest.mark.parametrize('utterance,kind',[ + ('Montre mon planning sur 7 jours','calendar'), + ('J’ai des choses de prévu le 8 novembre ?','calendar'), + ('Ajoute un rendez-vous dentiste demain à 15h','create'), + ('Supprime mon rendez-vous demain','delete'), + ('Affiche les caméras extérieures','cameras'), + ('Montre les températures','temperatures'), + ('Reviens à l’accueil','home'), + ('Génère-moi une image de poulet',None), +]) +def test_intent(utterance,kind): + result=intents.classify(utterance) + assert (result or {}).get('kind')==kind + + +def test_event_adapter(monkeypatch): + def fake_request(method,path,**kwargs): + if path=='/api/calendars':return [{'entity_id':'calendar.family','name':'Famille'}] + if path=='/api/states':return [{'entity_id':'calendar.family','attributes':{'supported_features':3}}] + if path.startswith('/api/calendars/'): + return [{'summary':'Dentiste','uid':'abc123','start':{'dateTime':'2026-10-25T10:00:00+01:00'}, + 'end':{'dateTime':'2026-10-25T11:00:00+01:00'}}] + monkeypatch.setattr(ha_views,'_request',fake_request) + data=ha_views.get_events(datetime(2026,10,25,tzinfo=PARIS),datetime(2026,10,26,tzinfo=PARIS)) + assert data['events'][0]['title']=='Dentiste' + assert data['events'][0]['id']=='abc123' + assert data['events'][0]['can_delete'] is True + + +def test_recurring_no_uid_cannot_delete(monkeypatch): + def fake_request(method,path,**kwargs): + if path=='/api/calendars':return [{'entity_id':'calendar.a','name':'A'}] + if path=='/api/states':return [{'entity_id':'calendar.a','attributes':{'supported_features':0}}] + if path.startswith('/api/calendars/'): + return [{'summary':'Test','start':'2026-10-26','end':'2026-10-27'}] + monkeypatch.setattr(ha_views,'_request',fake_request) + data=ha_views.get_events(datetime(2026,10,25,tzinfo=PARIS),datetime(2026,10,27,tzinfo=PARIS)) + assert data['events'][0]['all_day'] is True + assert data['events'][0]['can_delete'] is False + + +def test_create_event_calls_service(monkeypatch): + calls=[] + monkeypatch.setattr(ha_views,'list_calendars',lambda:[{'entity_id':'calendar.family','can_create':True,'can_delete':True}]) + monkeypatch.setattr(ha_views,'_request',lambda method,path,**kwargs:calls.append((method,path,kwargs)) or None) + start=datetime(2026,11,8,15,0,tzinfo=PARIS) + assert ha_views.create_event('calendar.family','Dentiste',start,start+timedelta(hours=1))['ok'] + assert calls[0][1]=='/api/services/calendar/create_event' + assert calls[0][2]['payload']['entity_id']=='calendar.family' + + +def test_delete_uses_websocket(monkeypatch): + import websocket + monkeypatch.setattr(ha_views,'list_calendars',lambda:[{'entity_id':'calendar.family','can_create':True,'can_delete':True}]) + monkeypatch.setattr(ha_views,'_cfg',lambda:('http://ha.example:8123','test-token')) + class FakeWS: + messages=[] + def __init__(self):self.replies=iter(['{"type":"auth_required"}','{"type":"auth_ok"}','{"id":1,"success":true}']) + def recv(self):return next(self.replies) + def send(self,m):self.messages.append(m) + def close(self):pass + ws=FakeWS() + monkeypatch.setattr(websocket,'create_connection',lambda url,timeout:ws) + ha_views.delete_event('calendar.family','event1') + assert 'calendar/event/delete' in ws.messages[1] + assert 'event1' in ws.messages[1] diff --git a/hermes/MCP_proxmox/.env b/hermes/MCP_proxmox/.env new file mode 100644 index 0000000..3233595 --- /dev/null +++ b/hermes/MCP_proxmox/.env @@ -0,0 +1,11 @@ +PROXMOX_HOST=192.168.1.20 +PROXMOX_USER=root@pam +PROXMOX_TOKEN_NAME=hermes +PROXMOX_TOKEN_VALUE=91ac0c88-c744-4b97-8a7d-a5e05c783354 +PROXMOX_VERIFY_SSL=false + +# Sécurité +ALLOW_POWER_ACTIONS=true +ALLOW_DELETE=false +ALLOW_ROLLBACK=false + diff --git a/hermes/MCP_proxmox/proxmox_mcp.py b/hermes/MCP_proxmox/proxmox_mcp.py new file mode 100755 index 0000000..98583fb --- /dev/null +++ b/hermes/MCP_proxmox/proxmox_mcp.py @@ -0,0 +1,353 @@ +#!/usr/bin/env python3 + +import os +from typing import Optional + +from dotenv import load_dotenv +from proxmoxer import ProxmoxAPI +from mcp.server.fastmcp import FastMCP + +load_dotenv("/opt/hermes/proxmox-mcp/.env") + +mcp = FastMCP("proxmox-mcp") + +PROXMOX_HOST = os.getenv("PROXMOX_HOST") +PROXMOX_USER = os.getenv("PROXMOX_USER") +PROXMOX_TOKEN_NAME = os.getenv("PROXMOX_TOKEN_NAME") +PROXMOX_TOKEN_VALUE = os.getenv("PROXMOX_TOKEN_VALUE") + +VERIFY_SSL = ( + os.getenv("PROXMOX_VERIFY_SSL", "true").lower() == "true" +) + +ALLOW_POWER_ACTIONS = ( + os.getenv("ALLOW_POWER_ACTIONS", "false").lower() == "true" +) + +ALLOW_DELETE = ( + os.getenv("ALLOW_DELETE", "false").lower() == "true" +) + +ALLOW_ROLLBACK = ( + os.getenv("ALLOW_ROLLBACK", "false").lower() == "true" +) + +def get_proxmox(): + return ProxmoxAPI( + PROXMOX_HOST, + user=PROXMOX_USER, + token_name=PROXMOX_TOKEN_NAME, + token_value=PROXMOX_TOKEN_VALUE, + verify_ssl=VERIFY_SSL, + backend="https", + ) +def power_allowed(): + if not ALLOW_POWER_ACTIONS: + raise PermissionError( + "Les actions d'alimentation sont désactivées." + ) + + +@mcp.tool() +def list_nodes(): + """ + Liste tous les nodes du cluster. + """ + + proxmox = get_proxmox() + + return proxmox.nodes.get() + + +@mcp.tool() +def cluster_status(): + """ + Retourne l'état général du cluster. + """ + + proxmox = get_proxmox() + + return proxmox.cluster.status.get() + + +@mcp.tool() +def list_vms(node: Optional[str] = None): + """ + Liste les VMs QEMU. + """ + + proxmox = get_proxmox() + + result = [] + + if node: + nodes = [node] + else: + nodes = [ + n["node"] + for n in proxmox.nodes.get() + ] + + for current_node in nodes: + + try: + vms = proxmox.nodes(current_node).qemu.get() + + for vm in vms: + + result.append({ + "node": current_node, + "vmid": vm.get("vmid"), + "name": vm.get("name"), + "status": vm.get("status"), + "cpus": vm.get("cpus"), + "mem": vm.get("mem"), + "maxmem": vm.get("maxmem"), + "uptime": vm.get("uptime") + }) + + except Exception as e: + + result.append({ + "node": current_node, + "error": str(e) + }) + + return result + + +@mcp.tool() +def list_lxc(node: Optional[str] = None): + """ + Liste les conteneurs LXC. + """ + + proxmox = get_proxmox() + + result = [] + + if node: + nodes = [node] + else: + nodes = [ + n["node"] + for n in proxmox.nodes.get() + ] + + for current_node in nodes: + + try: + lxcs = proxmox.nodes(current_node).lxc.get() + + for ct in lxcs: + + result.append({ + "node": current_node, + "vmid": ct.get("vmid"), + "name": ct.get("name"), + "status": ct.get("status"), + "cpus": ct.get("cpus"), + "mem": ct.get("mem"), + "maxmem": ct.get("maxmem"), + }) + + except Exception as e: + + result.append({ + "node": current_node, + "error": str(e) + }) + + return result + + +@mcp.tool() +def vm_status(node: str, vmid: int): + """ + Statut détaillé d'une VM. + """ + + proxmox = get_proxmox() + + return proxmox.nodes(node).qemu(vmid).status.current.get() + + +@mcp.tool() +def lxc_status(node: str, vmid: int): + """ + Statut détaillé d'un LXC. + """ + + proxmox = get_proxmox() + + return proxmox.nodes(node).lxc(vmid).status.current.get() + + +@mcp.tool() +def node_resources(node: str): + """ + CPU / RAM / stockage du node. + """ + + proxmox = get_proxmox() + + return proxmox.nodes(node).status.get() + + +@mcp.tool() +def start_vm(node: str, vmid: int): + + power_allowed() + + proxmox = get_proxmox() + + task = ( + proxmox.nodes(node) + .qemu(vmid) + .status.start.post() + ) + + return { + "action": "start", + "vmid": vmid, + "task": task + } + + +@mcp.tool() +def stop_vm(node: str, vmid: int): + + power_allowed() + + proxmox = get_proxmox() + + task = ( + proxmox.nodes(node) + .qemu(vmid) + .status.stop.post() + ) + + return { + "action": "stop", + "vmid": vmid, + "task": task + } + + +@mcp.tool() +def shutdown_vm(node: str, vmid: int): + + power_allowed() + + proxmox = get_proxmox() + + task = ( + proxmox.nodes(node) + .qemu(vmid) + .status.shutdown.post() + ) + + return { + "action": "shutdown", + "vmid": vmid, + "task": task + } + + +@mcp.tool() +def reboot_vm(node: str, vmid: int): + + power_allowed() + + proxmox = get_proxmox() + + task = ( + proxmox.nodes(node) + .qemu(vmid) + .status.reboot.post() + ) + + return { + "action": "reboot", + "vmid": vmid, + "task": task + } + + +@mcp.tool() +def create_snapshot( + node: str, + vmid: int, + snapshot_name: str, + description: str = "" +): + """ + Création snapshot VM. + """ + + proxmox = get_proxmox() + + task = ( + proxmox.nodes(node) + .qemu(vmid) + .snapshot.post( + snapname=snapshot_name, + description=description + ) + ) + + return { + "action": "snapshot", + "vmid": vmid, + "snapshot": snapshot_name, + "task": task + } + + +@mcp.tool() +def list_snapshots(node: str, vmid: int): + + proxmox = get_proxmox() + + return ( + proxmox.nodes(node) + .qemu(vmid) + .snapshot.get() + ) + + +@mcp.tool() +def rollback_snapshot( + node: str, + vmid: int, + snapshot_name: str +): + """ + Rollback snapshot. + Désactivé par défaut. + """ + + if not ALLOW_ROLLBACK: + raise PermissionError( + "Rollback désactivé." + ) + + proxmox = get_proxmox() + + task = ( + proxmox.nodes(node) + .qemu(vmid) + .snapshot(snapshot_name) + .rollback.post() + ) + + return { + "action": "rollback", + "vmid": vmid, + "snapshot": snapshot_name, + "task": task + } + + +if __name__ == "__main__": + mcp.run() diff --git a/hermes/MCP_proxmox/test_api.py b/hermes/MCP_proxmox/test_api.py new file mode 100644 index 0000000..7e8f97c --- /dev/null +++ b/hermes/MCP_proxmox/test_api.py @@ -0,0 +1,16 @@ +from dotenv import load_dotenv +from proxmoxer import ProxmoxAPI +import os + +load_dotenv("/opt/mcp-proxmox/.env") + +p = ProxmoxAPI( + os.getenv("PROXMOX_HOST"), + user=os.getenv("PROXMOX_USER"), + token_name=os.getenv("PROXMOX_TOKEN_NAME"), + token_value=os.getenv("PROXMOX_TOKEN_VALUE"), + verify_ssl=False, + backend="https", +) + +print(p.nodes.get()) diff --git a/hermes/MCP_wazuh/.env b/hermes/MCP_wazuh/.env new file mode 100755 index 0000000..b59cc85 --- /dev/null +++ b/hermes/MCP_wazuh/.env @@ -0,0 +1,6 @@ +WAZUH_URL=https://192.168.1.52:55000 +WAZUH_USER=wazuh-wui +WAZUH_PASSWORD=y+tx9RA3cYiBxqpnoE2+138pV08Jhnwa +INDEXER_URL=https://192.168.1.52:9200 +INDEXER_USER=kibanaserver +INDEXER_PASSWORD=cIh?n7Y1FeACDZRb063r0+REWoI3dQAA diff --git a/hermes/MCP_wazuh/requirements.txt b/hermes/MCP_wazuh/requirements.txt new file mode 100644 index 0000000..21b3426 --- /dev/null +++ b/hermes/MCP_wazuh/requirements.txt @@ -0,0 +1,3 @@ +mcp +httpx +python-dotenv diff --git a/hermes/MCP_wazuh/vikunja_mcp.py.bak b/hermes/MCP_wazuh/vikunja_mcp.py.bak new file mode 100644 index 0000000..d4b671c --- /dev/null +++ b/hermes/MCP_wazuh/vikunja_mcp.py.bak @@ -0,0 +1,327 @@ +#!/usr/bin/env python3 + +import os +from pathlib import Path + +import httpx +from dotenv import load_dotenv +from mcp.server.fastmcp import FastMCP + + +# ============================================================================= +# Configuration +# ============================================================================= + +BASE_DIR = Path(__file__).resolve().parent + +load_dotenv(BASE_DIR / ".env") + +VIKUNJA_URL = os.getenv("VIKUNJA_URL", "").rstrip("/") +VIKUNJA_TOKEN = os.getenv("VIKUNJA_TOKEN", "") +VIKUNJA_VERIFY_SSL = ( + os.getenv("VIKUNJA_VERIFY_SSL", "true").lower() + in ("true", "1", "yes") +) + +if not VIKUNJA_URL: + raise RuntimeError("VIKUNJA_URL n'est pas défini dans .env") + +if not VIKUNJA_TOKEN: + raise RuntimeError("VIKUNJA_TOKEN n'est pas défini dans .env") + + +API_URL = f"{VIKUNJA_URL}/api/v2" + +HEADERS = { + "Authorization": f"Bearer {VIKUNJA_TOKEN}", + "Accept": "application/json", +} + + +# ============================================================================= +# MCP +# ============================================================================= + +mcp = FastMCP("vikunja") + + +# ============================================================================= +# Client Vikunja +# ============================================================================= + +async def vikunja_request( + method: str, + endpoint: str, + params: dict | None = None, + json: dict | None = None, +): + """ + Effectue une requête vers l'API v2 de Vikunja. + """ + + url = f"{API_URL}{endpoint}" + + async with httpx.AsyncClient( + headers=HEADERS, + verify=VIKUNJA_VERIFY_SSL, + timeout=30.0, + ) as client: + + response = await client.request( + method, + url, + params=params, + json=json, + ) + + response.raise_for_status() + + if not response.content: + return None + + return response.json() + + +async def vikunja_get( + endpoint: str, + params: dict | None = None, +): + return await vikunja_request( + "GET", + endpoint, + params=params, + ) + + +async def vikunja_post( + endpoint: str, + data: dict, +): + return await vikunja_request( + "POST", + endpoint, + json=data, + ) + + +# ============================================================================= +# MCP Tools +# ============================================================================= + +@mcp.tool() +async def vikunja_get_projects() -> dict: + """ + Retourne les projets Vikunja disponibles pour l'utilisateur. + + À utiliser notamment avant de créer une tâche lorsque l'utilisateur + ne donne pas directement l'identifiant du projet. + """ + + projects = await vikunja_get( + "/projects", + params={ + "format": "markdown", + }, + ) + + result = [] + + for project in projects or []: + result.append( + { + "id": project.get("id"), + "title": project.get("title"), + "description": project.get("description"), + "parent_project_id": project.get("parent_project_id"), + "is_archived": project.get("is_archived"), + "created": project.get("created"), + "updated": project.get("updated"), + } + ) + + return { + "count": len(result), + "projects": result, + } + + +@mcp.tool() +async def vikunja_get_project( + project_id: int, +) -> dict: + """ + Retourne les informations détaillées d'un projet Vikunja. + + Args: + project_id: + ID du projet. + """ + + project = await vikunja_get( + f"/projects/{project_id}", + params={ + "format": "markdown", + }, + ) + + return { + "id": project.get("id"), + "title": project.get("title"), + "description": project.get("description"), + "parent_project_id": project.get("parent_project_id"), + "is_archived": project.get("is_archived"), + "created": project.get("created"), + "updated": project.get("updated"), + } + + +@mcp.tool() +async def vikunja_get_tasks( + project_id: int, +) -> dict: + """ + Retourne les tâches d'un projet Vikunja. + + Args: + project_id: + ID du projet Vikunja. + """ + + tasks = await vikunja_get( + f"/projects/{project_id}/tasks", + params={ + "format": "markdown", + }, + ) + + result = [] + + for task in tasks or []: + result.append( + { + "id": task.get("id"), + "title": task.get("title"), + "description": task.get("description"), + "done": task.get("done"), + "priority": task.get("priority"), + "due_date": task.get("due_date"), + "start_date": task.get("start_date"), + "end_date": task.get("end_date"), + "project_id": task.get("project_id"), + "labels": task.get("labels", []), + "created": task.get("created"), + "updated": task.get("updated"), + } + ) + + return { + "project_id": project_id, + "count": len(result), + "tasks": result, + } + + +@mcp.tool() +async def vikunja_get_task( + task_id: int, +) -> dict: + """ + Retourne le détail d'une tâche Vikunja. + + Args: + task_id: + ID de la tâche. + """ + + task = await vikunja_get( + f"/tasks/{task_id}", + params={ + "format": "markdown", + }, + ) + + return { + "id": task.get("id"), + "title": task.get("title"), + "description": task.get("description"), + "done": task.get("done"), + "priority": task.get("priority"), + "due_date": task.get("due_date"), + "start_date": task.get("start_date"), + "end_date": task.get("end_date"), + "project_id": task.get("project_id"), + "labels": task.get("labels", []), + "assignees": task.get("assignees", []), + "created": task.get("created"), + "updated": task.get("updated"), + } + + +@mcp.tool() +async def vikunja_create_task( + project_id: int, + title: str, + description: str = "", + due_date: str | None = None, + priority: int = 0, +) -> dict: + """ + Crée une nouvelle tâche dans Vikunja. + + Args: + project_id: + ID du projet dans lequel créer la tâche. + + title: + Titre de la tâche. + + description: + Description facultative. + + due_date: + Date d'échéance facultative au format ISO 8601. + Exemple : 2026-09-15T18:00:00+02:00 + + priority: + Priorité Vikunja. + 0 signifie aucune priorité. + """ + + data = { + "title": title, + "description": description, + "priority": priority, + } + + if due_date: + data["due_date"] = due_date + + task = await vikunja_request( + "POST", + f"/projects/{project_id}/tasks", + params={ + "format": "markdown", + }, + json=data, + ) + + return { + "success": True, + "task": { + "id": task.get("id"), + "title": task.get("title"), + "description": task.get("description"), + "project_id": task.get("project_id"), + "priority": task.get("priority"), + "due_date": task.get("due_date"), + "done": task.get("done"), + }, + } + + +# ============================================================================= +# Main +# ============================================================================= + +if __name__ == "__main__": + mcp.run()