From a6169bcfdb2860357faf7b6bba26bb715436ca48 Mon Sep 17 00:00:00 2001 From: pporcheret Date: Wed, 28 Oct 2026 15:38:38 +0100 Subject: [PATCH] v2.1 - Small improvement --- .../services/admin_console.py | 431 ++++++++++++++++++ .../static/js/alfred-admin.js | 183 ++++++++ .../templates/config.html | 55 +++ .../templates/surveillance.html | 19 + alfred/app/cache/weather_outfit_cache.json | 116 +++++ ...181d50d6cf911bfb587c2b8b0036c2ecb619a1.jpg | Bin 0 -> 25692 bytes ...a6625d32d9fe3b471d7a101a62c4a790a3926e.jpg | Bin 0 -> 25535 bytes ...2d02f38fe5cdc2c8b94ee40851fe702d6e8e85.jpg | Bin 0 -> 27120 bytes ...d2d7e9f173b55663a3d155002468deee01a3b4.jpg | Bin 0 -> 25497 bytes alfred/app/data/alfred_features.sqlite3 | Bin 32768 -> 32768 bytes alfred/app/data/alfred_features.sqlite3-shm | Bin 32768 -> 32768 bytes alfred/app/data/alfred_features.sqlite3-wal | Bin 24752 -> 24752 bytes alfred/app/data/timers.json | 10 + .../__pycache__/admin_console.cpython-313.pyc | Bin 30821 -> 33503 bytes .../admin_extensions_v2.cpython-313.pyc | Bin 0 -> 11150 bytes alfred/app/services/admin_console.py | 55 ++- alfred/app/services/admin_extensions_v2.py | 154 +++++++ alfred/app/static/css/alfred-admin-v2.css | 17 + alfred/app/static/js/alfred-admin-v2.js | 49 ++ alfred/app/static/js/alfred-admin.js | 6 +- alfred/app/templates/config.html | 20 + alfred/app/templates/surveillance.html | 27 +- 22 files changed, 1138 insertions(+), 4 deletions(-) create mode 100644 alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/services/admin_console.py create mode 100644 alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/static/js/alfred-admin.js create mode 100644 alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/templates/config.html create mode 100644 alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/templates/surveillance.html create mode 100644 alfred/app/cache/weather_outfit_images/9384429b2e4292835018049e1c181d50d6cf911bfb587c2b8b0036c2ecb619a1.jpg create mode 100644 alfred/app/cache/weather_outfit_images/a0e631c42d8a4fc9dc3abad674a6625d32d9fe3b471d7a101a62c4a790a3926e.jpg create mode 100644 alfred/app/cache/weather_outfit_images/bcd2bb01a6eee2d756888ce1272d02f38fe5cdc2c8b94ee40851fe702d6e8e85.jpg create mode 100644 alfred/app/cache/weather_outfit_images/dd262938ddb64632659bedd83dd2d7e9f173b55663a3d155002468deee01a3b4.jpg create mode 100644 alfred/app/services/__pycache__/admin_extensions_v2.cpython-313.pyc create mode 100644 alfred/app/services/admin_extensions_v2.py create mode 100644 alfred/app/static/css/alfred-admin-v2.css create mode 100644 alfred/app/static/js/alfred-admin-v2.js diff --git a/alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/services/admin_console.py b/alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/services/admin_console.py new file mode 100644 index 0000000..eae961f --- /dev/null +++ b/alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/services/admin_console.py @@ -0,0 +1,431 @@ +"""Authenticated local configuration and health UI for Alfred. + +Registration is additive and doesn't replace the existing audio, camera, HA, +weather or Hermes endpoints. Passwords are provisioned locally via CLI. +""" +from __future__ import annotations + +import functools +import hmac +import json +import os +import re +import secrets +import shutil +import socket +import subprocess +import threading +import time +from datetime import datetime, timedelta, timezone +from pathlib import Path +from urllib.parse import urlsplit + +import requests +from dotenv import dotenv_values +from flask import Blueprint, current_app, jsonify, render_template, request, session +from werkzeug.security import check_password_hash + +ROOT = Path(__file__).resolve().parents[1] +AUTH_FILE = ROOT / 'config' / 'admin_auth.json' +GENERAL_ENV = ROOT / 'config' / 'control_center.env' +WEATHER_ENV = ROOT / 'config' / 'weather_outfits.env' +CONFIG_LOCK = threading.RLock() +LOGIN_LOCK = threading.Lock() +LOGIN_ATTEMPTS = {} + +# Only these keys are writable; no token, command, system file or shell input. +FIELDS = { + 'ALFRED_NAME': ('general', 'text'), + 'WAKE_WORD': ('general', 'wake'), + 'PIPER_VOICE': ('general', 'voice'), + 'ALFRED_OLLAMA_URL': ('general', 'url'), + 'ALFRED_COMFYUI_URL': ('weather', 'url'), + 'HA_WEATHER_ENTITY': ('general', 'entity'), + 'ALFRED_WEATHER_ILLUSTRATION_ATTEMPTS': ('weather', 'attempts'), + 'ALFRED_WEATHER_ILLUSTRATION_TARGET_SCORE': ('weather', 'score'), + 'ALFRED_WEATHER_DISPLAY_SIZE': ('weather', 'size'), + 'ALFRED_WEATHER_REVIEW': ('weather', 'boolean'), + 'ALFRED_WEATHER_REFERENCE_MODE': ('weather', 'reference_mode'), + 'ALFRED_WEATHER_REFERENCE_DENOISE': ('weather', 'denoise'), + 'ALFRED_WEATHER_PRESERVE_FACE': ('weather', 'boolean'), + 'ALFRED_WEATHER_ACCESSORY_LIMIT': ('weather', 'accessory'), +} +DEFAULTS = { + 'ALFRED_NAME': 'Alfred', 'WAKE_WORD': 'alfred', + 'PIPER_VOICE': 'fr_FR-tom-medium', 'ALFRED_OLLAMA_URL': '', + 'ALFRED_COMFYUI_URL': '', 'HA_WEATHER_ENTITY': '', + 'ALFRED_WEATHER_ILLUSTRATION_ATTEMPTS': '2', + 'ALFRED_WEATHER_ILLUSTRATION_TARGET_SCORE': '8.6', + 'ALFRED_WEATHER_DISPLAY_SIZE': '350', + 'ALFRED_WEATHER_REVIEW': 'true', + 'ALFRED_WEATHER_REFERENCE_MODE': 'auto', + 'ALFRED_WEATHER_REFERENCE_DENOISE': '0.56', + 'ALFRED_WEATHER_PRESERVE_FACE': 'true', + 'ALFRED_WEATHER_ACCESSORY_LIMIT': '1', +} + + +def _auth_config(): + try: + return json.loads(AUTH_FILE.read_text(encoding='utf-8')) + except (OSError, ValueError): + return {} + + +def _authenticated(): + config = _auth_config() + password_hash = config.get('password_hash', '') + if not password_hash: + return False + # Changing the admin password instantly invalidates old signed sessions. + expected_version = __import__('hashlib').sha256(password_hash.encode()).hexdigest()[:24] + return bool(session.get('alfred_admin') and + hmac.compare_digest(str(session.get('auth_version', '')), expected_version)) + + +def _valid_csrf(): + token = request.headers.get('X-Alfred-CSRF', '') + expected = session.get('alfred_csrf', '') + return bool(token and expected and hmac.compare_digest(token, expected)) + + +def _trusted_request(): + origin = request.headers.get('Origin', '') + if origin: + parsed = urlsplit(origin) + if parsed.netloc.lower() != request.host.lower() or parsed.scheme not in ('http', 'https'): + return False + if request.headers.get('Sec-Fetch-Site', '').lower() == 'cross-site': + return False + return True + + +def _require_admin(handler): + @functools.wraps(handler) + def wrapped(*args, **kwargs): + if not _authenticated(): + return jsonify(error='Authentification administrateur nécessaire.'), 401 + if request.method != 'GET' and (not _valid_csrf() or not _trusted_request()): + return jsonify(error='Protection anti-CSRF : requête refusée.'), 403 + return handler(*args, **kwargs) + return wrapped + + +def _validate(key, value): + if key not in FIELDS: + raise ValueError(f'Paramètre non autorisé : {key}.') + kind = FIELDS[key][1] + if kind == 'boolean': + if value not in (True, False, 'true', 'false'): + raise ValueError(f'{key} doit être vrai ou faux.') + return 'true' if value in (True, 'true') else 'false' + if isinstance(value, bool): + raise ValueError(f'Valeur invalide pour {key}.') + value = str(value).strip() + if any(c in value for c in ('\r', '\n', '\x00', '"', "'", '\\', '$', '`')): + raise ValueError(f'Caractères interdits pour {key}.') + if kind == 'text': + if not 2 <= len(value) <= 30: + raise ValueError('Le nom doit contenir entre 2 et 30 caractères.') + elif kind == 'wake': + if not re.fullmatch(r'[A-Za-zÀ-ÖØ-öø-ÿ]{2,22}', value): + raise ValueError('Le mot-clé Vosk doit être un mot simple de 2 à 22 lettres.') + value = value.lower() + elif kind == 'voice': + if not re.fullmatch(r'[A-Za-z0-9_.-]{2,96}', value): + raise ValueError('Identifiant de voix Piper invalide.') + elif kind == 'url': + if not value: + return '' + parsed = urlsplit(value) + if (parsed.scheme not in ('http', 'https') or not parsed.hostname or + parsed.username or parsed.password or parsed.path not in ('', '/') or + parsed.query or parsed.fragment): + raise ValueError(f'{key} attend une URL http(s) de serveur sans chemin ni identifiants.') + if parsed.port and not 1 <= parsed.port <= 65535: + raise ValueError('Port réseau invalide.') + value = value.rstrip('/') + elif kind == 'entity': + if value and not re.fullmatch(r'weather\.[a-z0-9_]{2,80}', value): + raise ValueError('Attendu : weather.identifiant ou une valeur vide.') + elif kind == 'attempts': + if value not in ('1', '2', '3'): + raise ValueError('Nombre d’essais autorisé : 1, 2 ou 3 par personnage.') + elif kind == 'score': + value = str(float(value)) + if not 1 <= float(value) <= 10: + raise ValueError('Score entre 1 et 10.') + elif kind == 'size': + if value not in ('250', '300', '350', '400', '450', '500'): + raise ValueError('Taille autorisée : 250, 300, 350, 400, 450 ou 500 px.') + elif kind == 'denoise': + value = str(float(value)) + if not 0.25 <= float(value) <= 0.85: + raise ValueError('Denoise entre 0.25 et 0.85.') + elif kind == 'reference_mode': + if value not in ('auto', 'required', 'off'): + raise ValueError('Mode référence : auto, required ou off.') + elif kind == 'accessory': + if value not in ('0', '1', '2'): + raise ValueError('Accessoires visibles autorisés : 0, 1 ou 2.') + return value + + +def _read_config(): + local = dict(dotenv_values(GENERAL_ENV)) if GENERAL_ENV.exists() else {} + weather = dict(dotenv_values(WEATHER_ENV)) if WEATHER_ENV.exists() else {} + effective = {} + for key, (group, _) in FIELDS.items(): + env_file = local if group == 'general' else weather + # File values take precedence on the next restart. Show saved values. + effective[key] = str(env_file.get(key) if env_file.get(key) is not None + else os.getenv(key, DEFAULTS[key])) + return effective + + +def _save_env(path: Path, changes: dict): + path.parent.mkdir(parents=True, exist_ok=True) + existing = path.read_text(encoding='utf-8') if path.exists() else '' + lines = existing.splitlines() + updated = set() + result = [] + for line in lines: + match = re.match(r'^\s*([A-Z][A-Z0-9_]*)\s*=', line) + if match and match.group(1) in changes: + name = match.group(1) + if name not in updated: + result.append(f'{name}={changes[name]}') + updated.add(name) + else: + result.append(line) + for key, value in changes.items(): + if key not in updated: + result.append(f'{key}={value}') + tmp = path.with_name(path.name + f'.{secrets.token_hex(4)}.tmp') + try: + with tmp.open('w', encoding='utf-8') as fp: + fp.write('\n'.join(result).rstrip() + '\n') + fp.flush() + os.fsync(fp.fileno()) + os.chmod(tmp, 0o600) + tmp.replace(path) + finally: + tmp.unlink(missing_ok=True) + + +def _probe_http(url, *, token=None, timeout=2): + start = time.monotonic() + try: + response = requests.get(url, headers={'Authorization': f'Bearer {token}'} if token else {}, + timeout=timeout, allow_redirects=False) + if 200 <= response.status_code < 300: + state = 'ok' + detail = f'HTTP {response.status_code}' + elif response.status_code in (401, 403): + state, detail = 'warning', f'Connexion établie, accès refusé ({response.status_code})' + else: + state, detail = 'warning', f'HTTP {response.status_code}' + except requests.RequestException: + state, detail = 'down', 'Aucune réponse HTTP' + return {'state': state, 'detail': detail, 'latency_ms': round((time.monotonic()-start)*1000)} + + +def _probe_tcp(host, port, timeout=1.5): + try: + with socket.create_connection((host, int(port)), timeout=timeout): + return {'state': 'ok', 'detail': f'Port {port} accessible'} + except (OSError, ValueError): + return {'state': 'down', 'detail': f'Port {port} inaccessible'} + + +def _weather_comfy_url(): + # Respect the V5 precedence if possible, without reaching into credentials. + settings = dict(dotenv_values(WEATHER_ENV)) if WEATHER_ENV.exists() else {} + return (_read_config()['ALFRED_COMFYUI_URL'] or settings.get('ALFRED_COMFYUI_URL') or + os.getenv('ALFRED_COMFYUI_URL') or os.getenv('COMFYUI_URL') or + dict(dotenv_values('/opt/hermes/MCP_comfyui/.env')).get('COMFYUI_URL') or '') + + +def _health(wake, audio_settings): + now = datetime.now(timezone.utc).isoformat() + audio = audio_settings.get_settings() + try: + wake_state = wake.status() + except Exception: + wake_state = {'running': False, 'paused': True, 'error': 'Impossible de lire Vosk'} + cam_index = int(os.getenv('CAMERA_INDEX', '0')) + camera_path = Path(f'/dev/video{cam_index}') + camera_ok = camera_path.exists() and os.access(camera_path, os.R_OK) + # Checking /dev/videoN doesn't open the device: no conflict with facial recognition. + camera = {'state':'ok' if camera_ok else 'warning', + 'detail':f'{camera_path} détectée' if camera_ok else f'{camera_path} indisponible ou permissions insuffisantes'} + mute_micro = audio.get('mic_muted', False) + microphone = {'state':'muted' if mute_micro else ('ok' if wake_state.get('running') else 'warning'), + 'detail':'Micro coupé volontairement' if mute_micro else ('Détection Vosk active' if wake_state.get('running') and not wake_state.get('paused') else 'Vosk non disponible ou en pause')} + loudspeaker = {'state':'muted' if audio.get('speaker_muted') else 'ok', + 'detail':'Son coupé' if audio.get('speaker_muted') else f"Volume {audio.get('volume', '?')} %"} + + config = _read_config() + comfy = _weather_comfy_url() + ollama = config.get('ALFRED_OLLAMA_URL') or os.getenv('OLLAMA_HOST') or os.getenv('OLLAMA_URL') + ha_base = os.getenv('HA_URL', '').rstrip('/') + tts_host = os.getenv('PIPER_HOST', '').strip() + tts_port = os.getenv('PIPER_PORT', '10200') + services = {'alfred': {'state':'ok', 'detail':'Serveur Flask actif'}, + 'hermes_cli': {'state':'ok' if shutil.which('hermes') else 'warning', + 'detail':'CLI détectée' if shutil.which('hermes') else 'CLI non détectée dans le PATH du service'}} + services['hermes_dashboard'] = _probe_tcp('127.0.0.1', 9119) + services['hermes_dashboard']['label'] = 'Tableau de bord (optionnel)' + services['comfyui'] = _probe_http(comfy.rstrip('/') + '/system_stats') if comfy else {'state':'unknown','detail':'URL non configurée'} + services['ollama'] = _probe_http(ollama.rstrip('/') + '/api/tags') if ollama else {'state':'unknown','detail':'URL non configurée'} + services['home_assistant'] = _probe_http(ha_base + '/api/', token=os.getenv('HA_TOKEN')) if ha_base else {'state':'unknown','detail':'URL non configurée'} + services['wyoming_piper'] = _probe_tcp(tts_host, tts_port) if tts_host else {'state':'unknown','detail':'Wyoming Piper non configuré (Piper local possible)'} + + mcp_path = Path('/opt/hermes') + mcps = {} + for slug, label in [('MCP_home_assistant','Home Assistant'), ('MCP_comfyui','ComfyUI'), ('MCP_face_recognition','Reconnaissance faciale'),('MCP_wazuh','Wazuh')]: + found = (mcp_path / slug).exists() + mcps[slug] = {'state':'ok' if found else 'unknown', 'detail':'Dossier présent (connexion non vérifiée)' if found else 'Dossier absent', 'label':label} + + disk = shutil.disk_usage(ROOT) + mem = {} + try: + info = {} + for line in Path('/proc/meminfo').read_text().splitlines(): + k, _, v = line.partition(':') + if k in ('MemTotal','MemAvailable'): + info[k] = int(v.strip().split()[0]) * 1024 + if info.get('MemTotal'): + mem = {'total': info['MemTotal'], 'used': info['MemTotal'] - info.get('MemAvailable', 0)} + except (OSError, ValueError): + pass + return {'time': now, 'audio':audio, 'wake':wake_state, 'camera':camera, + 'microphone':microphone, 'speaker':loudspeaker, 'services':services, 'mcps':mcps, + 'system':{'disk_total':disk.total,'disk_used':disk.used, 'memory':mem, + 'load_1m':round(os.getloadavg()[0], 2) if hasattr(os,'getloadavg') else None}} + + +def register_admin_console(app, wake, audio_settings): + """Register pages/API on the existing Flask app. Call exactly once.""" + secret_file = ROOT / 'config' / 'admin_secret.key' + if not secret_file.exists(): + raise RuntimeError('Secret administrateur absent ; exécuter install_patch.py.') + app.secret_key = secret_file.read_bytes() + app.config.update(SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE='Strict', + SESSION_COOKIE_SECURE=os.getenv('ALFRED_ADMIN_HTTPS', 'false').lower() == 'true', + PERMANENT_SESSION_LIFETIME=timedelta(hours=8)) + + bp = Blueprint('alfred_admin', __name__) + + @bp.get('/config') + @bp.get('/config.html') + def config_page(): + return render_template('config.html') + + @bp.get('/surveillance') + @bp.get('/surveillance.html') + def surveillance_page(): + return render_template('surveillance.html') + + @bp.get('/api/control/auth') + def auth_status(): + authed = _authenticated() + return jsonify(authenticated=authed, configured=bool(_auth_config().get('password_hash')), + csrf=session.get('alfred_csrf') if authed else None) + + @bp.post('/api/control/login') + def login(): + if not _trusted_request() or not request.is_json: + return jsonify(error='Requête refusée.'), 403 + data = request.get_json(silent=True) or {} + password = data.get('password', '') + if not isinstance(password, str) or len(password) > 500: + return jsonify(error='Mot de passe incorrect.'), 400 + now = time.monotonic() + client = request.remote_addr or 'unknown' + with LOGIN_LOCK: + events = [x for x in LOGIN_ATTEMPTS.get(client, []) if x > now - 900] + LOGIN_ATTEMPTS[client] = events + if len(events) >= 5: + return jsonify(error='Trop de tentatives : réessaie plus tard.'), 429 + config = _auth_config() + password_hash = config.get('password_hash') + if not password_hash: + return jsonify(error='Définis d’abord le mot de passe avec set_admin_password.py.'), 503 + if not check_password_hash(password_hash, password): + with LOGIN_LOCK: + LOGIN_ATTEMPTS[client].append(now) + return jsonify(error='Mot de passe incorrect.'), 401 + with LOGIN_LOCK: + LOGIN_ATTEMPTS.pop(client, None) + session.clear() + session.permanent = True + session['alfred_admin'] = True + session['auth_version'] = __import__('hashlib').sha256(password_hash.encode()).hexdigest()[:24] + session['alfred_csrf'] = secrets.token_urlsafe(32) + return jsonify(authenticated=True, csrf=session['alfred_csrf']) + + @bp.post('/api/control/logout') + @_require_admin + def logout(): + session.clear() + return jsonify(ok=True) + + @bp.get('/api/control/config') + @_require_admin + def config_get(): + return jsonify(values=_read_config(), note='La plupart des réglages nécessitent un redémarrage.') + + @bp.post('/api/control/config') + @_require_admin + def config_update(): + data = request.get_json(silent=True) + if not isinstance(data, dict) or len(data) > len(FIELDS): + return jsonify(error='JSON invalide.'), 400 + try: + normalized = {key: _validate(key, value) for key, value in data.items()} + except (ValueError, TypeError, OverflowError) as exc: + return jsonify(error=str(exc)), 400 + general = {k:v for k,v in normalized.items() if FIELDS[k][0]=='general'} + weather = {k:v for k,v in normalized.items() if FIELDS[k][0]=='weather'} + try: + with CONFIG_LOCK: + if general: _save_env(GENERAL_ENV, general) + if weather: _save_env(WEATHER_ENV, weather) + except OSError as exc: + return jsonify(error=f'Impossible d’enregistrer : {exc}'), 500 + return jsonify(ok=True, restart_required=True, values=_read_config()) + + @bp.get('/api/control/health') + @_require_admin + def health(): + return jsonify(_health(wake, audio_settings)) + + @bp.post('/api/control/restart') + @_require_admin + def restart(): + if os.getenv('ALFRED_ALLOW_WEB_RESTART', 'false').lower() not in ('true', '1', 'yes'): + return jsonify(error='Redémarrage Web désactivé. Activer ALFRED_ALLOW_WEB_RESTART=true (voir README).'), 403 + systemctl = shutil.which('systemctl') + runner = shutil.which('systemd-run') + if not systemctl or not runner: + return jsonify(error='systemd-run ou systemctl absent. Redémarrer depuis le terminal.'), 503 + # Schedule restart in a DIFFERENT systemd unit/cgroup so stopping Alfred + # cannot kill the restart command mid-flight. + command = [runner, '--unit=alfred-web-restart', '--on-active=2s', '--collect', + systemctl, 'restart', 'alfred.service'] + if os.geteuid() != 0: + command = ['sudo', '-n', *command] + def do_restart(): + time.sleep(0.5) + try: + result = subprocess.run(command, capture_output=True, text=True, timeout=12, check=False) + if result.returncode: + app.logger.error('Planification du redémarrage refusée (%s): %s', + result.returncode, result.stderr[:350]) + except (OSError, subprocess.TimeoutExpired) as exc: + app.logger.error('Planification du redémarrage échouée: %s', exc) + threading.Thread(target=do_restart, daemon=True).start() + return jsonify(accepted=True, message='Demande de redémarrage transmise ; contrôler le retour du service.') , 202 + + app.register_blueprint(bp) diff --git a/alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/static/js/alfred-admin.js b/alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/static/js/alfred-admin.js new file mode 100644 index 0000000..eed0c22 --- /dev/null +++ b/alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/static/js/alfred-admin.js @@ -0,0 +1,183 @@ +(() => { + 'use strict'; + const $ = id => document.getElementById(id); + const page = document.body.dataset.page; + const state = {csrf: null, logged: false, baseline: {}}; + const fields = [...document.querySelectorAll('[data-setting]')]; + let polling = null; + const fetchJSON = async (url, options = {}) => { + const headers = { ...(options.headers || {}) }; + if (options.body) headers['Content-Type'] = 'application/json'; + if (options.method && options.method !== 'GET' && state.csrf) headers['X-Alfred-CSRF'] = state.csrf; + const response = await fetch(url, {...options, credentials: 'same-origin', cache:'no-store', headers}); + let data; + try { data = await response.json(); } catch { data = {}; } + if (response.status === 401 && url !== '/api/control/login') { showLogin(); throw new Error('Session expirée. Reconnecte-toi.'); } + if (!response.ok) throw new Error(data.error || 'Erreur HTTP '+response.status); + return data; + }; + const setMessage = (message, bad=false) => { + const el = $('message'); if (!el) return; + el.textContent = message; el.style.color = bad ? '#f1aaaa' : '#a1dcbe'; + }; + const showLogin = () => { + state.logged = false; $('mainArea').hidden = true; $('authScreen').hidden = false; + if (polling) clearInterval(polling); polling = null; + }; + const showPage = () => { state.logged=true; $('mainArea').hidden=false; $('authScreen').hidden=true; }; + const getFieldValue = el => el.type === 'checkbox' ? el.checked : el.value; + function configDirty() { + const changed = fields.some(el => String(getFieldValue(el)) !== String(state.baseline[el.id] ?? '')); + if ($('saveState')) $('saveState').textContent = changed ? 'Modifications non enregistrées' : 'Aucun changement'; + } + function markInput() { + if ($('scoreOut')) $('scoreOut').textContent = Number($('ALFRED_WEATHER_ILLUSTRATION_TARGET_SCORE').value || 8.6).toFixed(1) + ' / 10'; + if ($('denoiseOut')) $('denoiseOut').textContent = Number($('ALFRED_WEATHER_REFERENCE_DENOISE').value || .56).toFixed(2); + configDirty(); + } + async function loadConfig() { + const values = (await fetchJSON('/api/control/config')).values || {}; + fields.forEach(el => { + if (el.type==='checkbox') el.checked = String(values[el.id]).toLowerCase() === 'true'; + else el.value = values[el.id] ?? ''; + state.baseline[el.id] = getFieldValue(el); + }); + markInput(); + } + async function saveConfig() { + const values = {}; + fields.forEach(el => { values[el.id] = getFieldValue(el); }); + try { + const result = await fetchJSON('/api/control/config', {method:'POST', body:JSON.stringify(values)}); + state.baseline = {}; + fields.forEach(el => state.baseline[el.id] = getFieldValue(el)); + configDirty(); + $('pendingNotice').hidden = false; + setMessage(result.ok ? 'Configuration sauvegardée.' : 'Enregistrement inconnu.'); + } catch (error) { setMessage(error.message, true); } + } + function serviceStatus(el, data) { + if (!el) return; + const labels = {ok:'Disponible',down:'Indisponible',warning:'Attention',muted:'Coupé',unknown:'Non vérifié'}; + el.dataset.state = data.state || 'unknown'; + el.textContent = labels[data.state] || 'Inconnu'; + } + const safeText = (id, value) => { if ($(id)) $(id).textContent = String(value ?? '—'); }; + const readable = value => { + const x = Number(value) || 0; + if (x >= 1073741824) return (x / 1073741824).toFixed(1) + ' Go'; + return (x / 1048576).toFixed(1) + ' Mo'; + }; + function renderHealth(data) { + const timestamp = new Date(data.time).toLocaleTimeString('fr-FR', {hour:'2-digit',minute:'2-digit',second:'2-digit'}); + safeText('updatedAt', timestamp); + serviceStatus($('cameraStatus'), data.camera); safeText('cameraDetail',data.camera?.detail); + serviceStatus($('microStatus'), data.microphone); safeText('microDetail',data.microphone?.detail); + serviceStatus($('speakerStatus'), data.speaker); safeText('speakerDetail',data.speaker?.detail); + const wake = data.wake || {}; + serviceStatus($('wakeStatus'), {state:!wake.running?'down':wake.paused?'warning':'ok'}); + safeText('wakeLabel', `Vosk · ${wake.word || 'alfred'}`); + safeText('wakeDetail', wake.error || (wake.paused ? 'Détection en pause' : 'Détection active')); + const labels = {alfred:'Alfred / Flask', hermes_cli:'Hermes Agent (CLI)', hermes_dashboard:'Hermes WebUI', comfyui:'ComfyUI', ollama:'Ollama', home_assistant:'Home Assistant', wyoming_piper:'Wyoming Piper'}; + const list = $('serviceList'); list.replaceChildren(); + for (const [key,service] of Object.entries(data.services || {})) { + const row = document.createElement('div'); row.className='service-row'; + const desc = document.createElement('div'); + const title=document.createElement('div');title.className='service-label';title.textContent=labels[key] || key; + const sub=document.createElement('div');sub.className='service-sub';sub.textContent=service.detail || ''; + desc.append(title,sub); + const aside=document.createElement('div');aside.className='service-tail'; + if (service.latency_ms != null) {const delay=document.createElement('span');delay.className='muted microtext';delay.textContent=service.latency_ms+' ms';aside.append(delay);} + const status=document.createElement('span');status.className='status-pill';serviceStatus(status,service);aside.append(status); + row.append(desc,aside);list.append(row); + } + const sys = data.system || {}; + const diskPercent = sys.disk_total ? Math.round(100*sys.disk_used/sys.disk_total) : 0; + safeText('diskPercent', diskPercent+' %');$('diskBar').style.width=diskPercent+'%'; + safeText('diskText', readable(sys.disk_used)+' utilisés sur '+readable(sys.disk_total)); + const mem=sys.memory || {}; + const memPercent=mem.total ? Math.round(100*mem.used/mem.total) : 0; + safeText('memPercent',mem.total ? memPercent+' %' : 'N/D');$('memBar').style.width=memPercent+'%'; + safeText('memText',mem.total ? readable(mem.used)+' utilisés sur '+readable(mem.total) : 'Non disponible'); + safeText('loadAvg',sys.load_1m); + const mcps=$('mcpList');mcps.replaceChildren(); + for (const mcp of Object.values(data.mcps || {})) { + const card=document.createElement('div');card.className='mcp-item'; + const title=document.createElement('strong');title.textContent=mcp.label || 'MCP'; + const status=document.createElement('span');status.className='status-pill';serviceStatus(status,mcp); + const p=document.createElement('p');p.textContent=mcp.detail; + card.append(title,status,p);mcps.append(card); + } + } + let inFlight = false; + async function refreshHealth() { + if (inFlight || document.hidden) return; + inFlight = true; + try { renderHealth(await fetchJSON('/api/control/health'));setMessage(''); } + catch(err){setMessage('Surveillance : '+err.message,true);} + finally{inFlight=false;} + } + async function loadAudio() { + try { + const d=await fetchJSON('/api/audio/settings'); + $('liveVolume').value=d.volume;$('liveVolumeOut').textContent=d.volume+' %'; + $('liveMic').disabled = d.mic_gain == null; + if(d.mic_gain != null)$('liveMic').value=d.mic_gain; + $('liveMicOut').textContent=d.mic_gain == null ? 'N/D' : d.mic_gain+' %'; + $('muteMicro').checked=Boolean(d.mic_muted);$('muteSpeaker').checked=Boolean(d.speaker_muted); + } catch(err){safeText('audioResult',err.message);} + } + async function updateAudio(values) { + try { + const d=await fetchJSON('/api/audio/settings', {method:'POST',body:JSON.stringify(values)}); + safeText('audioResult','Réglage appliqué.'); + $('liveVolume').value=d.volume;$('liveVolumeOut').textContent=d.volume+' %'; + $('muteMicro').checked=Boolean(d.mic_muted);$('muteSpeaker').checked=Boolean(d.speaker_muted); + } catch(err){safeText('audioResult','Erreur : '+err.message);await loadAudio();} + } + async function onLogin(event) { + event.preventDefault(); + const password=$('adminPassword').value; + try { + const response=await fetchJSON('/api/control/login',{method:'POST',body:JSON.stringify({password})}); + state.csrf=response.csrf;$('adminPassword').value='';await enterApp(); + } catch(err){safeText('loginMessage',err.message);} + } + async function enterApp() { + showPage(); + if(page==='config'){ + await Promise.all([loadConfig(),loadAudio()]); + }else{ + await refreshHealth(); + polling=setInterval(refreshHealth,15000); + } + } + async function init() { + $('loginForm')?.addEventListener('submit', onLogin); + $('logoutButton')?.addEventListener('click', async ()=>{ + try{await fetchJSON('/api/control/logout',{method:'POST'});}catch(e){} + state.csrf=null;showLogin(); + }); + fields.forEach(el=>{el.addEventListener('change',markInput);el.addEventListener('input',markInput);}); + $('saveConfig')?.addEventListener('click',saveConfig); + $('restartButton')?.addEventListener('click',async ()=>{ + if(!confirm('Redémarrer Alfred maintenant ? Les opérations en cours seront interrompues.'))return; + try{const data=await fetchJSON('/api/control/restart',{method:'POST'});setMessage(data.message||'Demande transmise.');$('restartButton').disabled=true;setTimeout(()=>$('restartButton').disabled=false,5000);} + catch(err){setMessage(err.message,true);} + }); + $('testVoice')?.addEventListener('click',async()=>{ + try{await fetchJSON('/api/tts/say',{method:'POST',body:JSON.stringify({text:'Bonjour, je suis Alfred. Voici un test de ma voix actuelle.'})});setMessage('Test vocal demandé.');} + catch(err){setMessage(err.message,true);} + }); + let volDelay, gainDelay; + $('liveVolume')?.addEventListener('input',e=>{safeText('liveVolumeOut',e.target.value+' %');clearTimeout(volDelay);volDelay=setTimeout(()=>updateAudio({volume:Number(e.target.value)}),250);}); + $('liveMic')?.addEventListener('input',e=>{safeText('liveMicOut',e.target.value+' %');clearTimeout(gainDelay);gainDelay=setTimeout(()=>updateAudio({mic_gain:Number(e.target.value)}),250);}); + $('muteMicro')?.addEventListener('change',e=>updateAudio({mic_muted:e.target.checked})); + $('muteSpeaker')?.addEventListener('change',e=>updateAudio({speaker_muted:e.target.checked})); + $('refreshHealth')?.addEventListener('click',refreshHealth); + const login=await fetchJSON('/api/control/auth').catch(()=>({authenticated:false})); + if (login.authenticated){state.csrf=login.csrf;try{await enterApp();}catch(err){setMessage(err.message,true);}} + else{showLogin();if(!login.configured)safeText('loginMessage','Mot de passe non défini : exécute set_admin_password.py sur Alfred.');} + } + document.addEventListener('DOMContentLoaded',init); +})(); diff --git a/alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/templates/config.html b/alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/templates/config.html new file mode 100644 index 0000000..9b49a47 --- /dev/null +++ b/alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/templates/config.html @@ -0,0 +1,55 @@ + + + + +Alfred · Configuration + + + + +
+
+
A
AlfredCentre de contrôle
+ +
+
+

PERSONNALISATION

Configuration

Paramètres de l’assistant, voix et services. Les modifications prennent effet après redémarrage.

Aucun changement
+ +
+
✦

Identité et voix

Nom affiché, mot d'activation et voix de synthèse

+
Visible dans l’interface après redémarrage.
+
Un seul mot. Le modèle Vosk doit pouvoir le reconnaître ; le mode OpenWakeWord nécessite un modèle entraîné.
+
Exemple : fr_FR-tom-medium. La voix doit exister sur ton serveur Wyoming ; pour Piper local, c’est le fichier modèle qui détermine la voix.
+
Utilise les réglages déjà actifs, sans redémarrage
+
+
☁

Illustrations météo

Qualité, visages de référence et accessoires ComfyUI

+
+
8.6 / 10
+
+
+
+
0.56
Une valeur élevée modifie davantage la tenue, mais risque de changer le visage.
+
+
+
+
♫

Audio en direct

Volume, sensibilité et coupures immédiates

+
—
+
—
+
+
+

Les réglages audio sont appliqués sans redémarrage.

+
+
⌘

Connexions et météo

Adresses des serveurs et source météo

+
Laisse vide pour conserver la configuration Hermes existante.
+
Cette adresse sert au contrôle de disponibilité ; Hermes garde sa propre configuration LLM.
+
Exemple : weather.forecast_home. Le jeton Home Assistant n’est pas exposé ici.
+ Voir la surveillance des services +
+
+

Maintenance

Redémarrer Alfred pour appliquer les paramètres enregistrés. Action protégée par mot de passe administrateur.

Le bouton requiert une autorisation systemd explicitement activée.
+

+
+
A

Accès administrateur

Connecte-toi pour modifier la configuration d'Alfred.

Accès local / réseau privé uniquement.
+
+ + diff --git a/alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/templates/surveillance.html b/alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/templates/surveillance.html new file mode 100644 index 0000000..fddc276 --- /dev/null +++ b/alfred/app/backup_admin_surveillance_v2_20261027_175910_468934/templates/surveillance.html @@ -0,0 +1,19 @@ +Alfred · Surveillance + +
+
A
AlfredCentre de contrôle
+
+

DIAGNOSTIC

Surveillance

État des périphériques, de l’assistant et des services locaux.

Chargement…
+
+
▣
CAMÉRAWebcam—

Vérification…

+
◉
MICROPHONECapture audio—

Vérification…

+
♫
HAUT-PARLEURSortie audio—

Vérification…

+
◌
MOT DE RÉVEILVosk—

Vérification…

+
+
⇄

Services et connexions

Tests réels : HTTP, port TCP ou présence du binaire

Chargement…

+
▤

Système

Ressources du mini-PC hébergeant Alfred

Disque de l’application—

—

Mémoire RAM—

—

Charge système (1 min)—
Les contrôles de caméra n'ouvrent pas le flux vidéo, afin de ne pas bloquer la reconnaissance faciale.
+
⚙

MCP Hermes détectés

Présence des répertoires, pas un test de communication des outils MCP

Chargement…

+

+
+
A

Accès administrateur

Connecte-toi pour consulter l'état d'Alfred.

Accès local / réseau privé uniquement.
+
diff --git a/alfred/app/cache/weather_outfit_cache.json b/alfred/app/cache/weather_outfit_cache.json index 08ff115..def1bfc 100644 --- a/alfred/app/cache/weather_outfit_cache.json +++ b/alfred/app/cache/weather_outfit_cache.json @@ -423,5 +423,121 @@ }, "cached_at": 1793111827.3695734, "focus_label": "samedi 31 octobre" + }, + "bcd2bb01a6eee2d756888ce1272d02f38fe5cdc2c8b94ee40851fe702d6e8e85": { + "file": "bcd2bb01a6eee2d756888ce1272d02f38fe5cdc2c8b94ee40851fe702d6e8e85.jpg", + "prompt_ids": [ + "a40affa5-4232-46a2-8ab7-19ca0e731dd4", + "930a85ff-aa99-404b-9356-911324ff34e0" + ], + "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": 1793116044.4125576, + "focus_label": "aujourd’hui" + }, + "9384429b2e4292835018049e1c181d50d6cf911bfb587c2b8b0036c2ecb619a1": { + "file": "9384429b2e4292835018049e1c181d50d6cf911bfb587c2b8b0036c2ecb619a1.jpg", + "prompt_ids": [ + "dd15bd0c-ac93-4da8-a93c-17810a04237f", + "940eda05-2e4c-4c4c-a9fb-29259c907e1b" + ], + "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": 1793181286.3307507, + "focus_label": "aujourd’hui" + }, + "a0e631c42d8a4fc9dc3abad674a6625d32d9fe3b471d7a101a62c4a790a3926e": { + "file": "a0e631c42d8a4fc9dc3abad674a6625d32d9fe3b471d7a101a62c4a790a3926e.jpg", + "prompt_ids": [ + "edd9eaf1-9c36-4b5b-b765-f4346fae3699", + "e4c7e52d-00b3-4571-a8b8-dd9c6bf3ff96" + ], + "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": 1793183681.1423311, + "focus_label": "aujourd’hui" + }, + "dd262938ddb64632659bedd83dd2d7e9f173b55663a3d155002468deee01a3b4": { + "file": "dd262938ddb64632659bedd83dd2d7e9f173b55663a3d155002468deee01a3b4.jpg", + "prompt_ids": [ + "b1ae1d61-d434-409c-8d89-be73382dd328", + "f55b483c-eb69-4792-acb4-4c52b687801f" + ], + "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": 1793186782.712917, + "focus_label": "aujourd’hui" } } \ No newline at end of file diff --git a/alfred/app/cache/weather_outfit_images/9384429b2e4292835018049e1c181d50d6cf911bfb587c2b8b0036c2ecb619a1.jpg b/alfred/app/cache/weather_outfit_images/9384429b2e4292835018049e1c181d50d6cf911bfb587c2b8b0036c2ecb619a1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cc78499af290c70efdab9e88a8d0c6350671174e GIT binary patch literal 25692 zcmb5Ubx>SQ^fowHaF+!4z(61g!Cit4KEMz>1b27$KyVGNgAWj3a0u=a+}+*XmUp*y z_xq}T`^TPJed|`=+uf(S`ktrzdCtr1%PIg5Bqb{aKtKQh5MD39%K|_GfQ0y;{dY%t zHRLzQ{~6kwHz;q=(J(O3(a_N`uyC<3FmW)^(XsKdad7eQ@bNIP2nY%A2w$J^{_6zc zzm`bIXs-?NFwrqzYyW@ROB(8BZDtcpuK(8wns%4OBD}QY}eH)I4H`1=t_4KM%(~UCeBy zCUU+2Oc!*%7D$yg@d3T%-TqeFuBv%&ya23a3keQdiF+rbZPe8sm9k<{tEs*Q6GTXolbg0Cj?277r#S zab)Ui#La?6i|JvC-Yg)Iw+vj9)6Y~G{gQIYeV39>SrX!kIeIxq@*3=nFL<-&WG{cP{SX;T$iA4V2tqk>&A1pAdXARLN5 z^rTl@Zyvi*7W6-4MFz4`J2!t_9|_syN&xDv$##Sj$WZf7f@1z8lu_J~?T=1E4lcia z+JTvQPewWABUjBa7{%~k0ld(K5z{n&@vjOdexvkIHO_C&%^1OzKk=^>t_eRsS}qV? zWP#UHejcyZY2q4IB`7dr$hm$#mubJ90hi@5a@DAhu;a=z=Z0UqV=(9X3D>Ada>}Sk z5&`mgH{|5X8fzD=#muyb2lAOIf)V-g?8GjJ9@hB@OV-5JC%OL-d)cel_E~-Z>@F_m z>_$mQ`_5aWOOHC_w>s;!#}DIy7l2b$Ur1LI#qXaUo*d*mqVCWONqb>MM&oZHjfDt( zT*r+|)hc?0Oh3wTz~oxe!DMxia~TU}e-t-5wMsCNh(EQvFI$U+$pi)Q*t}!h*Xu-= z-*LF^K$>joH8an=ona+|p%HN233k{S%$$%<44o^x5TnF>dSx=FiDRLDIr-nLgPcly z-?%uU6hLNIutgZLo@YtUvZ`0Dsk{3(3?a)~?>xl5N@~Y}A0rw*N-}}o%f_#hb0^El zKA^m9owtrBLeS!Ys};U2z;373aGnwSUORn+e?L3{oEa#r$z+qJ;$mfaV=%}EVa?)L zBY;jsY1wduvt9RJC+bdQVsY&7|cR5xli|NgDWbS=HTnym7J zgc2ju&F>g7YDU~0d6O7|3YK~6f=O4I^0HsTKbG+H0+;0)$<-qiF(U3}`S%6j(I;bk=&!AVbkm}Uf>~S74;7C!n z-h8~L_Ae058LyZVDeFxdnq{F+-0s^}^gtk+&`x&;uIK|dXxx9t%%)j zdh4+gnem&j!jaAQ5SB}ys}_9YTVsp7@k5-a{~Xnpth$-_P&S{svd&@Vd5M3=PRZl% z0#Qzk7YBdVKu-C)c7=to&sO~IwJoh)!Jk4g;-Kfj1RH4t1Tl!w+#XgeTYNqBWI7K- z*z{iY+O)n99-4LuOy(O|DOW8A;pK5Z21JYf?0Ktq%RvTRish1u7CApSAK@Hkz zTLxw#7Fif3ixo&Ro3J;CxN4H9crVz8WKc64&l>=O!GtB+KO`9 zCgIG*UzKyinUM}8iBw+$xr!*@;(QqvisIz@z?eIyQ`^9_c*YHp+C%mku0BX*^YT_felXv%akR=sC}Ce8eM0nczQdRlpnB%e%?=!g^fqcr@KBudK?ogbQe#UxN)Uu3b7eZff0Sp;Y|D zv<-C&B}dX?q-frg!KFjRocj6%>5Y4+B)nk6cqxfl({ND)4>Pl)?goRe0vL8E3UbpzTBD=buek=CZ1lO(?iXHkMJ@zGF(H`w=VV6{gE8jGfmqkGZ-ytvB?7rbUxgt#-|6@!wrEk0IWJm(_ z>|=Dl;G?UXGY~M~oc#GP9VQE!mSbVU-0JBZ?VA{<`}Y8JXTXz^wEFbLFqyhIbf1@? zBU$)eJ`_8@*bNM-{$KtGtBr#h0{}&NG5+z|%nJPVhiY3nGBQY9@mreY&RhYgVqowX zaf@GDF>Ccy9C!A5WGhx8lyU-B*4hH3z61jpxM?(k#28F1B0bzD@i7rD5X1;v92c0%>$6JM2?YJj$XuGq5AF7C>{3U$S z)-#hb$LD>Hir?=Aj@ppaQGAE(mVj4OO%p*%exi`Yh3!^-o_Nd&^W)EQiGOEIXKDU? z$OU1$3@DslU2RBhm;I3+Pydo-QzP#jI=L?DDtu4IWGAz4U&jS$(db}(bh8>rfqoz` zXa!#+GeCQ`dm#vzC!n8>%M0(wZD%YJ;GyOUi~{nyFpVB!Dl-*P=P=4$CsU*tq5vGg z0`br!g%o_*?b`%)pSbw9HoK$MFIgYl)ymAZwYS{w@z0A3X8*d|k7gur2o(<+o(mkh zMmOrx;5HIDp#GcgwpmI_F6?thT2c)M-kRDD5<|5IHi{U$YImB*IlQocgaeDdy#fQm z)hkfsiu<<^2S!j__FN*E@KP6`1WH#6OL$dPgcDLQ*d!6gjQe<&K1?1?-4{^BO`O|K zjDY_Q;IFTb^vT?24RSRRW6{$NiaNUFIl3+B}}|H`3)%eC*oYTg9RQki3j4)HYjv({E_B z#{Y2a@-uZ4&jn1MEU~KSIl-f}Ds#4`=+6U6$th>23*`iSUjk$VD6m`Mipj;MmFJLU zuJ>l|BFr)bk6Rl2Cg*r4Srdz%&2T4o6b-3 zzs-Mz?vbNPR2DW_d2IL(CrnDxLk<<#We*_QWyyVeX=ctW4&XtnTC{;aQr=O~H=7xL zS2Svt4y)I>KKtE`Sl=%jWKJ5uGH7BVCnV*?iXLuR7{2D#^gPP?hUSzs74ZB{S{}K0 zJ~f{kzGE_BHkvDq4Tu+tjxKEnGZ|z)Wrl-MN53<=zL@}O2*~oAMW`glfIWWwvz)6v zNYE7LLIiIeCW{ja+pA*@lyj@BdDB#`#!63q`-m-7+lTv2&!zV>2*c`m=;H}PR58~s zE?(;KT5}LRI&h@3qiFPPst);TL+$6jUCdiknD(Vlp0R7O?9+g7)A)W9We@8i2b`$F zEK^hV#xP1LOZ5fNnmClxOSDmHZ^{A=6Cp68WVHs~^bc-(o#S2GtK7YwFZ7heUUxtW zKq2PuuUHHHQ`+_#_BwMUiEwIYgkj;cGG`UP3@v8g^|0ug{W3&48~z?IRz_wPgSw}Q zj=3OY&ae%&L9!Fy>SnD@<{O**K(fQ9uc9Ju)D&g2^V^01wYiE1ebz>j%|uxP!0yXx?W1G$;@0=*{NZ&sYI4NY_o9oaM6 zsckVm)l^b_B7g@oxK@q_on%V5a5gE*7*^?#)5b${%X*+MFk0LE74S%~q}j=ZYeMZKVp+hMt_*kx?heW0JAkrR<)Yg%g&$ z{uoM*i9^h~G;sffI^9IjB%XV^xmlH>oXT-&-RJE>JwE*UJgfnO3!iZE9 z2#ABRpc2CjKI*CWQqt~>1up>auEKrM@@%%vlnCaD-mV1OSSTaqD)@Zn&3m{^pZH+rCFthj!b;0Heiy)+P#@*ABW0L*!JJ$a9ea zoH-Gh7#Xn&HKvTwd;Zq}Mf;y+Rv{uU)ajhTAo=iP56XGQ%{fAsNkuHo4-x^=nzlG< z{`(20+X|AYCAWu#8=~Ip#EvTNCxlBhmv^N|TVU*GN0$~%>l(IYX?6^U11{5IYG=|r zL?7qj$w#b2yD^p-?mZ0#JsR=rBRked-8Dj50>BCb?Pcqf!4c+AGUJj|Il&Z_mimwAZE!MCES>sXBri< z6X-C+`m5V(n9MQCX+@7d8V^AuCrX2=RFC5>UjX<%8xu~!nq>XaA@Se{;>A;4z7^}@# z^7C|sJG1jf5UsI5kWnf5s+v;o&Dvra32$7`=Aw4W5gU4+jCa!HvNAYZM%%P4K&(`r zrI2b)kzZ-DY@eXGN4eb7@?V7!kQJ=xaKdT5~^_5Rp&5usj zMa87}55n93$8aZlAc+KXrZDOBM)7W2u$UIx(?%mbEq1=8`}TcD03v$9bz>qBM`0b3 z23~a5iOfM~0Z)1nyxF|R_#?=A__@2qW#Mfkq{GQL@gE6g1GjvgvhQ9G)%CL_keY0( zZsx0JnTMGAmquMUtO|9UU+NA9=c)sl7lp^#H}=W95Jm*W-y~HqK>1XJ#ano*%*aJB zc|n{mnb8RgG-beZp_@$llwRU}xKY+|G)6BvFG?pBZbj>OQYgb>inYp0v!aGO<9V4Q zl*SX5Hw=8m?VVBv4yy^eqBSM%g0p?8so4D%NfS86ZTQn%+1@u?+?79`qt}vrhu*|p z3s)rdX&>rgVvdfnRM^r{>uEB#iNu8G#T(zuo4ng~UytU{N!}-w;Xt@32YO?2w z4nE?=6{OC{dcdW;Jq090wN-egdhI~doZ<$bIDZfWqJ#avj9|sx`spp5RK5N4Cp2&0 z_vtz&>B)|#E&7`}Q52^SN30AyBx6jmy!Wb1(M!pkvDHJ%ly-ILY>c)BUmsS`4uCvXtGm{f8l8E+lUP6yE5>iwKYtk zK9N?2kp&cjAu}5wh)q|6g7^P!L~E_Tv+Q6q^~gy)pJ`3~vH51RM%+2}u{Q7gTX_W& zb%4VW%MRy}(AzdH#?fKKT<+AK&)~miiLQ+S9fHVj)vxSD_Ed85{Uf~@c3JK{6zyBvkFqrR8P{Af{hb+?JzOpQKHCpS zC7n2T0+1Yb;rSQ9-tG==iV03kowiy~F@D~Ae$}XA(XIA745{MP@~D|Qmx{Uteu`8? zj=L>z;s5~jQ%jA{58#MJfsnqQ$3EhvWf|e|#w1S-ax_7w3;py+lEH#SQ<{;JS;(xs z(iNyG&Slm2LCW#)CjS%hlks-%r>NQeb-zXb%@_rh3hQ5%QJ0T)<`njhMG+5bxp&%B z%CPAtJ~MwD3hun_x*i(_m$CRDM45hp;;+SD<0s>Fnx6Jjs_7Rtp&v5qm7mqaZXj^u zoI=i?BJ*@Mb{-a5>&sH=pupI`jeir~3&N!tMWpphMjbjNd?0O*`U)|?!M*YOouQRR zN&`sY@Jv#@1IPu#d((J0LwDCz>`^f4>dieDH~+hS_yb-Ye?5dGo1iSh(a(F)E!^a~ ze8OeHlbO`UvVYOmf+ijg&*}K*mCZ!!8)tS)$_eX}8KV z?}7gH0tEOIlU>X&t`qbED1v9X`(oE#Drm18>$>ELpcN0b_4WjnMcvTRtKnQZkezA$ zf@Mjp3OUB=#9_#SLeOTzotF*ifa0n*2|T@_&#S5)JI$F6pWw7C-_9XCFjvL2!y_Nu zEN89t!o}v)SEgAr5#fK{@+$k?=hl+T9nfNNL)L$S2px<*k z(zINt;7G&iXD_ZPmbEH1iNW}Z0B~B22RrxN0ViP~$Lny}j>L!A+0<2*P3$4w)Aqz6 z_5J)(-+qZbeGyy2?^WxqF94qnP;WF#q=9u`)f}I~;lf0;?q`+oF+59QzB7v{7nlBg zPL$AwU&rsiLS%r z$UgXc{(9W`%~a2CTQWWD4Xde1xJ>bErlns|b} z037Mls20PlJs!siS_icJL{<)Pcuswl2z)N?bG~FEjeUUX!<<%6-v-V*CvPepITmEZ zFV+WpM`LJwMqx@3+%!eKsDRdm2t~6EJXgBX2hHWs8fC33WBop@H>w9zy^Ec#ZtVvfXjxegpm>W5X218(7ibp)3|-iV%R& zuiod*r+6wBV@@UQ5Jr@+t|;DaTybHmKMO3W%*lF8#r=ClkTvP)aOQ$A48(9;-u*1k zy%yq#CuT(34eea#Uil%}0sR_Rzq>8b7XZIu?Y}eP*nUv z1*|cjmUQY?PCkND*n8(k+MK7^M&6B_7cX+}2ULpoF5P-SQjf3-X2TpszZ&ruE;5}Z zM9|W2%ie*HKIhAMNsEWNtRZM#K5>s~?eG6mOM6Iq;A)wk5B~>#m2i-(amuNeM_vGg z;oVoi=oR;VonH^{h?i*W-+c@=r=!A`?>|`#x1-Q!H9wc(6Y29dk9}|Jk)lxhW$29b zVqpLK40i~pLfFi|eT)Gl{E6ZC5$Q(Uly(+j_`7*dVc72^Ld15q<*TmJj7e8>%~Ozj zoeEPQS$R)_>01#c@fnst9V+{tHp((9C6iMQIWA|Gowam4?5 zFisjJ#Q*6>HPlNzb^Mu19p=KI&k4ykaYUuqMUA~3Jm@(YK9cC9)4Ze}%K2*aeTN&980Pm)A7us)0|Nz@QuNvkR~kfl~Pw zot>I8QjWN%*C})kMb8`h(N)hD)UaDdv9T6z)xGXg77$NxWA!z$?l_rL!zJ%bY(WG` z9*6OaNA(xO9Z8er=S6IX+6ETZn*?#`#&qky8LDcB@hVc`{q>NCEJWTHKvx>pJ6!W4 z&Ja3rBSJ=nOy#w+foPR&+5Qq&m*&3a5aW4_Kvs9LZvQC9ozn%RogH0kv0S&|Uy&y!XdeIBgBBU>5@;?>iW-qLRJ@;}j?K>W-R) zITr@{4rPMAPsmL8^hJ^gJUg}es#LE1dsQJ2WO(^Xy`QMbwCA}eJh#~s5B3>lYbX`aHQG` z4x;g*o-yT6gEs8uS<~g_++noPzS0UesS5QnoL!#mg*J8|M2cW z+T%epx-F#Pk9|tSs|bkXO+6w0z@O_N1xzmgKK!2X`Cq2_wK?Sj^DFwM8(3#a7Siu2 z4tnns4;QJjm7}J_&Dyh&uQ51@bn)YFW4p~1AN>`tcA1+Ca^9JHq47Md!<2g?jDG;2Lx6 z&qch!Im=U!-x_iAu%ShD-pt8mo#Wy@kkr0`5yTeg@fY@QQA+pmexm7qi$^e7K_xM9pQfgl-LPqw+dnn%PacGcH5SQc_-$5`fG5w#W>|q z`pj})ozbFZ386My#*>CvXCz%pQ0bfiM=y|clRP!Vql0EWMa*RKo#;sIUI6p{5b&|@ zp6`S293;Qh@{)}zXHvd2NY$n`$um(ud*SGxCg?7l;6VlfaJz;gA)H9mTcuFhE&*P5 z^1EXk2#aT>U1MewVU#->8mB68L| zg@$W5=92qZ_StY+HVxOv-h?MpvM3|<9v5y~`rG~OO90U$4(f$2ix94zm+Pqy_7Ax` zrkh&bt^ZNHzBoD*ZwV537(7$8A{~PNxJEYkn)|qH}!C zAafKGb$93)#v;Jr7Uu!5bgbq7R#>g^c=HIDy zT-h`TVZ14-8P=%NWXd%}^KuOXlH+-S)B^0pMth<1jXvJF7c2r3G!MuP!+{AO@Sq5j zta)L=$Pu>`YUUeCbbn3X=2ee8vL2_-%U#m5>E@E9<}iLpN=IE@AT7GlJl)l^wy2XZ z?xoNJVAB+9qSCB-Of{*;N-{Ebu?ZJ-TQ(R19P$$Ro+c6Wc6Tp+Ef{;Rbg9?xvAqDY ze!$GBkmN#bPHeGh8_wP1RQ$3RXffUimw^3yL)j#?N~;Hk>abFxgbOKYS$23?Fm}t1 z75UXK9fl@UX8a>|UI5LvlS(ictSgewpt6oEW=^zM&w)UMAKparDPSf?KXj5Q)+*r% zG(+egQ+~~-WveR6PXG5fPjC#~Zi{L^G%il@%h?5&qO)#d22Tn5yoccCu+0M3Y;jDi z-ws`aV|0?R`u_V09R)t~dfk}(oIxjPLTdUG;hOX8JMVKL%K_J>(}&E-T)iC)4<&GO z<_{nD;+D^x`E~8Ok7Bb;oDDl!hd;uZOAR^yH~o&B8bOjs11FeBng8FLE?x)rFYoHU zim9G2eRc8Mhrp2wFX&og$N4?De~(`eza~R*S^5nT85WRP(Ti}T&m?Cg*AzbJ_gXU| z^xSQ9rd+3|M+VxD>#RJqBkkxFhq2KxFnZo*A*t(1d0L=^5IHws_rCyY>TWik!3ns( z+Y7FL26dL{fKrrLOnLL~O_67`yt+DK9sJe?eAmgZKz}cEA`zI=oR<`t5(-QJ!bPZe zQxY(aX>Ly27Ay7kUM>4O48vyXY8<@y$Ac~CFNpWI>j(*1n@{Gh?_`k>n6>=ukyb0* ze9a~rX8`Z%NNOHNs4T1*gIc=dm!5V>deW&;VD8kPfEGFBM=`Ee4Kvv2c}r7Txx?m@ z*Q(xrY2GCpasTXP9n(_ishzMQZ@9i4W#qEiApfR7T|0QU*N?#RB&nsphcI={+4^&w zIoLAP-bMGZ=nF9F?wt+)h`ZN$&R>`qzjTerTi9vZkN!?=9(Dv264d1TA2luPEvg+W zB0{9kd8W?If0bgdYOhl#$5PSYc9xR0zUVeXQ0fOsC(0w>Jejs6H#^0`oFeyUr=|g< zZI|*A0dbd-RKH)4pQ`lau4KBM(PtX`dle7XdMQAgnId`=+&k_N!;S5nSO`ae-jBav zRL8r&|6J|%ugc+Fp2VF$D|N$M6_&Nqt@|>EA3h~G;H~&=33<7eFE{Bs6W7_l0Nxn+ z$f{67xo_!Kh)B~<*E_b$w~o4Zx3&B1+s0D>(N1ghDNMFnCuhW$bW4uxM1Dby&GCM3 z)I>5&Ip`OhVtu0?N`4uuZdm;o^stxiVBKY!$zXVflT6O_z`2;DwKr=MN}KBa5)GZ8 z3KdMNGSr};{R#Zze8`q`?euHfi7b7V7ISVY!|Of+@LyqZIyA@?dCi_%IL56WB~+Al z+1-|sc0;9?c(fe7vA`@{d!lX>IebF(nI5eU{iuO}9ekT)Sm@>PmZaiz(yHMayfPZ# zs#v;U)a}*qgek$Ht+LkemXSqHXHAdNG8uS#P)8n~CVN7dRk&*I8k<-fj7RVQ zvfNqI>D!tz|BclckXT%$hO2VIQa!ZT8{Gac&WH%^^+~ykou5eN}s5SEdh8O7A${@PCLa#DJuiOx=p0=Q-^ab5~U;$ z^vcoB;J!H+%Fj#6c?RlPlPUDi{F>AF(`y_mXI;qZzI=Ml?Z2LzEleIi4i)ffz^#$n zq4|!qt`sRIRATV<`&MO9<1!Mci{ z_h~=E44e7JDxwfMtEUQQU*rQueq$9dw8R!4t=C!Tm1kd8x+wiq@=b!<6+>D>IjdY` zyoCBK+sHzFp!1Fulp`TbA!`}*kLNz{vi$O+jK(s$AfHPi4+c>Z$m@!jch$nbHh#)mOSUt`5{>iEZy9i?xu~Q(M&>d%~}_-qv&1X?8y~ zp;ua08Fh>uwYni;y~&Z3k+&cBhjCJ`{fBn2X1*orLp_p(7!68FG=k&`w&fIKL$z zH+lhRT;JMABNfsqZx~XqjgG{lH5AK@5il}hOHU<#4n9B3M$}dkHBT!@+FWMc2R=>s z`ymcwDOhMdEo^mseP`iC%T`H{2xSP098p|h zIm=69#kHkWD*w=!78+CYfhm7LsSc#29ky~TkWWwls1J%dd{jM(*`0 zT~t15eRalh)=SBTE6QSsBR26kDDkm27?m=nx?ZJ6RJ33zS;=KIW`K}tuZUccySszm z_mibWKwPn{eu`|;0{p-?W<#&^Xvz@*yK{0V|9PwNi%1G_N3*%&{7sU^HEjNg5{~Qn zw~3>z_XbDOR-n^n_6B*~1=hrw=zQW#pp1H}E)TQqU}w-W;B#32_!fpopLg=0{5YxewD`o@`JCKtolDL-}r!L?&U;um2bk%6Xyi+iAP z=~ylyd)n1fRX>W0K5ITy&&146)ET(9JeS4{#VP-I5s3Op-Q z>j3`wztgo|HD=LwEL$jg+A}3yXcN7ncA?UHb(&lDB(+&FpAc|c9@HPtt7it9r`Js4 z^%~ZN)5midWwPwdKYn*IX3FS%MlNa5@n)-80Gb(~%b+8*xnY#Bs~NjATkIXa^qC3=CxL}2#ti_!O!Nk1A3rq2TqLsc^VX7CBr zKn>_~?P5>K)urPVcB4kw*@~4;!!fgK=e<75LB_!G5Ad92hrHiAi^!Mf<(c&T)!Ezl z-Yp}*hXXn6^joIl$Gzo(WgQb;6M>xGwWOI`(Qgdb$-6H zGF{vswH!@jKQ(DFt|q6~sL9_ic6j|Y=T!us*oMsI=5y!qn}Mk3UBG6#n{_JFiC9F* z=aO#tz!lAsd`({^t#(3VEpD$znNW`Q)-+yL@56$JbYbIq4~nKhb$bg#zrDl<-lQ;L zGrNc>uOpK2uP^~x0iX(%xc|Y;cup3K6@7m)j8ada%l1&BVL1WEoW^~;WViQ!j9A73 zl=p7aKpAses;xhX4aZFDF4Qo9N9mll99?@GHZ_2@DkW)ik%3z^aJe;iP359b`5wdm zKyyTUR+Xek0oD}&3N#n|i+q%7Z_cU>VPi6{dDB5&*A`7G*o5CVt{Od%mii}HZX2sz zYn%tC?A+mgyPA8jQBm?r4$c6f;;;~s#p!fPw$~k2A&*#})T6}?9I)e4t^UIp4%3eP*!SbzE)QMf*;rpN!WeKqw}M_ja3pGWI>n>9W~!@WCh37?Rh#X!LocZy zA%a_n;XHPxdDvo>_cP4qGnn!Sr7+Mm+u`w5br_a!70D#qU!KsqHp!pjdB>i6T4c%g z^#m_!tA@+Z&M`_t5hl=aJmQeeICbskLe|$WaovpPV@_PytqYevI-e7ZJ^A)}1Uzn; zJFB6UI+<)=Tdu|uQ?s%PX`8f#Jo5kB;rLA9;2o&2p~(X70RBcwPS+3D@7KGm8oZO zUj2`ghKo$6+M*WN0s>-ktvcI(oTC^Ne|sk3Q+};oMQiH^>L&*)kX>6={ILoc4Erd; zRvF`qFAA~Wxv^lF(khkf4`yp6vzwkMq=mw150<+1CNF@>J>oM7P;WcJpRhW6E>4FKtu`N)x=T^ets~eTg>3Z>fg3xYX-QKik)z6)E%E+BtF|VJs9gtFo z#mx~>@TW?!SIcsuT!Pr5M!}=#I3C^J9i;!W_&e|5%3~=6@i_5TazZ_0S?k(N`vQ%T zd6Ilu{pJ`M)NMds;h??QMS9ETXd;mcgn{t&VDUjGgopo zGbXu(>R)BvBuTfUBRyQXK!HNNynM`*EJTOu`J{pEnC=N3dHpSe@y?hU8d@yJ3(CSe zu*<8fVoR_N8<{bSjrvNfU4$!|S+VIxWavosU6Sh((>eDNdzNW5QFE)W3f!~#!D+z@ z*Ob;Wz0;6M2=e6)NvS~8-f(hKap~#ffgBO<_3F=)eyxWoTz3lz5Ck=eMK6$2Y!fJf zL|WA9{=o#^KJK=LApE!*uUvW*tgcm5gC;Q43Vqv|qEVW#2uPdV73E19wVBc)n z!efR$TB}FAn}7*ydA$3`B{AAg8V;g)B<2^p|3TYx8?fsd31G{CCeh2lNBQnIGJX91!nJmN#k% zSoJ%-)kzCY`A~<)hvnqBkIr9{w@dx9jjIU!*YX|Xd6EgFpru?Dvso>tNzJh zpkr)Es2N8URX?H5I?#RKi|Ydrp?ZHj{>0 zWSh1`y@uGraJW^NHL*xHHUJ91_^_tP(+b(>P51Dj^P%yW%2e}C5~LCBO|EHGeUHX` zT^bmDOK+f?RGGkLp}d8$c#km&@4;IO=DSJYuuLg&#`wT5`~q-ECX-OmhN-^4*`mn~ z;)GJ*xd=GXs9SuU9tG-6rhjU*X9DYA^#rsCre&;KmEVPxNPkj25Nu;jE^05PW4x#W zrBH51&S(lLFO5|Sld^JQ?Y1aES6wZBRXCS%lriVjk~xzKWI1<+r?3g?jZGI26vA;z z8u(*l(A9Tot<=NePVG9Y9*t&CvP;S{nC61by1x%=h|Q4KB>JlSh?>zap)87g0bqm!Z7tQR<-lLuZ>lHxXDuQ{FhtybK97(F10N6b{k*S5 zNmq<1+^~0et_bnU%FwL;5YVY4{ToNbOKMvHSiRxL76z{%JIP!{iC#fRT&>&|A#(N@&E`FDKov(en-zy51+EB`FgD)mETy9R3nvd z^y5}|_54iuh0|>V=SE7j%Mb8tA~$35zwsmrnbQ$O^{D*(tCEs0w^2%wvU{Eh6f4F0 zQzh3V4{RsmX4$FjTo!88ujz>e;s0mY?QIrALoSMT!X;c~eyf+!nnTLeC{L>~53P)L@1$9KZG4s)O9%ntQA}-@;k=C`KIP#uP zkbLeqABNIh3)_i{g$6MT&yTeiO4;@E3w$ViG`1V#+2wdF8zpd@qN$5C5T%P28-7ey zW5`C~O79C`VEx=Tq|lP{U2T)2vH?fPZ)1eoe?jL^AUOcAC!MWuZk6xY3eD=2e`W@4 z{W7X^xFB_9C`}%sF?|B=5+%h)I0B^&`Dh5pn`P`BB_(GjqB zbXl1cjru^vrf^InL)9`Ke`SAYfTBWhdl7cK>Xy1X`whM!DYjaLaXCDkT#I-Z;Mj^W zr!128Bh@?K8ur;5E_|3w{9Z7)iXiVep_uPghC;hjgB8+g(kM(-9x9@=OXZt~K=@gc zXt(J{msyHt;F7iC+2#ua7=b|h-3kV$6F%{{id6B?f!whkamh^W6{TdVti=j76-nI` z!2cxsl*-MThKQJ3Y}q?_UkkqD_YgnYH6Br>wx8YKVbS`Dh7O94YGCRlpDL+b`pGDl z(Ia!OC^6_V!SW&HxJwyBJfCBk+zAnfCFD9L07fZ{MuIgG4uTvjAll)-9qahCdeD-Tv~G7&ZH|`1|2A0Y}|*QfRRX z`jdjkL$0;8Sk(fo4KVrch$HqX2swJZIeV>{x~4w3VDD!WEqS`Rj*wr*m`c?4U5k;cV*zm0k-rSwhgY^UPb#BiOW0zm7V0uz@0K zLQ;d+>8Oy*9#8@W2=TxuUl~Vrb(ooJH6m#PAJ15NDXOl=tU2qy0Mx~Dg{~#NWi{lg zYM+BK#|7oqu5vDadJ)wq(SJ7?WD>z6umIQEAE+}R?@{5~9ghug>(BLwPh6-Vofpx| zWd}q1xu?#t2Npyj!n)=!02G(*T8cWik_B%@U6Efy|bGfAIdF8`R{HS zN_)xj&j|G;e6_?f%*Nyei4Q+lLfL>+`&3&mfcO}Wgj?)$4&VWd7gkulza8urVF2X? z;jQ*Ep9-tLfm=7rS-~_+dwj86z^svO6GyZ-s8sGd4H!SYpB?-oC$qU_sQ!XV|K@It zDp;E}1yH(H$TzX<9oR&z;-s15>1fy0Cc6FJC%_emrgnW~MpGJS9v!`b`dceE~M}mn?;w>j0qUMT&))VO_&%w{1 zYAGpNcF$LOr}OmaExPvPoEDZw@Z^+#@Bx|adFf*rR^2!KfM-54v2jF`*er*gtDCuG z(!E_%Vn-6)b(xLxmt_gvA9AMiM?BJ3&1V@O zI^EH~qOmb*O3APcH2c;7XS>W5%lL#v87|F*gq7TZOrdO!%&d4$sk5Tjum*+PU7@lid`-DH|0tD;cdyG_HKk zghWN$?!TMv1D709pc@PoI902=d^h(Pri;#zJc)D3HFHLy5 zMI0fnfq*2$uT7j!s}1Sksz|$rs-b$WCVz<;l5E2h5;w0YyOi&UsH2fAkNZV=J!}1~ z?NDpNc$-b{7DwrrB(a;J_^TkF3ieQL8zxebGt}_Q4QCA%&ld)?WjU@-g;1bZ-%HCj(St7x|9)E@ejt;$xsJ z`FT6?KCBV8fmxBT=fb?17LnXCE%gKVz$k8F5Kzv(>w(!g&p)!?;z#stLVP*PVeV{R3hdWt7zxw7y#x~f|{pJ{H}DSFr6*?$g;**&%CqDE`a1j4V4 ziMBo`6t+?lhz#rHEJV|3leU4Sr<+#~t8F;A7A`;>^tFTE?}!_+#)x+h@vuzPHwIV+ zg|+MS>6t>{U{n2+={vP@aEw@=kdL~mZ@$mH&KN&OS4<5lkLzYJUyp>mL*SJQEbnVE z5Y*^CMfn^{9u7TFApBujWk0lF+&gCQc4XmbT>#hNGmWfbPpjQ z(yh`d(gM;gpcnt^-n-Ua>wb8@oNs5XbKbr8v!Cau;zN&@Z6OpqZQw=1YADCP^lXqM zE?oSsytZPs09I1%BoyABJSq<$(V3n_LiWRjQ=N8blJnh&lK0Zlvq+A>&?Wctnv1UU zR@w%~GHnUwodjrPmiEepWwb#&yd}j1oTgoVNnWmR!ZPphD z(2|L?39|dJ@!grPi6Z(0m|LMWJ9zg}zkgMu!jS5W)Cmd2TL_zcQn>EQ-&)oR+@gP#DR0y?!Monz{iTAk8c5VlW zedV#7JUNC4i?bZ7_E#p<*LRpTM>5wuX9_79zY>S|V(8#Gi&ba-Meya*K7A(l1X%X<% zjR3zdaB?Ol76Mn;$GNl$b$OW^Doh};^f z^-OK2g666`Azl0iwRk1jw0QR!+xt|%^wh%1iUwe%?O^$b&pAHFQ>*yB%-S(VuB{Qo zw$*`N%SU?TQTa{Ryi^Bo)j&aHqI06TL`+=#P1pUcQp(kt_q}vAFVr46#D84ahwVcgd*dE*WMt*kk(XMSi?D&eB z%~`*_%viYXxLh<+fJM6a@toC>}Tgl&dWH~D`7#*yOfi^l8b@1eRGuPp?(BxpDxviW+j)R7$X zsA#U1DF>IkQ{jPgeL#2BOsLZTS?vE0D#M3#rSa7O@=^3; zvfc_C29tPr!!clW@ELbn---#crd{I6Ir7rNtK3&tkPQO;iU_tF#_~uOsJ`qzMMWhx z6CG1>OIP{)i7fjIs7(PefX}ClS1TUYMhh(6Np;B80m9XfAByj|;Ps&wMk3w^dkiC= z_gkQ6Iw1k}1_Vcm${&@z^b1?73DcLX`2>EnZ2=^cw)Fl2-pZjZ&angb{_uyz z@OcOe7Z1JOrgOFBhbI|lz-@oVK!6=KzZ9akv?__Kqmxf%PVrfg{*>eW!hJ)P*kFJO z^fuAYj}drUYCY=Mbof&EQq@>KDl9znhA3V)aC_jV?243blvOma@>6Wj+-OM2qabCZ zGOghJ^BV)Y>}dI(T>XBIL5Fl})+@SX73#r}k9O~hO(OYkaod6xi$g_e>tth^!~f`k z-rd(#A<22fHp{GRM+KRLONJ4*H!_zDEt6t=80xu$L4w%w&B=ZjzX5p`;h69NfCPlT|~~P&fFT2N`kN z6oEsJAOX&CxSQq(;C92;6pa-KNwt$A-c&W{k59P3b2DSeidH^A!+C!;&;}C1w@rDg z8&leKQwcRumApl_`X{|A!b{%1Pds0~tZG>?UE&=k0C)s;kk3`5R#bL}ARH9po5p45 zKzE4<)LB>tD#h~PK>bhf#GtfNa#g_Dj#}nYgt^tG2acX@wO)w zZJE%Rwx(W8+X1h*SG3(($zMD?XQ8{894EiC&q@S z3mYUwN9TJr_u3t@X051ZD}<<8ny?9Orew`DzCD0hjyqOR)sNguYT=qeG+Hn>2 zdkX&94v)5eoUigo=FDFE2GgtNqFZ=_qr-O{aQh)yw0e|BD%DV7GGbJonn)hE#aYmX zN}@CJ(Z&(ilt$Vla4^%Iw*xjK>HqRmG2DTaMV5Jo+LvK}{3@?=cbhKteR}8=-4qk5 zTG4?Gsy)oQnYLpx*BiszJfROum=J7}y{_@|o3W12%{51UYHk=(dLF=50*d9VDfoZ; z+=omj=EHf!kg@vB^u_G5M0;R;ktgetT3X^+o4JV(2_BsUbnr5?=JBi8&CJ<%>~oy$ z^nwT5ckjaR$b=F3$$2Fcjhw9f&J~uuA;Aq*e!3<;T_QC;WvJ#RmBf0g0|^Api*6hw zHB@IjJbn!*E+g{fX9?1qvZ)5iY`y6kY@B!AoGm@?Svt(*ENEIw+x{sVr?h`1&9-23 z6@Pq{g*I&+7lb(%&-`HX_1)qK2yp0M1%A+`(vmaa??{{0^Pgk#-j``D^CL*=i+ZmA z&S<452qe5SdDu+z#Y#us=}GXL_)VXUxxo!nrS{ohPw#VdG?RKh6h0LY6BND3A&1BA)>E&0{Xze+2eMEt5~T^Xr+t|KhGS z=C;$v5Tulg5HjW@GNc~_6ubglPpf?d;@fvYTJscTT@9cOJtx zw03V`NaKy+OPaCaEuF9V9FI}uOgQL{SKNT`5WjG(;j$SgzN7#~O#ULDGtc>^ZWow( zyg;jf!T9YP0jMBa0+(d##&6n5aykL&YZ~q1`SKN$jZZvW$E}bjG`;m@wgcS3lIgS@ zTEk}DX+Y>2~m?rc~(tj@E-G`}p z?hV!Vi9O4pR2x#c9a;fZG}fA_rsWeH%T81sHsv9W8QnYSICAj@9V+~?yEkgZi!*pl z0v*4+R~`{p_pJQ{y%zTF4-C0LvS{7Z4eRjq2|Y~~9BE4OZTwNI9UgvMUI@|Hd4)*H zZwznfbiki#vHNDl0*>Hk%D}9i8lAFU`xFVy5n6i^;s3liJ%5t8r}UE_Hg2=vO65D^ zUQqYQz(cO1Vj?V>svxfd$SB$PyXaRBd<(qcl!VlmaAv!4=?INzU7sxsWt~=n)~0Pd zxF&J`Qw3GD{NN9D5Pr+GYv?6S=oyv^X>*X!X#P(6(Y3gWRTLbyLI~@)A>A?%)091+ zb%1^T!p|(bRuMS=XZx`6tC%!vX7nO0t!5-koH{aGnd#qfmk1d^IL>c4xsl zqs*gij51+TZeQ=J=_S6{cPt0im3L62VPL@q!Lt82m;sJ_b;CvcBwzp>sra+=1v;q5 zyi78Y2Vr+vxgL8T%dI)i%PtW2D0po1abfN6DeQ}Ke)&aacCg0%y6#>?nJL~1Tzo-R zqpP`j@cTmi@SFoXoq;#*S7*e~rh=~t1Gn~1oaZ8zy8b#@66ITXAZTC!x#qpB18ufS z=2_P`Cf~Pg;E`|D@0?dBvBp4U*w?~&A*i9|y`l$hr%MW#tHJKR@f_P6u`aTzFoFN2 z5;sB!09m!%4sRcWfo&V%Qs?w{UWg&1h1T+|p|gJ;{Qt=}Vw(^Dq1RM;XPY(M|6W#r%U|30|a z;v!PVf7IICmR`@$yi2x5hnRXCTm=g`;7OD8l)?p2Y0P4mxfKbvXrajVhYaAwgf?2) zI8cU$4*1p6;btvYb<&v(Sjr-=V&rNIJ;yq)*a=QwE}P!Beq3%ScT%owXh!bm$=_DS z+UT5^s8Y?1!O7S;1lFupeCo4?EO)YLC{8ITKNl9?Wq5U?lxF65u$9<+^5#Z=?B!Di zfM!mH4^v}tgo?PwiPV!9N zc21o>l0Z570PaR-%Ur+6*&8+(GUb-tz0j}J$MlH>&+@Eyxwg}q2LI(LL`B0}L6`V1 zK+Zu(hH`0oPOJtNTcEG2tE(Kc1Tc6qZRE=~+f4pYb$qFv9-7Z@%!(9RIElYFP&l=5 zX}|W(Nxd7}0bleQ)=e3~lrw(;k@ksw{%zrnbF6H(blBzA5?aSJ6B}QGX-_vAk$3i8 zVs^K2aa;DIv6k(@q37kbGgh6DmNsC!mMw&smDt=ekIdDfOor%Fp|1J)g17G4#Y6O4 z)o7_8*WLlBISA1pyjj8e3lK4Dy>fZGO3*Bwv0x7^QQ(uj!Qm}#*K#A!@Roqdv4;Kt zRB{A3dp88lsJSC8^P!+7Ia~`~S`q zTUdD6HCBqp`Ua`;oACSDqe0J()?L@=Z0-3n>(QS|4ieDxNH)lAcbJR_Xm|H?bKBZbM{ygo8Ohcpx^)*ZCZl7|8i!+TR-umKA*ZxtIHhbhFc(|(zYQx!*Sh5) zxt;+S$EM?aXG9N}9bwK{v)fQ(eEUoiq3IyZ%0H#Kd^`M$JHn+{m80sY+YzjQjgdA! z39!L%AB?~{Ibp;tDZ|Ng7Rr*)k9(^+$DrRZ*7^jSn72?Da))(UB7zuf<$dNmlXe~!bcK~zF z$s9#r^GYHjM4xR*Q|zb}Mk>({6i7O}5*uqZs)2LQc-pAyGYSg ztvf-EAbI7FS?=R1qMQeu;^{>IXI)w>kD|acVb;>+2Cs(8y{qMM$(x< zvcJz`TZ54rH@8%tCjjJPTbT1l3$`5aN@$1oUGvkP6B2rifo17F|zH_v~Z5voVDkw%wnT*3=U3pJV5 z*knOYEoEu}9s6ar452ar5)2dL%om)SEe!?wlS1#ojvCMH;Uyn{0K${xdlJKMa~xk| zry`!gyRH88JHQ&&lpw1dG}p5yRdP1GB@Av|5c-da1tRN-hGWsF{CyoA8jN1gbo~$C zpd*??vl+`mWwSq=n725+^ExS8w71Og_qX8U?zw=A8lDWwjs^`xO*6|MCZ!L~R(O$o zDXJ*8kcaCG@IUZHY6IhbB34=r&HRV27DpR0d{*V|H%A;-lu~H?S%w18;ARM-$h-OK zCP%Y#UR;ot5<4jH9w+iK7Wuccr=) z!hAEWg7fQ3#$<^B*ayw-o}dNvJUtiah_Oe>FK^ELJGGm98&+*fyj`4Kop!ShIH;@d+bPXJ{T;A$y894v7#-MGj#t+=G8SpMMhw zrQN}Kmi zTl$K?pswxN@+`Y@r+`i6F&Iq;m-=#XV|WLQVrA=&c~ zx09NUJNcC3LfBN5J8MERF-a-+TV4Hz;*CzvEq4`eotkO_#t?cy;d9g1%P&4PyBwlY zWhj=j1|l~2ed|3wOr{y(Q9jB6)oF0D*XhX-j&n(K?-bG+O9uRW#WwK?q@kQ|yV`kL z0?XZWIJPMS4a|fEaEXlPQ;y-#Pv>yX)3DbYa*WhLM7};xk=Hsa`DTg*o@sQ>_?1>X zJTWer!5HOzS80G5yO&uoA<|r=j!Ev+CIosE4SQ&-tMaaG3?%2uO8HE(Oi~pn8k18I zay6c%%bOo<^vGT$kwY@hj^z9kgsD%@dA7MtOpuzrn=%ft4;48^J^sXDH6wB@7SkH>LGtFj~p(mV13^t zX1&K55F;SfGh3$5^b(+K}_MMUDQ8Svz~}Fr)esMN(`m( z@hD}mfIX{uM3I<9x+xI_T5hRe(EM^MGx3h?Ya2@5NZuN&sa}x${i?rP=nk|6)k~yZ zSkBE@Vm7UlZ~UX=j7R5PD(e?0wW_Zw{4A^}U?x69#<%v}{zLR(G2>Oo^VfzD9ebTv zCZDtrncTbGC|%smxmGj!x0S%Pw~e%hX&zrjD5^he7R-q4s^Waq-W{4F$Q0?2sv~Qm zrXj$hBkdAh3VMeU$dJ0FI-A&O&+|#EyBqgY?42MtbsFpX8b1_uY&)Q@a-Zs-&Zfe( zpPrslw`^6kZMzgh2`WY}SWdME`ktnujUg0A`nzyxs^{?{E-s*XAfHX)iLUIA&6*d%VhP2yVWMeDtFw z==d7O{j=*6NPaMZ_jsTd6Tb>J!Htb&YWFke^DdNWR&ZBAjwM!~bE_sKhZ|hlIBJT) zC^5+k)o4t6|3Y3?R&!kownDH*2*Vj`K2w-D4jRE$Kl(I{SyKz;*YD5!B z_C&){#>{Vp--jj)Z?k#K{HjIrFV-gdIfxc;!LK#iq!);=i==qbHQvO7rE*w($zaDh zf(D>sOR1_5)jIldWpX)dL0%t5=zh)#xRU#dC0Uu4{HkQN5HE?UGA)l|_DM5wG5m6r zs3!+T29cM!#Pk}XyQ}yf5^>qFsA#F9#0`+*AXRwG7o=0}66(_9W*%EPQAA*jwX zbh8vV4PMg@KPW$xwD_9iX*CJ|^EGP%I1IPz$um&HvEdkc=B=n4Lt~p<#OMy8cL6@C15JnK2e1%*pM!db|@-*KEbdwygA+2G4Hg$O6|qx(iCz# zLL>|p%xvhjOBS^+qmFpXa#? zfri|+I7k}zLh+;C@%r(7zSuiR>Mk0*F>2YII`vkoH<6k8N;f$)Z|$Xz!TU^m&erai zePsl0P0~lKZwP4!2Yzs39i#pNV)JCfJuo)GQCbTSkPgO7hUcA$^@lT#QKUjdXC68( z7f2twf;j%dcwbiNaW0bFOq+eHatz~t>@wW7$Sza~UpU2fT~NsJD9bi2c75V4KCyzk z!Xo+(LQ|;%QHRs`J9>=wm}}Q07hC8u&Gs0n2WpMDo1gPGu16(!Se77*NRAAKIkAvV zLIQbyb4EX&&TT=EwKewtYXu+VwWg>L;KTqo$NRxf6ahf2kwjy2ybOSg9KlxqoAeu4 z_cK8=X(?NGtyMUhdg4P_jSh9hQXCDGVn0SsdoTp_Wh4n;`WNtXe=^muesA3LjO+38 z8^z{tVjH_4&^^dk#?>}k&FJ(zm-Od zJkDN=pa(wN(ua7bK9g!aiyhcAZYVxE#r)`v5(M_RS$7(+KtpsQ;@96p%_bLExSJ8a z2TRT9l3n|tZTgUN4l&jOrwG*1C*{Xt{#+-#agmt|Z%jfu9aP;IU8Ew)Y`7T|#cxVx z{msoO4$^dK2w**{ugPCr>6${eWq;g*DqTNm@8xqdInSXtwO3E#UG)u`n^2Yh&B6dBwBccAECm4e(zyBSI29ZD|MhMe?~?mY!4 z1BRb%P-ZT`qaVa&!Uj|&2)JETcGTX~m=cZ?K8Qi&D*@C?rMsAt6;i~) T2+~SOY(~3+ITu4@|1SI&Mo5x3 literal 0 HcmV?d00001 diff --git a/alfred/app/cache/weather_outfit_images/a0e631c42d8a4fc9dc3abad674a6625d32d9fe3b471d7a101a62c4a790a3926e.jpg b/alfred/app/cache/weather_outfit_images/a0e631c42d8a4fc9dc3abad674a6625d32d9fe3b471d7a101a62c4a790a3926e.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c5f8ea1e01e5f7673d4737dd8f857885077f1d2d GIT binary patch literal 25535 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
  • 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%0DE~FoN{S}56DTE%{_OS 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 E+2{r-eEpEaCdiiTYmp{ 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�kZ2d)Tv(>eN>mURdzy74Z(z?HK7WZH-~a<@~0 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 literal 0 HcmV?d00001 diff --git a/alfred/app/cache/weather_outfit_images/dd262938ddb64632659bedd83dd2d7e9f173b55663a3d155002468deee01a3b4.jpg b/alfred/app/cache/weather_outfit_images/dd262938ddb64632659bedd83dd2d7e9f173b55663a3d155002468deee01a3b4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..666ee97387b2bc29888e455f558853b9b91b58ef GIT binary patch literal 25497 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 literal 0 HcmV?d00001 diff --git a/alfred/app/data/alfred_features.sqlite3 b/alfred/app/data/alfred_features.sqlite3 index d9a60ec06f0757a709b2185626c5a618a4125d78..319d6e2b1b56886c4fffb8a89fbc27e88b862ddc 100644 GIT binary patch delta 101 zcmZo@U}|V!njp={F;T{ukz-@R5`8X4?%NF9x4Fvse{XC&%f0!wwI&N2BmZ><{_C5$ x9JcUJoFF;*y1%X%BmZLt{>S{;5Xko#0yj$qtOhEPW07ID=bWrBBe^I+0RWejAuIp@ delta 459 zcmZo@U}|V!njp={GEv5vk!54T5`8fyu7wQTx49Ow&tad#;mhI6bO;1DHnwxsH<>f> zn2IVly7M;HGuYcF=jWBAR+Q)(8YiYDrEe0vjQ7Bf=%uy&wRVc_wJiM+%!Ct{76)2mZsQ^@< zn+#&7Du8q+7765}7AvGyBqo=n=BDNWl}@&kld7kx73el7)fyQXndurD=o*=a7@As{ z7+Dz@Y8x0>85lq%grG8}=5U$Kx2-i<*qHeDG4Su(%;m6!Us8hElo4BKF-^YiuPesH tf0Tj$G5=BiP9V$%LJ-}#St?*PzmhDoJtIyhFv&36b57QmkzACZ007ukkU9VW diff --git a/alfred/app/data/alfred_features.sqlite3-shm b/alfred/app/data/alfred_features.sqlite3-shm index 89465ddf0b8f2ee097ea3c8476b49325c17cdb0c..b645afec686f4f8e2b7b009c59dc583e764612ad 100644 GIT binary patch delta 64 vcmZo@U}|V!njj&em+Ynclfy7+c|f1>ojKB$E$TUC+x8rrghy&)!h(7L8+jU| delta 64 vcmZo@U}|V!njj%jB){A-Yg>bLtz znwpt{1U&Q7@{4j4OEUBGoIy(RN;Fb66;jj_%knde6beceO4JiGQ&Nlc6rA$&@=_JD z@=J^I4zEmAP@KHMS}K-AJxaAk21aJOh6cJu79oZvR)!Wu`q~P0kN6gu74ID_JkekmDyr T;bYwHi(-*sw&$EIFCYm32kp4z literal 24752 zcmeI4?`tDP7{}8lUVms^s}%Y|y)nL6#iVSvNs|U76a?)-QO=h0;NDDjC++k$v+3+? zX(`9q7S!UsD~KZnYq_a`)^K(#-74 z^UP1dsp{Kmter2_S(}5!jwfKUN%je(UPU z_GqeFb$p@Ny2?+oLY-MNwq2|hbitgc`?5h5_r;t?SfI#iNR?z{nT3)C`je#4H1D!- zoiw-z6_mP+cqVPi=w6U-aFJ@VrE4ySJVzW;chsQL<-{%#-%)-Iy0k5OdSWxH zHkvG3Az5ucn>&1m&;O16><#Tke$vX@rG?32rC?WV`z6b&Sk}>sVyRoPUFvVMe05~& zW-rSrT6frVt>#X%V=UhQ7?ScJuR;QtS`H7GB#Hu0Km)LIBrv&06eCTKMxQBCC9g23 z^|_veyrchSM7O5W1LAC*I*K)zfFn;vc&26($XSyokc%tc%l3_28QU6rXYt~@qx032 zqQUk2OxY^Ul$gbeHK$OhIptcdOp9f!?3SjAR=J=LqJnxG0fR^j2=$oMQAZ&HeE_N7 zAp#wy0G|lCB21Aw&?XJQ7YZV2v*}B932IF$C0_%|G$7F(LLDb+xiA&&K71jfmJVmL zW1?*>w%|}nR-gwk1jp&#bswI|U6=%3=WteZW<~p=d_f%ARB+M|mx_SA<~JCb4FVo0 zIBh*lGw)3dxqh?RLjq_&MT- zgu77-Na-ld_4Hl`$AtC=EWk@&2t=E&8MW4;mS{KVTr^;8dcI%=1dr}XHW7r5!?tk_ePLZNf@fbX+M^mkgkUhvfWw=eq#AACCu zWohGX%J{~(Yy6b7>sGiS0VIF~kN^@u0!RP}AOR$R1dsp{Kmun@U_4#yOIln`AF1eB zDeZqDeP!rW>ww_<1$Yg{>jJ+I&p-L%Z{MXl>jL{J<5%Opv2W}e*J0m1-1m+B$2ZS> z)Mx++AOR$R1dsp{Kmter2_OL^fCP{L66g?kCSC38KJ!1WwMr+}#z$HJKn*;ZZN*0^9V8ndvPAY9_A5XC?WwQfCP{L5;zwEJJpOo(Qw=j?3TGHufjC$sfCP{L5dWTSQ~4t{!JdipU^J^qyPW_ diff --git a/alfred/app/data/timers.json b/alfred/app/data/timers.json index 940740e..ebe7a19 100644 --- a/alfred/app/data/timers.json +++ b/alfred/app/data/timers.json @@ -63,5 +63,15 @@ "created_at": 1793109993.4915156, "state": "finished", "finished_at": 1793110013.7477841 + }, + { + "id": "a0634d97d4", + "label": "Chronomètre", + "kind": "stopwatch", + "duration": null, + "elapsed": 3067.5718233585358, + "started_at": null, + "created_at": 1793117300.2246997, + "state": "cancelled" } ] \ No newline at end of file diff --git a/alfred/app/services/__pycache__/admin_console.cpython-313.pyc b/alfred/app/services/__pycache__/admin_console.cpython-313.pyc index 7a9b009724ef7cdcc2978f02c1fbbcbd6c3f5473..5e2ce281ef6bc86d77e1dcaa7a54c279dfa6984c 100644 GIT binary patch delta 6474 zcmZ`-2~eEJmHvA!jsa#s9AW_RBO$|Zj5v%C=p4a-us9@s(1qmT7xNE{n9KdoAcQ23 zTuvg{NqnT8jp8I_xl%4mu`8A{uB?;zP~s}DvyKzAvx=yHwXvNl??zR-E^I6DQQ6JD z*E0i>!~O-|y#MukuV26Je*OObV3EB0RbAdExw+W_JohfVcg(%-@x0aKM=#d=8!;Si z9Cow!+g4T9N_MGEs+St1M#&*O51J1F$3+C^wJK=>Cj0# z;ax{JOS_~lpa|U}b%W$q$z>CA=r*YbUbau_?R{~tuFnv&>*y-*QOi~%5w{YY^aYhP zD-&5$AvJObBOIbjj2x8k%fZR0Jh`N1MrOnpP(slq!Bxnc2nB=6#GP$1=sFi01M;j} zj(U6{dxn-Rjf}WmgS~x&F0F9?!J&b^!Cv?NU47CX*RXrnkTml5+&x3XBiiaO%l115 zdqi~&E%eOc%v2s1;L9j31HW0#z<&d32K?jY#8FNcvKzmMUis*X7`Nj_j%kC zD)rJZH2B%YoV{d!{EM77i79|)#@U;B+snDM)g7Tx6>PYva$i(YBlHwXuqw+rQ2CMN zby9&2(j0_50L>IoB2ys`H%(8mujQ{N2iWuZjpTl&2IYqZ zVT~|WnzlVc(kekc45geKhsl&GhKRH#Xc#p}l|~RA)vLO#y3S6p0Db{ik6e8B*aTsS zh_?qxOl#!{(m+*O7+%)48q?h=MA9t}aa5n?b0_N|=1aq0ZW}Jcv}j(JmgVo|j^qee zQdk;$^-7^%p9=k5JoGhbs|r~*l?;7hDuT7#%3Tm0`&b2^wK`N~bhS<2k(&NSoPLXA z*;slaU|prmY~W_n>!k@5T+Pc0OY5qI%l!e@OKobtJg_1sTgNI>uh5d#3mQj_tg5)k zd^fII5A0ZYcNUjt$|Fb)S2I#vX6)kZr-AKh;p|$BXl@(VS<5+fD43DY=?0)}ELOZK ze`BjI-IGGVoP)Q#TAZOr`XNVZ0bAOarC{KEzU-UQyrl?q@R2p$J~Fu`6~Io6$Jpky zj*^br7Os=rnOj%bncJ2%mLj;F>%bztZAMFBGgavIW2Jloci;pbaju}d^S1uFR_L#L z1s&J_+h0$rzh3Mwwrd65zT5h{XNCTDub|VckMHF%Wlur#SRP+V;`J#xOF`zv@I5nsk4Ju;a9_%0*6oa9F z*gYsUh^ib^#V`#;LK7js;-Iig1>fKPCJ51WK<$CTAuvD=)fRc4hy*biQbUTSwhR4|p%EdbFq0Nrm~^2@%Z?t&q3 zGa7i~5=H+VT-dI=iCOjLApp>ac@=K*^A~3=S_JSVBg1frQ$?IJh)P79%NA7yDk6`f zIyBNmw}L#q0B{q1n7hjrq{^fZ&Q~P>YGP8g4K+75Eo-t5Y@ZU9{d?IUYbq}%&$5AX zuaWkFh4@d)cMv1}A&~6ziY>9#nyIJH)!!}AQDAA=KGhdgBXV#;N!ANf$TdQLzObg3 zgC{f{RkSQ`)b9_-k%=iS%cpvj312|=YkD7y<2aZ1`a^O=GtrcuUkTDoXrL#sH4|&c zpd3)N%qaWz8pojBr0J)X6Z8peHB7^5&`c23qYBkbM?=w|N2MpBNi_k8<03rC5+y5H zqCojW--IS!C+sz{yvi%F_cF}qP9-wR7R=W9)%6K;{f&b1`HJpDfond;wWt?z8W#I? zLQejf{>46>ke&CKEs<4r!;=5#sfSO^mv6sPbluW>)zZtgyorLz`J746vhE^_Jt?)W zCA9@h;iIvKV%II=Rf~9$JTF}?iamGeTJ4UP+W@$Uor!|3`JApB)>Th*oj0<3nB|;t z@f1lqaFU<{YZ6(NDF@E)zF71^*`>1QDz01FuUguklOH;9zUP`{ z!v*ify-)jqK=YM%U*7k!^TmUgcfTCD((_jHFZ-@+e(TtL#lb|uq4}Ic3+)NZTG)so0tRTN6asLnJaU9en#_ znh)WY`DiN>S>g(D!?10Axdnia*_9~hna}CDGi--aqdv$D6@Purg2kGQ>NQLCg|z@T z3gUmNJfYk0*{uK(thP@C0x`k)-Q?eAcBQ&{HLrDZp!}4fFb)O1zDarwWZq+ct^Ntrxi74L z+F%+$r9rl@uA%S|Aa5D~<_wNVAgqz&^gR2Ay4ucPql^Rq-&bM3FXD&KD^=60N`$_I zTqECH^k0xR$>A_$U)X&f5B&}MtZqy0&$x`&A62L5JIq<%NM2*1`nK-(flog`_!R=* zPkQWF=MAg5z{0(=^rSbDD$gkJi8n((2Z{fR|Ec~{lKBjfki3HN|7_IjlGXkfY;i+_ z=>atSAY0q?T{6yIX=?5ZBI64H%n~wA#^lEyVy`~1!-h~-l{SeHf9DU!tOB4HSNJy zgX)h?LcA{ZmY4noJtWgK!GCAypW#;(0oeP!C2X-b)0SFu8L1C^It?c3;a`0_p1*68 z!BAqmY-0oc-6eSnIn#41Q^;9&VdL{#66Q_whE1Q{^06QE@2JTae%tOeO0~kg;H-t8 zM7~v8uTNAH#9FJgS)b@6(&o5f;2{H4c#rJ8pWMrGhdt(f;0oQ3aDbf}-fDwWLQt^~ zIV0RM3D(NUn+(5{8Q-O{4oDN1h$^7>i>l{2;){C6HEqP#{F; ztBi~_m@z=UufV3I7ucq;cDV8gkM*}afC~2_97Z^T;0CxUBIO5wY;_uk5CgE6!HvR) zV^wA2*!m&@Pvt*H%7juGgw-8xFmunX$o(Ue4~sDw$)gAp2p)w00MK&zRS9l%RrV_O zOwHhtA=|x*0=S?w(}dp#gV4yVW>RK=QfUD;V${@3fK>WRtqG$)1XpU>`eZQ&iG<%| zeCtRFc`&~E$g8?4Oo5Qgc;WaMdd$Op`~3XH-AaDQ>c*>Je)`9MY~{P|c7bepbLAya zRSUsZa?(q_@BdzKzIFPl#Xq0vr~Bg``MkdDDUk5rFZ=ObZyzxLydjA`wuu`L=>?vi9gma>q;ohvL zuh-o_)U}(RK=dt?;0c*;cpR5Ki!wC5e5_>$LllZm-y9@6y)rvNeb|1CS-tpe)97`Q zAIC#pMYsNORM#aPzzqoW0Te9)KEG8ua|TxE=TmsZH~K_ap@Tl74tCPWwBN*o@F070 zS|;b>wf=G)`GUEEGng7*4Q}W;12$m!Mm!3o{0P$WMv@ntuGui$PkHPHo)+k{&?6k+ z;joqgCq_Vqk`~JXQRJPsg!;+v2d6H4&ecW?;t!$;1+>Ch2%wq=Md0y6|V}%$H!qn=_h%P7sH5(P4pVbOvbxa zqpk+W1mS)S9p|UhM@Zo^w3A6dcs|>1-@(Gsvb!IKm*h?89;Eo#UqTA^I_2@sM;Bg3 zcnbk0{Pk$7E|aiZ(ec=I6yw1gMrvunYR2LI(8M%-22?ZyC%=i>k09`U@H>?M9X6Lk zfi&ZT%ErOBe)6bN;iyyL;G$S9(EX^!Q`9kh4I|*S>RulIDk^!dVkyE8 z8y=tAlRC(0nOg!OPt>n$qkjZB_!HDu;-Ai}CI=Qpfn=P`eIR$Cpnjpe75~J|3zZ!U zHGK;e9UoaTExKDmCebez2qf#QF~KJvlD#lA;fWu)!w%D<41w5md&^^zZO zt+X1jL8<|4lxhJ3(i*@fsSdDNYLV(Igb6FXO|nR9rG`3DYJ}e+ZIRYV0iXz7CpCd& ztJDnGCfx?OK4S5=rvD^%ThqrJYly82T_1ZFRr%YDka1t@mRQJN0qIZ011{G#~?I}ipDh5-DcX)_X&YTR_F(@8Z5 zHgpmAJ|E@wo${0t)k1Pmr(rq*3%l3@u0gUTZMuF%?7PuSFZ)5kW)GJxkPVvB!G=uL zyHcud&mmjMR6T~yEb~=H5cDRqpGZgVp^OY0D%Y#7T0<3=s`Ti!p8=LMG(l9r|jv|#W9k# zLJ*|L5Rt+vv#gAW%$Ctp7(ju}P!$MZDLBJYztlVRjEF(2M{wsO>SD`?j z{}#p7sK`DmuVA~DxE7SwTvw{&O7&c6?JY_Te78o<4{&}V_o4~;h0Xu#ssA?S_D+yl zcE2}7I@m?82a?a>t0muL%|3;NmwiU|vwcM~x}xln2fG=W4stVQTgLFNoQRrd5G zn&bezmvuI+vcHHjo7i1VCElljG;NtP{}^ezl1!@c5JV;vqA#)Mnl|J;$7RCNls-;h zW}h^zAt#xqxw+t%Xyz@1=Mm7MlQ z7K@naR~OiKTWjr6RFAWFTAw7nOlxbf??J`|cA~AM?r%u`9YD_l@MS&={V+)rW2!uE z7)g2=R^JoSAGKX0wHHw5uK=^Q9SLgqW}QAI7*us#8;h!^M0&#}{a9eR8|&IlixxNN zW>jkj@LOmQ>1Pqpm~H`ptuy=P$eDeOV3K|g3-dyHcH^A5=M0MT*q%lz8v*Z!@`nF1 zQfC4D#B}JYVQBG)u46m9Zg!D;Kvm%Vpm4Xb@Qj?IQTin;Ul!O<`;*Xq|Jhz{eIKl{ zL;5oIJBO2PUsROt?r6_ur+QYBBkT`7HRK+4t*5PK64cDx&W`O{hdSi${;sXN>Epnf z&8ZKmQ}t6?NKNqg(2oQ**1Ng<3d(24w4fYI!HWS2fnW5Rz|QqniC+oq!`>6*X!=NB z8zFnrKi^(QoHt|WVpsdyR(RkfbO}J_6*H8mX-(;1D<`5FP%;%oM(h~B*HPTwtSyPc5Kj7_-E80lk_M2drJ4RHwKrL zXVNiC9#@qpX!V2Ee?UNgJ$-F(oz+@W_QP4$x~nTUS0$GOi%lh8TvgcdUG3n}?^`-- zQkn3s(6I`>&XWSE#B#ovAeI$KHJ0#FrY^4*p8V49YMIZ+>XyFDu z!M+I9I`d&m?ORZ>^EOtmw!nlIQTrNskLgCO+Y#`YC+fN`_%g%&7 zMR;f(AD2uw!@j~Ugd50?bbjPju@XBMv>-kq8t73js&*jYLjl77(gGyODkmx-^qmuD z3V31MDnKUJlPR`watXw1Wbz4%I|6oQT|Lr3zfX(vF;cCgJctv_m1veUScg4vYqNmD zv4) zCPMo6rlR&tzOh$R3W=t@Qy!6AU>l~Vu$TR4y57Ybu3a}mP~CJhyF6WLe-B-JnB~p5 zA%FcdedL++)XWA_akJNrp#*oQ3H@khzn%33{*7MR=yBk{%#n)6!Q!*H{0zeP0d$<8 zA#>WnU9|8R^B$}v$64RO4_%la{r*DQg2FGd)fknc`TIegoI{A>brU4A9xr?{eXEoy`%3NfXCTv*UjI#-IEUW*t@I?i$RC0~B86$@ClNsSe6iWT!OW87 zP$DSHW{xa}Q$`B@?T{%x(KLm?|Djrs6hAwTIP`4<9_Fh^J&oSIh`@{4iaZ;8ZEm%A zQD7J6M(55UhevTEQaAF|v<>wof|K+?&^4``ybH}x1YQQOpgeC2yrGTYPjsIDPn0PQ zO!rJ3;M9_Nv%W+M(~<|I`qpxX71OaHmI$Sy>L&Uo$iSbVhtux6%ZT{tQugpYZhI>! Y{B)YI)Av*rtRpJ9GDQv&;^htFzf%|cEdT%j diff --git a/alfred/app/services/__pycache__/admin_extensions_v2.cpython-313.pyc b/alfred/app/services/__pycache__/admin_extensions_v2.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8742e592670eb4710c6bcf7586eab705d27705ac GIT binary patch literal 11150 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 LeMAG{P`mLz^8&Q& literal 0 HcmV?d00001 diff --git a/alfred/app/services/admin_console.py b/alfred/app/services/admin_console.py index eae961f..3b9716f 100644 --- a/alfred/app/services/admin_console.py +++ b/alfred/app/services/admin_console.py @@ -65,6 +65,28 @@ DEFAULTS = { } +# ALFRED_ADMIN_SURVEILLANCE_V2 : champs du moteur STT existant. +FIELDS.update({ + 'STT_ENGINE': ('general', 'stt_engine'), + 'WYOMING_WHISPER_HOST': ('general', 'stt_host'), + 'WYOMING_WHISPER_PORT': ('general', 'stt_port'), + 'WYOMING_WHISPER_LANGUAGE': ('general', 'stt_language'), + 'WYOMING_WHISPER_TIMEOUT': ('general', 'stt_timeout'), + 'WYOMING_WHISPER_CONNECT_TIMEOUT': ('general', 'stt_connect_timeout'), + 'STT_MAX_AUDIO_SECONDS': ('general', 'stt_max_audio'), +}) +DEFAULTS.update({ + 'STT_ENGINE': 'vosk', + 'WYOMING_WHISPER_HOST': '192.168.1.13', + 'WYOMING_WHISPER_PORT': '10300', + 'WYOMING_WHISPER_LANGUAGE': 'fr', + 'WYOMING_WHISPER_TIMEOUT': '20', + 'WYOMING_WHISPER_CONNECT_TIMEOUT': '3', + 'STT_MAX_AUDIO_SECONDS': '30', +}) +from services.admin_extensions_v2 import extend_health as _admin_v2_extend_health + + def _auth_config(): try: return json.loads(AUTH_FILE.read_text(encoding='utf-8')) @@ -124,7 +146,35 @@ def _validate(key, value): value = str(value).strip() if any(c in value for c in ('\r', '\n', '\x00', '"', "'", '\\', '$', '`')): raise ValueError(f'Caractères interdits pour {key}.') - if kind == 'text': + # ALFRED_ADMIN_SURVEILLANCE_V2: stricte validation des paramètres Whisper. + if kind == 'stt_engine': + if value not in ('vosk', 'wyoming'): + raise ValueError('Moteur STT attendu : vosk ou wyoming.') + elif kind == 'stt_host': + if not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9.\-]{0,252}', value): + raise ValueError('Hôte Whisper invalide : indiquer une IP ou un nom DNS, sans protocole.') + elif kind == 'stt_port': + if not value.isdecimal() or not 1 <= int(value) <= 65535: + raise ValueError('Port Whisper invalide (1 à 65535).') + value = str(int(value)) + elif kind == 'stt_language': + if not re.fullmatch(r'[a-z]{2}', value.lower()): + raise ValueError('Langue Whisper invalide (exemple : fr).') + value = value.lower() + elif kind in ('stt_timeout', 'stt_connect_timeout'): + try: + number = float(value) + except ValueError: + raise ValueError('Délai Whisper non numérique.') + bounds = (5, 90) if kind == 'stt_timeout' else (1, 15) + if not bounds[0] <= number <= bounds[1]: + raise ValueError(f'Délai Whisper entre {bounds[0]} et {bounds[1]} secondes.') + value = str(number).rstrip('0').rstrip('.') if '.' in str(number) else str(number) + elif kind == 'stt_max_audio': + if not value.isdecimal() or not 5 <= int(value) <= 60: + raise ValueError('Enregistrement entre 5 et 60 secondes.') + value = str(int(value)) + elif kind == 'text': if not 2 <= len(value) <= 30: raise ValueError('Le nom doit contenir entre 2 et 30 caractères.') elif kind == 'wake': @@ -299,10 +349,11 @@ def _health(wake, audio_settings): mem = {'total': info['MemTotal'], 'used': info['MemTotal'] - info.get('MemAvailable', 0)} except (OSError, ValueError): pass - return {'time': now, 'audio':audio, 'wake':wake_state, 'camera':camera, + result = {'time': now, 'audio':audio, 'wake':wake_state, 'camera':camera, 'microphone':microphone, 'speaker':loudspeaker, 'services':services, 'mcps':mcps, 'system':{'disk_total':disk.total,'disk_used':disk.used, 'memory':mem, 'load_1m':round(os.getloadavg()[0], 2) if hasattr(os,'getloadavg') else None}} + return _admin_v2_extend_health(result, _read_config) def register_admin_console(app, wake, audio_settings): diff --git a/alfred/app/services/admin_extensions_v2.py b/alfred/app/services/admin_extensions_v2.py new file mode 100644 index 0000000..276403a --- /dev/null +++ b/alfred/app/services/admin_extensions_v2.py @@ -0,0 +1,154 @@ +"""Additive health information for Alfred admin console V2. + +All checks are read-only. No model download, subprocess of Hermes, or microphone +stream is triggered by the monitoring endpoint. +""" +from __future__ import annotations + +import json +import os +import socket +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +MAX_WYOMING_INFO = 65536 +_WHISPER_CACHE = {'key': None, 'at': 0.0, 'result': None} + + +def _describe_whisper(host: str, port: int, timeout: float = 1.8) -> dict: + """Check actual Wyoming info/ASR support; *not* a transcription test.""" + start = time.monotonic() + try: + with socket.create_connection((host, port), timeout=timeout) as sock: + sock.settimeout(timeout) + sock.sendall(b'{"type":"describe"}\n') + with sock.makefile('rb') as stream: + header = None + # Other Wyoming events may precede info; never wait indefinitely. + for _ in range(4): + line = stream.readline(4096) + if not line: + break + event = json.loads(line.decode('utf-8')) + if event.get('type') == 'info': + header = event + break + if header is None: + raise ValueError('Réponse info Wyoming absente') + length = int(header.get('data_length', 0)) + if length <= 0 or length > MAX_WYOMING_INFO: + raise ValueError('Taille de réponse Wyoming invalide') + payload = stream.read(length) + if len(payload) != length: + raise ValueError('Réponse Wyoming incomplète') + info = json.loads(payload.decode('utf-8')) + asrs = [x for x in info.get('asr', []) if isinstance(x, dict) and x.get('installed')] + if not asrs: + return {'state': 'warning', 'detail': 'Wyoming accessible, aucun moteur ASR installé', + 'latency_ms': round((time.monotonic() - start) * 1000)} + asr = asrs[0] + models = [x.get('name') for x in asr.get('models', []) + if isinstance(x, dict) and x.get('installed') and x.get('name')] + model = ', '.join(models[:3]) or 'non indiqué' + name = asr.get('name', 'ASR') + return {'state': 'ok', 'detail': f'{name} · modèle {model} · protocole Wyoming', + 'engine': name, 'model': model, + 'streaming': bool(asr.get('supports_transcript_streaming')), + 'latency_ms': round((time.monotonic() - start) * 1000)} + except (OSError, ValueError, TypeError, UnicodeError, json.JSONDecodeError): + return {'state': 'down', 'detail': f'Pas de réponse ASR Wyoming sur {host}:{port}'} + + +def _whisper_status(host: str, port: int) -> dict: + key = (host, port) + now = time.monotonic() + if _WHISPER_CACHE['key'] == key and (now - _WHISPER_CACHE['at']) < 10: + return dict(_WHISPER_CACHE['result']) + result = _describe_whisper(host, port) + _WHISPER_CACHE.update(key=key, at=now, result=dict(result)) + return result + + +def _timer_snapshot() -> dict: + """Read existing timer store without calling its mutating methods.""" + installed = (ROOT / 'services' / 'alfred_timers.py').is_file() + store_path = Path(os.environ.get('ALFRED_TIMERS_DB', str(ROOT / 'data' / 'timers.json'))) + if not installed: + return {'installed': False, 'state': 'unknown', 'detail': 'Module minuteurs non installé', 'items': []} + if not store_path.is_file(): + return {'installed': True, 'state': 'ok', 'detail': 'Module présent, aucun minuteur enregistré', 'items': []} + try: + if store_path.stat().st_size > 1024 * 1024: + raise ValueError('Fichier minuteurs trop volumineux') + data = json.loads(store_path.read_text(encoding='utf-8')) + if not isinstance(data, list): + raise ValueError('Format minuteurs inattendu') + now = time.time() + items = [] + for item in data: + if not isinstance(item, dict) or item.get('state') not in ('running', 'paused'): + continue + duration = item.get('duration') + elapsed = max(0, float(item.get('elapsed', 0))) + if item.get('state') == 'running': + elapsed += max(0, now - float(item['started_at'])) + remaining = max(0, float(duration) - elapsed) if duration is not None else None + # A timer whose deadline passed is omitted even if its worker has not + # yet persisted the finished state. + if remaining == 0 and item.get('state') == 'running' and duration is not None: + continue + items.append({'label': str(item.get('label', 'Minuteur'))[:80], + 'kind': item.get('kind') if item.get('kind') in ('timer', 'stopwatch') else 'timer', + 'state': item['state'], 'remaining': round(remaining, 1) if remaining is not None else None, + 'elapsed': round(elapsed, 1), 'duration': duration}) + return {'installed': True, 'state': 'ok', 'detail': f'{len(items)} en cours ou en pause', + 'items': items[:20]} + except (OSError, ValueError, TypeError, KeyError, OverflowError): + return {'installed': True, 'state': 'warning', 'detail': 'Stockage minuteurs illisible', 'items': []} + + +def extend_health(data: dict, read_config=None) -> dict: + """Enrich existing _health() JSON. The base implementation is left intact.""" + try: + saved = read_config() if callable(read_config) else {} + except (ValueError, OSError): + saved = {} + configured = str(saved.get('STT_ENGINE', os.getenv('STT_ENGINE', 'vosk'))).lower() + runtime = os.getenv('STT_ENGINE', 'vosk').lower() + runtime = 'wyoming' if runtime in ('whisper', 'wyoming_whisper', 'wyoming') else 'vosk' + host = os.getenv('WYOMING_WHISPER_HOST', '192.168.1.13') + try: + port = int(os.getenv('WYOMING_WHISPER_PORT', '10300')) + if not 1 <= port <= 65535: + raise ValueError('Port invalide') + except (ValueError, TypeError): + port = 10300 + path = ROOT / 'services' / 'wyoming_stt.py' + installed = path.is_file() + probe = _whisper_status(host, port) if installed else { + 'state': 'unknown', 'detail': 'Intégration Wyoming STT non installée'} + if configured != runtime: + state = 'warning' + detail = f'{runtime.upper()} actif ; {configured.upper()} sélectionné (redémarrage nécessaire)' + else: + state = 'ok' if runtime == 'vosk' or (installed and probe['state'] == 'ok') else 'warning' + detail = (f'{runtime.upper()} actif' + (' · retour Vosk si indisponible' if runtime == 'wyoming' else '')) + data['stt'] = {'state': state, 'active_engine': runtime, 'configured_engine': configured, + 'detail': detail, 'host': host, 'port': port, + 'language': os.getenv('WYOMING_WHISPER_LANGUAGE', 'fr'), + 'whisper': probe, 'vosk_fallback': runtime == 'wyoming'} + data.setdefault('services', {})['wyoming_whisper'] = probe + timer = _timer_snapshot() + data['timers'] = timer + data['services']['alfred_timers'] = {'state': timer['state'], 'detail': timer['detail'] + ' (lecture locale)'} + mcps = data.setdefault('mcps', {}) + for slug, label in [('MCP_alfred_timers', 'Alfred Timers'), ('MCP_autres', 'Alfred Autres')]: + found = (Path('/opt/hermes') / slug).is_dir() + mcps[slug] = { + 'state': 'ok' if found else 'unknown', + 'label': label, + 'detail': 'Dossier présent · test MCP non exécuté' if found else 'Dossier absent', + } + data['maintenance'] = {'web_restart_enabled': os.getenv('ALFRED_ALLOW_WEB_RESTART', 'false').lower() in ('true', '1', 'yes')} + return data diff --git a/alfred/app/static/css/alfred-admin-v2.css b/alfred/app/static/css/alfred-admin-v2.css new file mode 100644 index 0000000..5857546 --- /dev/null +++ b/alfred/app/static/css/alfred-admin-v2.css @@ -0,0 +1,17 @@ +/* Alfred Configuration & Surveillance V2: additive styles only */ +.admin-v2-stt-panel {margin-top:18px} +.admin-v2-form-grid {display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 20px} +.admin-v2-form-grid .field {min-width:0} +.admin-v2-help {line-height:1.6;color:#a7c0d0;font-size:.82rem;background:#ffffff08;border:1px solid #ffffff18;padding:13px 15px;border-radius:11px;margin:1px 0 0} +.admin-v2-help code,.admin-v2-monitor code,.admin-v2-maintenance code{font-size:.84em;color:#bde0fc} +.admin-v2-monitor {margin:17px 0} +.admin-v2-summary {display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px;border-bottom:1px solid #ffffff16;padding:10px 0} +.admin-v2-summary strong {font-size:.85rem;color:#eaf4fd;overflow-wrap:anywhere} +.admin-v2-monitor p.microtext {line-height:1.6;margin:9px 0 18px} +.admin-v2-monitor .info-note{margin-top:12px} +.admin-v2-timer-list{max-height:200px;overflow:auto;margin-top:8px} +.admin-v2-timer-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 0;border-bottom:1px solid #ffffff13;font-size:.82rem} +.admin-v2-timer-row span{color:#adc5d7;text-align:right} +.admin-v2-maintenance{margin-bottom:17px} +.admin-v2-maintenance p{line-height:1.65} +@media(max-width:720px){.admin-v2-form-grid{grid-template-columns:1fr}.admin-v2-summary{align-items:flex-start}} diff --git a/alfred/app/static/js/alfred-admin-v2.js b/alfred/app/static/js/alfred-admin-v2.js new file mode 100644 index 0000000..4f1f002 --- /dev/null +++ b/alfred/app/static/js/alfred-admin-v2.js @@ -0,0 +1,49 @@ +/* Alfred V2 monitoring extension: no credentials, no write calls. */ +(() => { + 'use strict'; + const byId = id => document.getElementById(id); + const text = (id, value) => { const el = byId(id); if (el) el.textContent = String(value ?? '—'); }; + const pill = (id, state) => { + const el = byId(id); if (!el) return; + const states = {ok:'Disponible', warning:'Attention', down:'Indisponible', unknown:'Non vérifié'}; + el.dataset.state = state || 'unknown'; + el.textContent = states[state] || 'Non vérifié'; + }; + const duration = seconds => { + const n = Math.max(0, Math.floor(Number(seconds) || 0)); + if (n >= 3600) return `${Math.floor(n/3600)} h ${String(Math.floor((n%3600)/60)).padStart(2,'0')} min`; + return `${Math.floor(n/60)} min ${String(n%60).padStart(2,'0')} s`; + }; + window.alfredAdminV2Health = data => { + if (document.body.dataset.page !== 'surveillance') return; + const stt = data.stt || {}, whisper = stt.whisper || {}, timers = data.timers || {}; + text('v2SttActive', stt.active_engine === 'wyoming' ? 'Whisper distant' : 'Vosk local'); + text('v2SttDetail', stt.detail || 'État indisponible'); + pill('v2SttState', stt.state); + text('v2WhisperAddress', stt.host && stt.port ? `${stt.host}:${stt.port}` : 'Non configuré'); + text('v2WhisperDetail', whisper.detail || 'Service non vérifié'); + pill('v2WhisperState', whisper.state); + text('v2TimerCount', `${(timers.items || []).length} actif(s)`); + pill('v2TimerState', timers.state); + const list = byId('v2TimerList'); + if (list) { + list.replaceChildren(); + const items = timers.items || []; + if (!items.length) { + const p=document.createElement('p');p.className='muted microtext'; + p.textContent=timers.detail || 'Aucun minuteur actif'; list.appendChild(p); + } + items.forEach(item => { + const row=document.createElement('div'); row.className='admin-v2-timer-row'; + const name=document.createElement('strong'); name.textContent=item.label || 'Minuteur'; + const val=document.createElement('span'); + val.textContent=item.state === 'paused' ? 'En pause' : + (item.remaining == null ? duration(item.elapsed || 0) + ' écoulées' : duration(item.remaining) + ' restantes'); + row.append(name,val); list.appendChild(row); + }); + } + const allowed = Boolean(data.maintenance?.web_restart_enabled); + text('v2RestartLabel', allowed ? 'Autorisé' : 'Désactivé'); + pill('v2RestartState', allowed ? 'ok' : 'warning'); + }; +})(); diff --git a/alfred/app/static/js/alfred-admin.js b/alfred/app/static/js/alfred-admin.js index eed0c22..bf600d0 100644 --- a/alfred/app/static/js/alfred-admin.js +++ b/alfred/app/static/js/alfred-admin.js @@ -78,7 +78,7 @@ serviceStatus($('wakeStatus'), {state:!wake.running?'down':wake.paused?'warning':'ok'}); safeText('wakeLabel', `Vosk · ${wake.word || 'alfred'}`); safeText('wakeDetail', wake.error || (wake.paused ? 'Détection en pause' : 'Détection active')); - const labels = {alfred:'Alfred / Flask', hermes_cli:'Hermes Agent (CLI)', hermes_dashboard:'Hermes WebUI', comfyui:'ComfyUI', ollama:'Ollama', home_assistant:'Home Assistant', wyoming_piper:'Wyoming Piper'}; + const labels = {alfred:'Alfred / Flask', hermes_cli:'Hermes Agent (CLI)', hermes_dashboard:'Hermes WebUI', comfyui:'ComfyUI', ollama:'Ollama', home_assistant:'Home Assistant', wyoming_piper:'Wyoming Piper', wyoming_whisper:'Whisper (STT)', alfred_timers:'Minuteurs Alfred'}; const list = $('serviceList'); list.replaceChildren(); for (const [key,service] of Object.entries(data.services || {})) { const row = document.createElement('div'); row.className='service-row'; @@ -108,7 +108,11 @@ const p=document.createElement('p');p.textContent=mcp.detail; card.append(title,status,p);mcps.append(card); } + if (typeof window.alfredAdminV2Health === 'function') { + window.alfredAdminV2Health(data); + } } + // ALFRED_ADMIN_SURVEILLANCE_V2: monitoring hook let inFlight = false; async function refreshHealth() { if (inFlight || document.hidden) return; diff --git a/alfred/app/templates/config.html b/alfred/app/templates/config.html index 9b49a47..e61a605 100644 --- a/alfred/app/templates/config.html +++ b/alfred/app/templates/config.html @@ -5,6 +5,9 @@ Alfred · Configuration + + + + + +
    +
    ◉

    Reconnaissance vocale (STT)

    Vosk local ou Whisper Wyoming distant · configuration effective après redémarrage

    +
    + + Le mot d’activation et le texte provisoire restent gérés par Vosk. Whisper améliore la transcription finale envoyée à Hermes.
    +
    +
    Adresse IP ou nom DNS, sans http://
    +
    +
    +
    +
    +
    +
    +

    Le modèle Whisper (small actuellement) et son vocabulaire technique (--initial-prompt pour Proxmox, Wazuh…) se configurent dans le conteneur Docker distant, pas sur Alfred. En cas de panne Whisper, retour automatique à Vosk si le correctif STT V1 est installé.

    +

    Maintenance

    Redémarrer Alfred pour appliquer les paramètres enregistrés. Action protégée par mot de passe administrateur.

    Le bouton requiert une autorisation systemd explicitement activée.

    diff --git a/alfred/app/templates/surveillance.html b/alfred/app/templates/surveillance.html index fddc276..9b6f1f2 100644 --- a/alfred/app/templates/surveillance.html +++ b/alfred/app/templates/surveillance.html @@ -1,4 +1,7 @@ -Alfred · Surveillance +Alfred · Surveillance + + +
    A
    AlfredCentre de contrôle
    @@ -12,6 +15,28 @@
    ⇄

    Services et connexions

    Tests réels : HTTP, port TCP ou présence du binaire

    Chargement…

    ▤

    Système

    Ressources du mini-PC hébergeant Alfred

    Disque de l’application—

    —

    Mémoire RAM—

    —

    Charge système (1 min)—
    Les contrôles de caméra n'ouvrent pas le flux vidéo, afin de ne pas bloquer la reconnaissance faciale.
    + + +
    +
    +
    ◉

    Transcription vocale

    Mode réellement actif et disponibilité du serveur Wyoming

    +
    Mode STT——
    +

    Vérification…

    +
    Serveur Whisper——
    +

    Vérification…

    +
    Wyoming est interrogé avec describe sans lancer de transcription. Une connexion réussie ne garantit pas la qualité STT.
    +
    +
    +
    ◷

    Minuteurs et chronomètres

    État enregistré localement, indépendant des appels Hermes

    +
    Gestionnaire Alfred——
    +

    Chargement…

    +
    La présence d’un MCP dans la liste ci-dessous ne prouve pas qu’Hermes l’a appelé : consulter ses logs pour vérifier les outils exécutés.
    +
    +
    +
    ✧

    Maintenance et intégrations

    Vérifications complémentaires des nouveautés

    +
    Redémarrage depuis /config——
    +

    MCP Autres (heure/date) et Alfred Timers : la grille ci-dessous signale les dossiers détectés dans /opt/hermes. La validation fonctionnelle se fait avec hermes mcp test.

    +
    ⚙

    MCP Hermes détectés

    Présence des répertoires, pas un test de communication des outils MCP

    Chargement…