Files
Alfredv2/alfred/app/static/js/dona-suite-v9.js
T
2026-11-02 10:44:05 +01:00

154 lines
11 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_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};
})();