Files
Alfredv2/alfred/app/static/js/dona-vision-v9-5.js
T
2026-10-31 11:04:45 +01:00

68 lines
8.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* DONA_VISION_V95 : YOLOv8 local opt-in et image Hermes sur demande. */
(()=>{'use strict';if(window.DonaVisionV95)return;
const $=id=>document.getElementById(id);
const el=(tag,cls,text)=>{const n=document.createElement(tag);if(cls)n.className=cls;if(text!==undefined)n.textContent=String(text);return n};
const btn=(text,action,cls)=>{const n=el('button',cls||'dv95-btn',text);n.type='button';n.addEventListener('click',action);return n};
let opened=false,busy=false,monitoring=false,toastTimer;
async function request(url,data){const r=await fetch(url,{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},body:JSON.stringify(data)});const out=await r.json().catch(()=>({}));if(!r.ok||out.ok===false)throw Error(out.error||'Erreur '+r.status);return out}
async function getStatus(){const r=await fetch('/api/dona/vision/v95/status',{credentials:'same-origin'});if(!r.ok)throw Error('État indisponible');return r.json()}
function message(value){const n=$('dv95Status');if(n)n.textContent=String(value||'')}
function render(data){monitoring=!!data.running;$('dv95Start').hidden=monitoring;$('dv95Stop').hidden=!monitoring;$('dv95People').disabled=monitoring;$('dv95Objects').disabled=monitoring;
message((data.message||'')+(data.ultralytics_installed?'':' · YOLOv8 à installer')+(data.model_ready?'':' · modèle absent'));
$('dv95Model').textContent='Modèle : '+(data.model||'yolov8n.pt')+' · balayage '+(data.interval||2.5)+' s';
const objects=$('dv95Detections');objects.replaceChildren();(data.latest||[]).slice(0,10).forEach(x=>objects.append(el('span','dv95-pill',x.label+' · '+Math.round(x.confidence*100)+' %')));
if(!objects.children.length)objects.append(el('span','dv95-muted','Aucune détection récente.'));
const history=$('dv95History');history.replaceChildren();(data.events||[]).forEach(x=>{const n=el('li');n.append(el('strong','',x.time+' · '+x.title),el('p','',x.detail));history.append(n)});
if(!history.children.length)history.append(el('li','dv95-muted','Aucun événement pour cette session.'));
}
async function refresh(){try{render(await getStatus())}catch(e){message(e.message)}}
async function setMonitor(enabled){if(busy)return;busy=true;message(enabled?'Démarrage YOLOv8…':'Arrêt en cours…');
try{const x=await request('/api/dona/vision/v95/monitor',{enabled,person:$('dv95People').checked,objects:$('dv95Objects').checked});render(x)}catch(e){message(e.message)}finally{busy=false}}
async function analyze(){if(busy)return;busy=true;$('dv95Analyze').disabled=true;const question=$('dv95Question').value.trim()||'Décris ce que tu vois devant toi.';
$('dv95Answer').textContent='Analyse de la capture par Hermes en cours…';
try{const x=await request('/api/dona/vision/v95/analyze',{question});$('dv95Answer').textContent=x.answer||'Pas de description.'}
catch(e){$('dv95Answer').textContent=e.message}finally{busy=false;$('dv95Analyze').disabled=false}}
function startPreview(){const img=$('dv95Feed');if(img.src)return;img.src='/api/camera/stream?v=95&t='+Date.now();img.hidden=false;$('dv95PreviewHint').textContent='L’aperçu est local et non enregistré. Si la caméra est déjà utilisée ailleurs, ferme l’autre aperçu.';}
function stopPreview(){const img=$('dv95Feed');img.removeAttribute('src');img.hidden=true;$('dv95PreviewHint').textContent='Aperçu coupé.'}
function open(){opened=true;$('dv95Modal').hidden=false;refresh()}
function close(){opened=false;$('dv95Modal').hidden=true;stopPreview()}
function toast(evt){let box=$('dv95Toast');if(!box)return;box.replaceChildren(el('strong','',evt.title||'Vision Dona'),el('span','',evt.detail||''));box.hidden=false;clearTimeout(toastTimer);toastTimer=setTimeout(()=>{box.hidden=true},8500)}
function init(){const modal=el('div','dv95-overlay');modal.id='dv95Modal';modal.hidden=true;modal.setAttribute('role','dialog');modal.setAttribute('aria-modal','true');modal.setAttribute('aria-label','Vision intelligente');
const frame=el('section','dv95-frame');const head=el('header','dv95-head');const h=el('div');h.append(el('h2','','◉ Vision intelligente'),el('p','dv95-muted','Détection locale YOLOv8 · analyse ponctuelle Hermes'));
head.append(h,btn('×',close,'dv95-close'));frame.append(head);
const content=el('div','dv95-body');const left=el('div','dv95-column');const right=el('div','dv95-column');
const video=el('div','dv95-preview');const img=el('img');img.id='dv95Feed';img.alt='Aperçu caméra locale';img.hidden=true;const info=el('p','dv95-muted','Aperçu caméra arrêté.');info.id='dv95PreviewHint';video.append(img,info);left.append(video);
const previewBtns=el('div','dv95-controls');previewBtns.append(btn('Afficher la caméra',startPreview),btn('Couper l’aperçu',stopPreview));left.append(previewBtns);
left.append(el('h3','','Surveillance des événements'));
const c1=el('label','dv95-choice');const p=el('input');p.type='checkbox';p.id='dv95People';p.checked=true;c1.append(p,el('span','','Apparition d’une personne'));
const c2=el('label','dv95-choice');const o=el('input');o.type='checkbox';o.id='dv95Objects';o.checked=true;c2.append(o,el('span','','Objet visible au centre de la caméra'));
left.append(c1,c2);
const run=el('div','dv95-controls');const start=btn('▶ Activer YOLOv8',()=>setMonitor(true),'dv95-btn dv95-primary');start.id='dv95Start';const stop=btn('■ Arrêter YOLOv8',()=>setMonitor(false),'dv95-btn dv95-stop');stop.id='dv95Stop';stop.hidden=true;run.append(start,stop);left.append(run);
const status=el('p','dv95-status','Surveillance désactivée.');status.id='dv95Status';left.append(status);const model=el('p','dv95-muted','');model.id='dv95Model';left.append(model);
const detections=el('div','dv95-pills');detections.id='dv95Detections';left.append(el('h3','','Dernières détections'),detections);
right.append(el('h3','','Demander à Hermes ce que voit Dona'));
const question=el('textarea','dv95-question');question.id='dv95Question';question.rows=3;question.maxLength=600;question.value='Décris les objets et les personnes visibles. Ne devine pas leur identité.';right.append(question);
const analyzeBtn=btn('◉ Analyser une capture',analyze,'dv95-btn dv95-primary');analyzeBtn.id='dv95Analyze';right.append(analyzeBtn);
const answer=el('div','dv95-answer','L’analyse est déclenchée uniquement sur demande.');answer.id='dv95Answer';right.append(answer);
right.append(btn('◎ Identifier une personne (MCP facial)',()=>{close();const face=$('donaFaceButton');if(face)face.click();else message('Le MCP facial V7 est indisponible.');}));
right.append(el('h3','','Événements récents'));const history=el('ul','dv95-history');history.id='dv95History';right.append(history);
right.append(el('p','dv95-muted','Aucune image n’est conservée par ce module. Les événements ne déclenchent ni reconnaissance faciale ni action domotique. Les images demandées à Hermes suivent la configuration de son fournisseur IA.'));
content.append(left,right);frame.append(content);modal.append(frame);document.body.append(modal);
modal.addEventListener('pointerdown',e=>{if(e.target===modal)close()});document.addEventListener('keydown',e=>{if(e.key==='Escape'&&opened)close()});
const toastNode=el('div','dv95-toast');toastNode.id='dv95Toast';toastNode.hidden=true;toastNode.setAttribute('role','status');document.body.append(toastNode);
// V9.2 génère le menu hamburger à partir des boutons originaux de l'en-tête.
const mount=()=>{const root=$('alfredMainV1');const bar=root?.querySelector('.av-head-actions');if(!bar)return false;if($('dv95Launch'))return true;
const action=btn('◉',open,'av-round dv95-launch');action.id='dv95Launch';action.title='Vision intelligente · YOLOv8';action.setAttribute('aria-label','Vision intelligente · YOLOv8');bar.append(action);return true};
if(!mount()){let attempts=0;const timer=setInterval(()=>{if(mount()||++attempts>100)clearInterval(timer)},150)}
const socket=window.socket||window.alfredSocket||null;
// Si la Socket.IO de l'application n'est pas exposée globalement, client dédié.
try{const listener=(socket&&typeof socket.on==='function')?socket:(typeof window.io==='function'?window.io({transports:['websocket','polling']}):null);
listener?.on('dona_vision_event_v95',event=>{toast(event);if(opened)refresh()});
listener?.on('dona_vision_status_v95',()=>{if(opened)refresh()});
}catch(e){console.debug('Vision : événements non disponibles',e)}
setInterval(()=>{if(opened)refresh()},6000);
window.DonaVisionV95={open,close,refresh};
}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init,{once:true});else init();
})();