320 lines
23 KiB
JavaScript
320 lines
23 KiB
JavaScript
/* ALFRED_MAIN_VISUAL_V1 — accueil vivant, conservation des contrôleurs existants. */
|
||
(() => {
|
||
'use strict';
|
||
if (document.getElementById('alfredMainV1')) return;
|
||
const shell = document.querySelector('.alfred-shell');
|
||
if (!shell || !document.getElementById('stage') || !document.getElementById('content')) return;
|
||
|
||
const I = {
|
||
home:'<path d="m3 10 9-7 9 7v10a1 1 0 0 1-1 1h-5v-7H9v7H4a1 1 0 0 1-1-1z"/>',
|
||
bulb:'<path d="M9 18h6m-5 3h4m-6-7c-1-1.2-2-2.5-2-5a6 6 0 0 1 12 0c0 2.5-1 3.8-2 5-.8 1-1 1.9-1 2H9c0-.1-.2-1-1-2z"/>',
|
||
server:'<rect x="3" y="3" width="18" height="8" rx="2"/><rect x="3" y="13" width="18" height="8" rx="2"/><path d="M7 7h.01M7 17h.01M12 7h6M12 17h6"/>',
|
||
brain:'<path d="M12 18V5a3 3 0 0 0-5-2 3 3 0 0 0-3 4 4 4 0 0 0 0 7 3.5 3.5 0 0 0 3.3 5A3.5 3.5 0 0 0 12 18m0-13a3 3 0 0 1 5-2 3 3 0 0 1 3 4 4 4 0 0 1 0 7 3.5 3.5 0 0 1-3.3 5A3.5 3.5 0 0 1 12 18"/><path d="m8 10 4 2 4-2"/>',
|
||
media:'<rect x="3" y="4" width="18" height="16" rx="3"/><circle cx="8.5" cy="9" r="1.3"/><path d="m4 17 5-5 4 4 3-3 4 4"/>',
|
||
more:'<circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/>',
|
||
mic:'<rect x="9" y="2.5" width="6" height="13" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v4m-4 0h8"/>',
|
||
micOff:'<path d="M9 9v3a3 3 0 0 0 5.5 1.7M15 9V5.5a3 3 0 0 0-5.5-1.7M5 11a7 7 0 0 0 11.5 5.4M19 11a7 7 0 0 1-.5 2.6M12 18v4m-4 0h8M3 3l18 18"/>',
|
||
speaker:'<path d="M11 5 6 9H3v6h3l5 4V5ZM15 9a4 4 0 0 1 0 6M18 6a8 8 0 0 1 0 12"/>',
|
||
speakerOff:'<path d="M11 5 6 9H3v6h3l5 4V5ZM16 9l5 6m0-6-5 6"/>',
|
||
camera:'<rect x="2" y="5" width="14" height="14" rx="3"/><path d="m16 10 6-3v10l-6-3"/>',
|
||
settings:'<path d="M12 3v2m0 14v2M3 12h2m14 0h2M5.6 5.6 7 7m10 10 1.4 1.4M18.4 5.6 17 7M7 17l-1.4 1.4"/><circle cx="12" cy="12" r="4"/>',
|
||
calendar:'<rect x="3" y="5" width="18" height="16" rx="2"/><path d="M7 3v4m10-4v4M3 10h18"/>',
|
||
clock:'<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
|
||
cloud:'<path d="M7 18h11a4 4 0 0 0 0-8h-1a6 6 0 0 0-11-1 4.5 4.5 0 0 0 1 9Z"/>',
|
||
shield:'<path d="m12 2 8 3v6c0 6-3.7 9-8 11-4.3-2-8-5-8-11V5z"/><path d="m9 12 2 2 4-4"/>',
|
||
arrow:'<path d="m9 5 7 7-7 7"/>',
|
||
chat:'<path d="M5 5h14a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H9l-5 3v-3a2 2 0 0 1-1-2V7a2 2 0 0 1 2-2z"/>',
|
||
close:'<path d="M5 5l14 14M19 5 5 19"/>',
|
||
sun:'<circle cx="12" cy="12" r="4"/><path d="M12 1v3m0 16v3M1 12h3m16 0h3M4 4l2 2m12 12 2 2m0-16-2 2M6 18l-2 2"/>',
|
||
spark:'<path d="m12 2 2.5 7.5L22 12l-7.5 2.5L12 22l-2.5-7.5L2 12l7.5-2.5z"/>',
|
||
};
|
||
const icon = (name, extra = '') => `<svg class="a-icon ${extra}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${I[name] || I.more}</svg>`;
|
||
const initialName = (document.title || 'Alfred').trim();
|
||
const dashboard = document.createElement('div');
|
||
dashboard.id = 'alfredMainV1';
|
||
dashboard.className = 'alfred-main-v1';
|
||
dashboard.innerHTML = `
|
||
<header class="av-head">
|
||
<div class="av-brand"><div class="av-logo">A</div><div><strong id="avName"></strong><small>Assistant vocal</small></div></div>
|
||
<div class="av-header-right"><div class="av-time"><strong id="avTime">--:--</strong><span id="avDate">—</span></div>
|
||
<div class="av-head-actions">
|
||
<button type="button" class="av-round" id="avTopCamera" aria-label="Ouvrir la caméra" title="Caméra">${icon('camera')}</button>
|
||
<button type="button" class="av-round" id="avTopMic" aria-label="Couper le microphone" aria-pressed="false" title="Couper le micro">${icon('mic')}</button>
|
||
<button type="button" class="av-round" id="avTopSpeaker" aria-label="Couper la sortie audio" aria-pressed="false" title="Couper le son">${icon('speaker')}</button>
|
||
<button type="button" class="av-round" id="avTopSettings" aria-label="Réglages audio" title="Réglages audio">${icon('settings')}</button>
|
||
<a class="av-round" href="/config" aria-label="Configuration d'Alfred" title="Configuration">${icon('more')}</a>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<nav class="av-rail" aria-label="Navigation principale">
|
||
<button type="button" class="av-rail-link selected" data-av-action="home">${icon('home')}<span>Accueil</span></button>
|
||
<button type="button" class="av-rail-link" data-av-action="house">${icon('bulb')}<span>Maison</span></button>
|
||
<a class="av-rail-link" href="/surveillance">${icon('server')}<span>Système</span></a>
|
||
<a class="av-rail-link" href="/surveillance">${icon('brain')}<span>IA</span></a>
|
||
<button type="button" class="av-rail-link" data-av-action="media">${icon('media')}<span>Médias</span></button>
|
||
<button type="button" class="av-rail-link" data-av-action="more">${icon('more')}<span>Plus</span></button>
|
||
</nav>
|
||
<section class="av-summary" aria-label="État de la maison et des services">
|
||
<button class="av-stat-card" type="button" data-av-action="house"><div class="av-card-top"><span class="av-circle home">${icon('home')}</span><span><strong>Maison</strong><small id="avHomeSummary">Chargement…</small></span>${icon('arrow','av-chevron')}</div><div class="av-lines"><p>${icon('bulb')}<span id="avLights">Lumières : —</span></p><p>${icon('home')}<span id="avCovers">Volets : —</span></p><p>${icon('sun')}<span id="avIndoor">Température : —</span></p></div></button>
|
||
<a class="av-stat-card" href="/surveillance"><div class="av-card-top"><span class="av-circle system">${icon('server')}</span><span><strong>Système</strong><small>Mini-PC Alfred</small></span>${icon('arrow','av-chevron')}</div><div class="av-lines"><p><span class="av-mini-dot"></span><span id="avLoad">Charge : —</span></p><p><span class="av-mini-dot"></span><span id="avMemory">Mémoire : —</span></p><p><span class="av-mini-dot"></span><span id="avDisk">Stockage : —</span></p></div></a>
|
||
<a class="av-stat-card" href="/surveillance"><div class="av-card-top"><span class="av-circle ai">${icon('brain')}</span><span><strong>Services IA</strong><small id="avAiSummary">Statuts en cours…</small></span>${icon('arrow','av-chevron')}</div><div class="av-lines"><p><span class="av-mini-dot" id="avOllamaDot"></span><span id="avOllama">Ollama : —</span></p><p><span class="av-mini-dot" id="avHermesDot"></span><span id="avHermes">Hermes : —</span></p><p><span class="av-mini-dot" id="avComfyDot"></span><span id="avComfy">ComfyUI : —</span></p></div></a>
|
||
<button class="av-stat-card av-weather" type="button" data-av-action="weather"><div class="av-card-top"><span class="av-circle weather">${icon('cloud')}</span><span><strong>Météo</strong><small>Home Assistant</small></span>${icon('arrow','av-chevron')}</div><div class="av-weather-body"><span class="av-weather-icon" id="avWeatherIcon">☁️</span><div><strong id="avWeatherTemp">—</strong><span id="avWeatherCondition">Chargement…</span><small id="avWeatherRange"></small></div></div></button>
|
||
</section>
|
||
<section class="av-voice-hero" aria-label="Commande vocale Alfred">
|
||
<div class="av-voice-controls">
|
||
<button type="button" id="avMicButton" class="av-sound-control" aria-pressed="false">${icon('mic')}<strong>Micro</strong><small id="avMicLabel">Actif</small></button>
|
||
<div class="av-voice-center"><div class="av-mic-orb" id="avOrb">${icon('mic')}</div><strong id="avVoiceTitle">En veille</strong><p id="avVoiceSubtitle">Dites « Alfred » puis votre demande</p><span class="av-status-line"><span class="av-status-dot" id="avStatusDot"></span><span id="avStatusText">Prêt à vous écouter</span></span></div>
|
||
<button type="button" id="avSpeakerButton" class="av-sound-control" aria-pressed="false">${icon('speaker')}<strong>Sortie audio</strong><small id="avSpeakerLabel">Activée</small></button>
|
||
</div>
|
||
<div class="av-wave-host" id="avWaveHost"></div>
|
||
</section>
|
||
<div class="av-shortcuts" role="group" aria-label="Raccourcis d'Alfred">
|
||
<button type="button" data-av-action="time">${icon('clock')}<span>Quelle heure est-il ?</span></button>
|
||
<button type="button" data-av-action="weather">${icon('cloud')}<span>Météo</span></button>
|
||
<button type="button" data-av-action="house">${icon('home')}<span>État de la maison</span></button>
|
||
<button type="button" data-av-action="proxmox">${icon('server')}<span>Vérifier Proxmox</span></button>
|
||
<button type="button" data-av-action="wazuh">${icon('shield')}<span>Alertes Wazuh</span></button>
|
||
<button type="button" data-av-action="comfyui">${icon('media')}<span>Créer une image</span></button>
|
||
<button type="button" data-av-action="more">${icon('more')}<span>Plus d'outils</span></button>
|
||
</div>
|
||
<footer class="av-history"><span class="av-history-heading">${icon('chat')} <strong>Interactions récentes</strong></span><div id="avHistoryList" aria-live="polite"><span class="av-history-empty">Aucune interaction depuis l’ouverture de cette page</span></div></footer>
|
||
<button type="button" class="av-back" id="avBack" hidden>${icon('close')} Retour à l’accueil</button>
|
||
<div class="av-toast" id="avToast" role="status" aria-live="polite" hidden></div>
|
||
<dialog class="av-image-dialog" id="avComfyDialog"><form method="dialog"><button type="submit" class="av-dialog-close" aria-label="Fermer">${icon('close')}</button></form><h2>Créer une image avec ComfyUI</h2><p>Décris l’image à générer. Alfred transmettra ta demande à Hermes.</p><label for="avComfyPrompt">Description</label><textarea id="avComfyPrompt" rows="4" maxlength="450" placeholder="Une maison futuriste au coucher du soleil…"></textarea><div class="av-dialog-actions"><button id="avComfySubmit" type="button">Envoyer à Alfred</button></div></dialog>
|
||
<dialog class="av-image-dialog" id="avMoreDialog"><form method="dialog"><button type="submit" class="av-dialog-close" aria-label="Fermer">${icon('close')}</button></form><h2>Outils Alfred</h2><div class="av-more-grid"><a href="/config">${icon('settings')} Configuration</a><a href="/surveillance">${icon('server')} Surveillance</a><button type="button" data-av-action="calendar">${icon('calendar')} Agenda</button><button type="button" data-av-action="camera">${icon('camera')} Caméra</button><button type="button" data-av-action="audio">${icon('speaker')} Réglages audio</button></div></dialog>
|
||
`;
|
||
shell.insertBefore(dashboard, shell.firstChild);
|
||
document.body.classList.add('alfred-glass-theme');
|
||
document.getElementById('avName').textContent = initialName;
|
||
const wave = document.getElementById('wave');
|
||
document.getElementById('avWaveHost').appendChild(wave);
|
||
window.dispatchEvent(new Event('resize'));
|
||
const $ = id => document.getElementById(id);
|
||
const timeZone = 'Europe/Paris';
|
||
let lastStatus = 'idle';
|
||
let mute = {mic_muted:false,speaker_muted:false};
|
||
let refreshingAudio = false;
|
||
let toastTimer;
|
||
|
||
function toast(message) {
|
||
const el = $('avToast'); el.textContent = String(message); el.hidden = false;
|
||
clearTimeout(toastTimer); toastTimer = setTimeout(() => { el.hidden = true; }, 5500);
|
||
}
|
||
function clock() {
|
||
const now = new Date();
|
||
$('avTime').textContent = new Intl.DateTimeFormat('fr-FR',{timeZone,hour:'2-digit',minute:'2-digit'}).format(now);
|
||
const date = new Intl.DateTimeFormat('fr-FR',{timeZone,weekday:'long',day:'numeric',month:'long',year:'numeric'}).format(now);
|
||
$('avDate').textContent = date.charAt(0).toUpperCase() + date.slice(1);
|
||
}
|
||
clock(); setInterval(clock, 15000);
|
||
function updateAudio(a) {
|
||
mute = {...mute,...a};
|
||
const mic = Boolean(mute.mic_muted), speaker = Boolean(mute.speaker_muted);
|
||
for (const id of ['avTopMic','avMicButton']) {
|
||
const button = $(id); button.setAttribute('aria-pressed',String(mic));
|
||
button.setAttribute('aria-label', mic ? 'Réactiver le microphone' : 'Couper le microphone');
|
||
button.title = button.getAttribute('aria-label');
|
||
button.querySelector('svg').outerHTML = icon(mic?'micOff':'mic');
|
||
}
|
||
for (const id of ['avTopSpeaker','avSpeakerButton']) {
|
||
const button = $(id); button.setAttribute('aria-pressed',String(speaker));
|
||
button.setAttribute('aria-label', speaker?'Réactiver la sortie audio':'Couper la sortie audio');
|
||
button.title = button.getAttribute('aria-label');
|
||
button.querySelector('svg').outerHTML = icon(speaker?'speakerOff':'speaker');
|
||
}
|
||
$('avMicLabel').textContent = mic?'Coupé':'Actif';
|
||
$('avSpeakerLabel').textContent = speaker?'Coupée':'Activée';
|
||
dashboard.classList.toggle('av-mic-is-muted',mic);
|
||
dashboard.classList.toggle('av-speaker-is-muted',speaker);
|
||
renderMode(lastStatus);
|
||
}
|
||
async function refreshAudio() {
|
||
if (refreshingAudio) return;
|
||
refreshingAudio = true;
|
||
try { const r=await fetch('/api/audio/settings',{cache:'no-store'});
|
||
if (!r.ok) throw Error('HTTP '+r.status);
|
||
updateAudio(await r.json());
|
||
} catch (e) { console.warn('[ALFRED UI] Réglages audio :',e); }
|
||
finally {refreshingAudio=false;}
|
||
}
|
||
async function toggleAudio(kind) {
|
||
const prop = kind==='mic'?'mic_muted':'speaker_muted';
|
||
try {
|
||
const r=await fetch('/api/audio/settings',{method:'POST',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},body:JSON.stringify({[prop]:!mute[prop]})});
|
||
const data=await r.json();
|
||
if (!r.ok) throw Error(data.error || 'HTTP '+r.status);
|
||
updateAudio(data);
|
||
toast(kind==='mic' ? (data.mic_muted?'Microphone coupé':'Microphone réactivé') : (data.speaker_muted?'Sortie audio coupée':'Sortie audio réactivée'));
|
||
} catch (e) { toast('Impossible de modifier le son : '+e.message); }
|
||
}
|
||
for (const id of ['avTopMic','avMicButton']) $(id).addEventListener('click',()=>toggleAudio('mic'));
|
||
for (const id of ['avTopSpeaker','avSpeakerButton']) $(id).addEventListener('click',()=>toggleAudio('speaker'));
|
||
$('avTopSettings').addEventListener('click',()=>{const btn=$('audioSettingsButton'); if(btn)btn.click();else location.assign('/config');});
|
||
refreshAudio(); setInterval(()=>{if(!document.hidden)refreshAudio();},30000);
|
||
|
||
function serviceText(state) {return state==='ok'?'Connecté':state==='offline'?'Hors ligne':state==='installed'?'Installé (non testé)':state==='unconfigured'?'Non configuré':'Non vérifié';}
|
||
function setService(id,dotid,label,state) {
|
||
$(id).textContent = `${label} : ${serviceText(state)}`;
|
||
$(dotid).className = `av-mini-dot ${state==='ok'?'is-ok':state==='offline'?'is-error':''}`;
|
||
}
|
||
async function refreshDashboard() {
|
||
try {
|
||
const r=await fetch('/api/alfred/dashboard',{cache:'no-store'});
|
||
if(!r.ok) throw Error('HTTP '+r.status);
|
||
const d=await r.json();
|
||
const h=d.home||{}, s=d.system||{}, ai=d.services||{}, w=d.weather||{};
|
||
$('avHomeSummary').textContent=h.available?'Home Assistant connecté':(h.detail||'Données non disponibles');
|
||
$('avLights').textContent = h.available?`${h.lights_on} lumière(s) allumée(s)`:'Lumières : indisponible';
|
||
$('avCovers').textContent = h.available?`${h.covers_closed} volet(s) fermé(s)`:'Volets : indisponible';
|
||
$('avIndoor').textContent = h.indoor_temperature!=null?`${h.indoor_temperature} °C · intérieur`:'Température : non disponible';
|
||
if (window.alfredHomeCardCustomUpdate) window.alfredHomeCardCustomUpdate();
|
||
$('avLoad').textContent = s.load_1m!=null?`Charge 1 min : ${s.load_1m}`:'Charge : non disponible';
|
||
$('avMemory').textContent = s.memory_percent!=null?`Mémoire : ${s.memory_percent} %`:'Mémoire : non disponible';
|
||
$('avDisk').textContent = s.disk_percent!=null?`Stockage : ${s.disk_percent} %`:'Stockage : non disponible';
|
||
setService('avOllama','avOllamaDot','Ollama',ai.ollama);
|
||
setService('avHermes','avHermesDot','Hermes',ai.hermes);
|
||
setService('avComfy','avComfyDot','ComfyUI',ai.comfyui);
|
||
$('avAiSummary').textContent='États vérifiés / installation locale';
|
||
$('avWeatherTemp').textContent=w.available&&w.temperature!=null?`${w.temperature} ${w.unit||'°C'}`:'—';
|
||
$('avWeatherCondition').textContent=w.available?w.condition:'Non disponible';
|
||
const sunny=['sunny','clear-night','partlycloudy'].includes(w.condition_id);
|
||
$('avWeatherIcon').textContent=sunny?'🌤️':w.condition_id==='rainy'?'🌧️':w.condition_id==='snowy'?'❄️':'☁️';
|
||
} catch (e) {
|
||
$('avHomeSummary').textContent='Données indisponibles';
|
||
$('avAiSummary').textContent='Surveillance indisponible';
|
||
$('avWeatherCondition').textContent='Indisponible';
|
||
console.warn('[ALFRED UI] Tableau de bord :',e);
|
||
}
|
||
}
|
||
refreshDashboard(); setInterval(()=>{if(!document.hidden)refreshDashboard();},30000);
|
||
async function refreshForecast() {
|
||
try {
|
||
const r=await fetch('/api/ha/weather',{cache:'no-store'});
|
||
if(!r.ok)return;
|
||
const d=await r.json();
|
||
const day=(d.daily||[])[0];
|
||
const vals=[];
|
||
if(day?.temperature!=null) vals.push('↑ '+day.temperature+'°');
|
||
if(day?.templow!=null) vals.push('↓ '+day.templow+'°');
|
||
$('avWeatherRange').textContent=vals.join(' ');
|
||
} catch (_) { /* Not all installations expose the weather forecast route. */ }
|
||
}
|
||
refreshForecast();setInterval(()=>{if(!document.hidden)refreshForecast();},180000);
|
||
|
||
function renderMode(mode) {
|
||
lastStatus = mode||'idle';
|
||
const states = {idle:['En veille','Dites « '+initialName+' » puis votre demande','Prêt'],
|
||
waiting:['En veille','Dites « '+initialName+' » puis votre demande','Prêt'],
|
||
listening:['Je vous écoute…','Quelle est votre demande ?','Écoute en cours'],
|
||
thinking:['Je réfléchis…','Traitement de la demande','Hermes travaille'],
|
||
speaking:['Je vous réponds…','Réponse vocale en cours','Piper parle'],
|
||
error:['Un problème est survenu','Consultez la transcription ou la surveillance','Erreur']};
|
||
const item=states[mode]||states.idle;
|
||
$('avVoiceTitle').textContent=mute.mic_muted?'Microphone coupé':item[0];
|
||
$('avVoiceSubtitle').textContent=mute.mic_muted?'Réactivez le micro pour utiliser le mot d’activation':item[1];
|
||
$('avStatusText').textContent=mute.mic_muted?'Écoute désactivée':item[2];
|
||
dashboard.dataset.mode=mode;
|
||
$('avOrb').setAttribute('aria-label',item[0]);
|
||
}
|
||
renderMode('idle');
|
||
|
||
const recent=[];
|
||
let lastTranscript='';
|
||
function pushRecent(text) {
|
||
const value=String(text||'').trim();
|
||
if(!value || value===lastTranscript)return;
|
||
lastTranscript=value;
|
||
recent.unshift({text:value,at:new Date()});
|
||
if(recent.length>5)recent.pop();
|
||
const root=$('avHistoryList');root.replaceChildren();
|
||
for(const e of recent) {
|
||
const node=document.createElement('span');node.className='av-history-item';
|
||
const t=document.createElement('time');t.textContent=new Intl.DateTimeFormat('fr-FR',{timeZone,hour:'2-digit',minute:'2-digit'}).format(e.at);
|
||
const content=document.createElement('span');content.textContent=e.text;
|
||
node.append(t,content);root.append(node);
|
||
}
|
||
}
|
||
function focusScene(show) {
|
||
dashboard.classList.toggle('is-focused', Boolean(show));
|
||
$('avBack').hidden = !show;
|
||
}
|
||
function onRender(d) { if(!d || typeof d!=='object')return;
|
||
if(d.type==='home')focusScene(false);
|
||
else if(d.type && d.type!=='text' || (d.text||d.title||d.url))focusScene(true);
|
||
}
|
||
const socket=window.alfredSocket;
|
||
socket?.on('alfred_state',state=>{
|
||
renderMode(String(state.mode||'idle').toLowerCase());
|
||
if(state.mode==='thinking' && state.transcript)pushRecent(state.transcript);
|
||
});
|
||
socket?.on('alfred_render',onRender);
|
||
socket?.on('audio_settings',updateAudio);
|
||
fetch('/api/state',{cache:'no-store'}).then(r=>r.ok?r.json():null).then(s=>{if(s)renderMode(String(s.mode||'idle').toLowerCase());}).catch(()=>{});
|
||
|
||
async function goHome() {
|
||
focusScene(false);
|
||
if(window.AlfredViews?.goHome){try{await window.AlfredViews.goHome();}catch(_){}}
|
||
else if($('viewHome'))$('viewHome').click();
|
||
}
|
||
$('avBack').addEventListener('click',goHome);
|
||
function openCamera() {
|
||
if($('cameraPopupButton')){ $('cameraPopupButton').click();return; }
|
||
const camera=document.querySelector('.camera-panel');
|
||
if(!camera){ toast('Caméra non installée');return; }
|
||
const isOpen=document.body.classList.toggle('av-legacy-camera-open');
|
||
const toggle=$('cameraToggle');
|
||
if(toggle && (toggle.getAttribute('aria-pressed')==='true')!==isOpen)toggle.click();
|
||
if(isOpen && !$('avLegacyCameraClose')){
|
||
const close=document.createElement('button');close.id='avLegacyCameraClose';close.type='button';close.textContent='Fermer la caméra';
|
||
close.addEventListener('click',openCamera);camera.prepend(close);
|
||
}
|
||
}
|
||
$('avTopCamera').addEventListener('click',openCamera);
|
||
|
||
function openView(name) {
|
||
const f=window.AlfredViews?.[name];
|
||
if(typeof f==='function'){ focusScene(true);f();return; }
|
||
const fallback={openWeather:'viewWeather',openTemperatures:'viewTemperatures',openCalendar:'viewCalendar'}[name];
|
||
if(fallback && $(fallback)){focusScene(true);$(fallback).click();return;}
|
||
toast('Cette fonction n’est pas disponible sur cette installation.');
|
||
}
|
||
async function quickCommand(action,description) {
|
||
try {
|
||
const r=await fetch('/api/alfred/dashboard/command',{method:'POST',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},body:JSON.stringify({action,description})});
|
||
const data=await r.json(); if(!r.ok)throw Error(data.error||'HTTP '+r.status);
|
||
pushRecent(({proxmox:'Vérifier Proxmox',wazuh:'Consulter les alertes Wazuh',comfyui:'Générer une image'})[action]||action);
|
||
toast('Demande transmise à Hermes');
|
||
} catch(e) {toast('Commande indisponible : '+e.message);}
|
||
}
|
||
function showTime() {
|
||
const current=new Intl.DateTimeFormat('fr-FR',{timeZone,hour:'2-digit',minute:'2-digit',second:'2-digit'}).format(new Date());
|
||
const content=$('content');content.replaceChildren();
|
||
const title=document.createElement('h1');title.textContent='Il est '+current;
|
||
const subtitle=document.createElement('p');subtitle.textContent='Heure de Paris · horloge locale du navigateur';
|
||
content.append(title,subtitle); focusScene(true);pushRecent('Quelle heure est-il ?');
|
||
}
|
||
async function act(name) {
|
||
if(name==='home'){await goHome();return;}
|
||
if(name==='house'){openView('openTemperatures');return;}
|
||
if(name==='weather'){openView('openWeather');return;}
|
||
if(name==='calendar'){ $('avMoreDialog').close();openView('openCalendar');return; }
|
||
if(name==='camera'){ $('avMoreDialog').close();openCamera();return; }
|
||
if(name==='media'){ $('avComfyDialog').showModal();$('avComfyPrompt').focus();return;}
|
||
if(name==='time'){showTime();return;}
|
||
if(name==='proxmox'||name==='wazuh'){await quickCommand(name);return;}
|
||
if(name==='comfyui'){ $('avComfyDialog').showModal();$('avComfyPrompt').focus();return;}
|
||
if(name==='more'){ $('avMoreDialog').showModal();return;}
|
||
if(name==='audio'){ $('avMoreDialog').close();$('avTopSettings').click();return; }
|
||
}
|
||
dashboard.addEventListener('click',event=>{
|
||
const button=event.target.closest('[data-av-action]');
|
||
if(button)act(button.dataset.avAction);
|
||
});
|
||
$('avComfySubmit').addEventListener('click',()=>{
|
||
const text=$('avComfyPrompt').value.trim();
|
||
if(text.length<4){toast('Décris l’image à générer.');return;}
|
||
$('avComfyDialog').close();quickCommand('comfyui',text);
|
||
});
|
||
window.addEventListener('resize',()=>{ /* legacy alfred.js recalculates the waveform canvas */ });
|
||
})();
|