/* 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(); })();