68 lines
8.5 KiB
JavaScript
68 lines
8.5 KiB
JavaScript
/* 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();
|
||
})();
|