/* DONA_SUITE_V9: all model and user text inserted as textContent, never innerHTML. */ (()=>{ 'use strict'; if(window.DonaSuiteV9)return; const $=id=>document.getElementById(id); const el=(tag,cls,text)=>{const a=document.createElement(tag);if(cls)a.className=cls;if(text!==undefined)a.textContent=String(text);return a;}; const btn=(label,callback,cls='dv9-button')=>{const b=el('button',cls,label);b.type='button';b.addEventListener('click',callback);return b;}; let opened=false,current=null,lastPrompt=''; const status=t=>{if($('dv9Status'))$('dv9Status').textContent=String(t||'');}; async function api(url,body){ const r=await fetch(url,{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},body:JSON.stringify(body)}); const j=await r.json().catch(()=>({}));if(!r.ok||j.ok===false)throw Error(j.error||'Service indisponible');return j; } async function get(url){const r=await fetch(url,{credentials:'same-origin'});if(!r.ok)throw Error('Service indisponible');return r.json();} function open(){opened=true;$('donaSuiteV9').hidden=false;document.body.classList.add('dv9-open');loadPins();} function close(){opened=false;$('donaSuiteV9').hidden=true;document.body.classList.remove('dv9-open');} function section(title,subtitle){const c=el('article','dv9-card');c.append(el('h3','',title));if(subtitle)c.append(el('p','dv9-muted',subtitle));$('dv9Results').append(c);return c;} function paragraph(host,text){host.append(el('p','dv9-body',text));} function list(host,items){const ul=el('ul','dv9-list');items.forEach(x=>ul.append(el('li','',x)));host.append(ul);} function recipeCard(rec){ const c=section(rec.title,(rec.minutes||'?')+' min'); const cols=el('div','dv9-columns'); const ingredients=el('div');ingredients.append(el('h4','','Ingrédients'));list(ingredients,rec.ingredients||[]); if(rec.matched?.length)paragraph(ingredients,'Déjà disponibles : '+rec.matched.join(', ')); if(rec.missing?.length)paragraph(ingredients,'À prévoir : '+rec.missing.join(', ')); const prep=el('div');prep.append(el('h4','','Préparation'));const ol=el('ol','dv9-list');(rec.steps||[]).forEach(x=>ol.append(el('li','',x)));prep.append(ol); cols.append(ingredients,prep);c.append(cols); } function render(result,prompt){ if(!result)return;current=result;lastPrompt=prompt||lastPrompt||'';open(); $('dv9ResultTitle').textContent=result.title||'Résultat';$('dv9Results').replaceChildren();status(result.source||''); const kind=result.kind; if(kind==='error'){section('Erreur',result.error);return;} if(kind==='recipe'){ (result.recipes||[]).forEach(recipeCard); if(!result.recipes?.length)section('Pas de recette locale','Essaie une recette plus générale ou une liste d’ingrédients.'); }else if(kind==='menu'){ showMenu(result.menu); }else if(kind==='briefing'||kind==='daily'){ const b=result.briefing||{}; const c=section('Agenda · '+(b.date||''),b.days+' jour(s)'); if(b.events?.length)list(c,b.events.map(x=>(x.start?.slice(0,16).replace('T',' ')||'')+' · '+x.title+(x.location?' · '+x.location:''))); else paragraph(c,'Aucun événement trouvé pour la période, ou agenda indisponible.'); const w=b.weather; if(w)section('Météo · Home Assistant',w.description+(w.temperature!==null&&w.temperature!==undefined?' · '+w.temperature+' '+w.unit:'')); (b.errors||[]).forEach(x=>section('Source indisponible',x)); if(b.suggested_menu)showMenu(b.suggested_menu); }else if(kind==='memories'){ const h=section('Souvenirs enregistrés','Enregistrés uniquement sur demande.'); if(!result.memories?.length)paragraph(h,'Aucun souvenir.'); (result.memories||[]).forEach(item=>{ const row=el('div','dv9-memory');row.append(el('span','',item.text),btn('Effacer',async()=>{ if(!confirm('Supprimer ce souvenir de Dona ?'))return; try{await api('/api/dona/v9/memory/delete',{id:item.id});await ask('Dona, montre mes souvenirs');}catch(e){status(e.message);} },'dv9-danger'));h.append(row); }); }else if(kind==='documents'){ const d=result.documents||{}; section('Recherche locale',d.note); (d.found||[]).forEach(x=>{const c=section(x.file,'Extrait '+x.section);paragraph(c,x.snippet);}); }else if(kind==='routine'){ const r=result.routine||{};const c=section(r.name,r.message); if(r.lights?.length)list(c,r.lights); if(r.executable)c.append(btn('Confirmer et éteindre ces lumières',async()=>{ if(!confirm('Éteindre ces lumières Home Assistant maintenant ?'))return; try{const j=await api('/api/dona/v9/routine/execute',{confirm:'Eteindre les lumieres autorisees'});status(j.result?.message||'Commande envoyée.');}catch(e){status(e.message);} },'dv9-danger')); }else if(kind==='child'){ const q=result.quiz||{}; const c=section('Quiz de découverte',q.note); (q.questions||[]).forEach(x=>{ const row=el('div','dv9-quiz');row.append(el('p','',x.question),btn('Voir la réponse',e=>{const a=el('span','dv9-answer',x.answer);e.currentTarget?.replaceWith(a);},'dv9-button')); // Event handlers are attached directly by btn(), no interpolation of model text. c.append(row); }); const link=el('a','dv9-link','Accéder à l’espace enfant');link.href='/enfant';c.append(link); }else if(kind==='monitor'){ const d=result.monitor||{};section('Sources Hermes · MCP',d.note); const board=d.dashboard; if(board?.cards?.length){ const head=section(board.title,board.subtitle); board.cards.forEach(x=>{ const c=section(x.title||x.type,''); if(x.type==='metric')paragraph(c,String(x.value??'—')+(x.unit?' '+x.unit:'')); if(x.type==='text')paragraph(c,x.text||''); if(x.type==='list')list(c,(x.items||[]).map(String)); if(x.type==='table'){list(c,(x.rows||[]).map(row=>(row||[]).join(' · ')));} if(x.type==='bars'){list(c,(x.items||[]).map(item=>item.label+' : '+item.value));} }); } }else section('Réponse',result.speech||''); const pin=$('dv9PinResult');pin.hidden=!['recipe','menu','briefing','daily','documents','monitor','routine'].includes(kind); } function showMenu(menu){ if(!menu)return; const c=section('Menus pour '+menu.people+' personnes · '+menu.days+' jours',menu.note); (menu.meals||[]).forEach((x,i)=>{const item=el('div','dv9-meal');item.append(el('strong','','Jour '+(i+1)+' · '+x.title)); item.append(btn('Voir la recette',()=>{ $('dv9Results').replaceChildren();recipeCard(x.recipe);$('dv9ResultTitle').textContent=x.title;}));c.append(item); }); const shop=section('Liste de courses · à vérifier','Les éléments sont proposés sans quantités précises.');list(shop,menu.shopping||[]); shop.append(btn('Ajouter à ma liste de courses',async()=>{ if(!confirm('Ajouter '+(menu.shopping||[]).length+' ingrédients à la liste de courses de Dona ?'))return; try{const j=await api('/api/dona/v9/shopping/add',{items:menu.shopping});status(j.added.length+' ajouté(s) à la liste de courses existante d’Alfred'+(j.already_present.length?' · '+j.already_present.length+' déjà présent(s).':'.'));}catch(e){status(e.message);} })); shop.append(btn('Ouvrir la liste de courses d’Alfred',()=>{ if(window.AlfredLists?.open){close();window.AlfredLists.open();}else status('La fenêtre Listes d’Alfred est indisponible.'); },'dv9-button')); } async function ask(text){ if(!text.trim())return;status('Dona prépare la réponse…');$('dv9Run').disabled=true; try{const x=await api('/api/dona/v9/ask',{text});render(x.result,text);} catch(e){status('Erreur : '+e.message);} finally{$('dv9Run').disabled=false;} } async function loadPins(){ try{ const x=await get('/api/dona/v9/pins');const node=$('dv9Pins');node.replaceChildren(); (x.pins||[]).forEach(p=>{const div=el('div','dv9-pin');div.append(btn(p.title,()=>ask(p.prompt))); div.append(btn('×',async()=>{try{await api('/api/dona/v9/pins/delete',{id:p.id});loadPins();}catch(e){status(e.message);}},'dv9-pin-del'));node.append(div);}); }catch(e){status('Épingles indisponibles : '+e.message);} } function init(){ if($('donaSuiteV9'))return; const overlay=el('div','dv9-overlay');overlay.id='donaSuiteV9';overlay.hidden=true; const panel=el('section','dv9-panel');panel.setAttribute('role','dialog');panel.setAttribute('aria-modal','true');panel.setAttribute('aria-label','Dona, quotidien et fonctionnalités'); const head=el('header','dv9-head');const brand=el('div');brand.append(el('span','dv9-small','DONA · SUITE V9'),el('h2','','Mon majordome'));head.append(brand,btn('×',close,'dv9-close'));panel.append(head); const examples=el('nav','dv9-quick');[ ['☀ Briefing','Dona, briefing du jour'],['▦ Organiser','Dona, organise ma journée de demain'],['♨ Cuisine','Dona, montre-moi la recette de la quiche'], ['▤ Menus','Dona, prépare des menus pour 3 jours'],['⌂ Routine','Dona, bonne nuit'],['▣ Serveurs','Dona, état des serveurs Proxmox et Wazuh'], ['▤ Documents','Dona, cherche dans mes documents Proxmox'],['✦ Mémoire','Dona, montre mes souvenirs'],['✿ Enfant','Dona, quiz enfant'] ].forEach(([label,prompt])=>examples.append(btn(label,()=>ask(prompt),'dv9-chip')));panel.append(examples); const form=el('form','dv9-form');const input=el('input','dv9-input');input.id='dv9Prompt';input.placeholder='Demande une recette, un briefing, un document…';input.maxLength=500;input.required=true; const run=btn('Demander',()=>{},'dv9-button dv9-primary');run.type='submit';run.id='dv9Run';form.append(input,run);form.addEventListener('submit',e=>{e.preventDefault();ask(input.value);});panel.append(form); const statusLine=el('p','dv9-status');statusLine.id='dv9Status';statusLine.setAttribute('role','status');panel.append(statusLine); const pinsWrap=el('div','dv9-pins');pinsWrap.append(el('h3','','Épingles persistantes'));const pins=el('div','dv9-pins-row');pins.id='dv9Pins';pinsWrap.append(pins);panel.append(pinsWrap); const subhead=el('div','dv9-subhead');const title=el('h2','','Bienvenue');title.id='dv9ResultTitle';const pin=btn('☆ Épingler cette demande',async()=>{ if(!current)return; try{await api('/api/dona/v9/pins',{kind:current.kind,title:current.title||'Carte',prompt:lastPrompt});await loadPins();status('Demande épinglée sur le serveur.');}catch(e){status(e.message);} },'dv9-chip');pin.id='dv9PinResult';pin.hidden=true;subhead.append(title,pin);panel.append(subhead); const result=el('main','dv9-results');result.id='dv9Results';result.append(el('p','dv9-welcome','Une seule interface pour les recettes, l’agenda, les souvenirs, les documents et les serveurs.'));panel.append(result); const foot=el('p','dv9-footer','Données locales ou Home Assistant. Aucun ordre domotique n’est lancé sans confirmation. Les résultats Hermes sont des indications à vérifier.');panel.append(foot); overlay.append(panel);document.body.append(overlay); overlay.addEventListener('click',e=>{if(e.target===overlay)close();});document.addEventListener('keydown',e=>{if(e.key==='Escape'&&opened)close();}); let attempts=0;const interval=setInterval(()=>{const target=document.querySelector('#alfredMainV1 .av-head-actions')||document.querySelector('.header-actions'); if(target&&!$('dv9Open')){const b=btn('✦',open,'av-round dv9-launch');b.id='dv9Open';b.title='Dona — Quotidien';b.setAttribute('aria-label','Ouvrir le cockpit Dona');target.prepend(b);clearInterval(interval);} else if(++attempts>80)clearInterval(interval); },150); const socket=window.alfredSocket; socket?.on('dona_suite_v9',result=>{if(opened)render(result,lastPrompt);}); } if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init,{once:true});else init(); window.DonaSuiteV9={open,close,render,ask}; })();