v2.2 - Small improvement Interface change

This commit is contained in:
pporcheret
2026-10-29 11:13:02 +01:00
parent a17c3cb614
commit 7f26f9b015
271 changed files with 6207 additions and 16044 deletions
+140
View File
@@ -0,0 +1,140 @@
/* DONA_HOLOGRAPHIC_V1
* Cartes contextuelles issues du TEXTE réellement reçu d'Hermes.
* Aucune donnée, état de serveur, distance ou durée n'est calculé/inventé.
* Les autres vues (images, calendriers, cartes de renderer) restent inchangées.
*/
(() => {
'use strict';
const words = Object.freeze({zero:0,un:1,une:1,deux:2,trois:3,quatre:4,cinq:5,six:6,sept:7,huit:8,neuf:9,dix:10,onze:11,douze:12,treize:13,quatorze:14,quinze:15,seize:16,dixsept:17,dixhuit:18,dixneuf:19,vingt:20});
function numberValue(s){
if(!s)return null;
const normalized=String(s).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[\s-]/g,'');
if(Object.prototype.hasOwnProperty.call(words,normalized))return String(words[normalized]);
const str=String(s).trim();return /^\d{1,5}$/.test(str)?str:null;
}
const number='(\\d{1,5}|vingt|dix[- ]?neuf|dix[- ]?huit|dix[- ]?sept|seize|quinze|quatorze|treize|douze|onze|dix|neuf|huit|sept|six|cinq|quatre|trois|deux|une|un)';
function getFacts(input) {
const text=String(input||'').replace(/\u00a0/g,' ').slice(0,16000);
const facts=[];const seen=new Set();
function add(key,icon,label,value,note='Mentionné dans la réponse'){
if(!value||seen.has(key)||facts.length>=4)return;
facts.push({key,icon,label,value:String(value),note});seen.add(key);
}
let m;
m=text.match(new RegExp('\\b'+number+'\\s+(?:machines?\\s+virtuelles?|VMs?\\b)','i'));
if(m)add('vm','⬡','Machines virtuelles',numberValue(m[1]),'Chiffre annoncé par Hermes');
m=text.match(new RegExp('\\b'+number+'\\s+(?:n[œo]uds?|nodes?)(?:\\s|\\.|,|$)','i'));
if(m)add('nodes','▦','Nœuds mentionnés',numberValue(m[1]),'Chiffre annoncé par Hermes');
m=text.match(/\b(\d{1,3})\s*\/\s*(\d{1,3})\s*(?:n[œo]uds|serveurs|nodes?)\b/i);
if(m)add('online','●','Nœuds disponibles',m[1]+' / '+m[2],'Valeur citée par Hermes');
const route=/\b(trajet|distance|route|entre\s+\S+\s+et\s+\S+|autoroute|en voiture)\b/i.test(text);
const building=/\b(hauteur|haut(?:e|s)?|b[aâ]timent|immeuble|tour|monument|construction)\b/i.test(text);
if(route){
m=text.match(/\b(\d{1,5}(?:[,.]\d+)?)\s*(kilom[eè]tres?|km)\b/i);
if(m)add('distance','↗','Distance indiquée',m[1].replace(',', '.')+' km','Distance issue de la réponse');
m=text.match(/\b(\d{1,3})\s*(?:à|a|–|-)\s*(\d{1,3})\s*(minutes?|min|heures?|h)\b/i);
if(m)add('duration','◷','Durée annoncée',m[1]+' à '+m[2]+' '+m[3],'Estimation communiquée par Hermes');
else {
m=text.match(/\b(\d{1,3})\s*(minutes?|min|heures?|h)\b/i);
if(m)add('duration','◷','Durée annoncée',m[1]+' '+m[2],'Estimation communiquée par Hermes');
}
}
if(building&&!route){
m=text.match(/\b(\d{1,5}(?:[,.]\d+)?)\s*(?:m[eè]tres?|m)\b/i);
if(m)add('height','♜','Hauteur indiquée',m[1]+' m','Valeur issue de la réponse');
}
if(/\b(temp[eé]rature|m[eé]t[eé]o|degr[eé]s)\b/i.test(text)){
m=text.match(/(-?\d{1,3}(?:[,.]\d+)?)\s*°\s*C\b/i);
if(m)add('temp','☀','Température citée',m[1]+' °C','Valeur issue de la réponse');
}
// Ne jamais induire qu'un nœud est en ligne à partir du seul nom.
if(facts.length<4){
m=text.match(/[«"']\s*([\wÀ-ÿ][\wÀ-ÿ_.-]{1,39})\s*[»"'][^.!?\n]{0,90}(arr[eê]t[eé]e?|[eé]teint(?:e)?)(?=[\s,.!?]|$)/i);
if(m)add('stopped','⏻','Équipement signalé arrêté',m[1],'État décrit dans la réponse');
}
if(facts.length===0){
m=text.match(/\b(?:il est(?: actuellement)?|heure(?: locale)?\s*[::])\s*(\d{1,2}[:h]\d{2})\b/i);
if(m)add('time','◷','Heure indiquée',m[1].replace('h',':'),'Issue de la réponse, non vérifiée ici');
}
const names=[];
const re=/(?:n[œo]ud|node)\s*['"«]?([\wÀ-ÿ_.-]{2,35})['"»]?\s*:/gi;
while((m=re.exec(text))&&names.length<10){if(!names.includes(m[1])) names.push(m[1]);}
return {facts,names};
}
function summaryOf(text,title){
const clean=String(text||'').trim();const t=String(title||'').trim();
const first=clean.split(/(?<=[.!?])\s+|\n{2,}/)[0]?.trim()||clean;
const headline=t && !/^(r[eé]ponse|hermes|texte)$/i.test(t)?t:first;
const truncated=headline.length>170?headline.slice(0,167).replace(/\s+\S*$/,'')+'…':headline;
let excerpt=clean;
if(clean.startsWith(first)&&first.length>35)excerpt=clean.slice(first.length).replace(/^\s+/,'');
if(!excerpt)excerpt=clean;
if(excerpt.length>290)excerpt=excerpt.slice(0,287).replace(/\s+\S*$/,'')+'…';
return {headline:truncated,excerpt};
}
if(typeof module==='object'&&module.exports){module.exports={getFacts,summaryOf,numberValue};}
if(typeof document==='undefined')return;
const body=document.body;
if(!body||body.dataset.donaHoloV1==='active')return;
body.dataset.donaHoloV1='active';body.classList.add('dona-holo-v1');
const main=document.getElementById('alfredMainV1');
const content=document.getElementById('content');
if(!main||!content)return; // Pages secondaires : seulement le thème transversal.
function el(tag,className,txt){const node=document.createElement(tag);if(className)node.className=className;if(txt!==undefined)node.textContent=String(txt);return node;}
let busy=false;let original='';
function renderCards(){
if(busy || content.firstElementChild?.classList.contains('dh-response'))return;
if(content.querySelector('.cards, img.hero, video, canvas, iframe'))return;
// N'afficher les cartes que pour une réponse réellement ouverte par Alfred.
if(!main.classList.contains('is-focused'))return;
const children=[...content.children];
if(!children.length)return;
const text=children.filter(n=>n.matches('h1,h2,p,pre')).map(n=>n.textContent.trim()).filter(Boolean).join('\n\n').trim();
if(!text || text.length<13)return;
if(/^(bonjour\.|à votre écoute\.|en attente|chargement)/i.test(text))return;
if(text===original && content.dataset.holoTransformed==='done')return;
const title=content.querySelector('h1,h2')?.textContent||'';
const raw=(title && text.startsWith(title+'\n\n'))?text.slice(title.length+2):text;
const {facts,names}=getFacts(text);
const summary=summaryOf(raw,title);
const wrap=el('section','dh-response');
wrap.setAttribute('aria-label','Synthèse contextuelle de la réponse Hermes');
const primary=el('article','dh-primary');
primary.append(el('div','dh-kicker','RÉPONSE · DONA'),el('div','dh-headline',summary.headline));
if(summary.excerpt && summary.excerpt!==summary.headline)primary.append(el('div','dh-excerpt',summary.excerpt));
else primary.append(el('div','dh-excerpt','Réponse disponible dans son intégralité.'));
const more=el('button','dh-more');more.type='button';more.setAttribute('aria-expanded','false');more.append(el('span','', '▤ Afficher la réponse complète'),el('span','','›'));
primary.append(more);wrap.append(primary);
if(facts.length||names.length){
const grid=el('div','dh-facts');
facts.forEach(f=>{
const c=el('article','dh-fact');
c.append(el('span','dh-fact-icon',f.icon),el('div','dh-fact-label',f.label),el('strong','dh-fact-value',f.value),el('div','dh-fact-note',f.note));
grid.append(c);
});
if(names.length && grid.children.length<4){
const c=el('article','dh-fact');c.append(el('span','dh-fact-icon','⬡'),el('div','dh-fact-label','Nœuds cités'));
const tags=el('div','dh-tags');names.forEach(n=>tags.append(el('span','dh-tag',n)));c.append(tags,el('div','dh-fact-note','Noms présents dans la réponse'));grid.append(c);
}
wrap.append(grid);
} else {
wrap.classList.add('dh-no-facts');
}
const full=el('section','dh-full');full.hidden=true;full.id='dhFullReply';
full.append(el('h3','', 'Réponse complète d’Hermes'),el('div','dh-full-text',text),el('p','dh-source','Affichage fidèle au texte reçu. Les faits n’ont pas été vérifiés séparément.'));
more.setAttribute('aria-controls','dhFullReply');
more.addEventListener('click',()=>{
const opened=full.hidden;full.hidden=!opened;more.setAttribute('aria-expanded',String(opened));
more.firstElementChild.textContent=opened?'▤ Masquer la réponse complète':'▤ Afficher la réponse complète';
more.lastElementChild.textContent=opened?'⌃':'›';
});
wrap.append(full);
busy=true;content.replaceChildren(wrap);content.dataset.holoTransformed='done';original=text;busy=false;
}
const obs=new MutationObserver(()=>{if(!busy)renderCards();});
obs.observe(content,{childList:true});
// Accès à l'écran de réponse après mise à jour de la classe focus, sans boucle.
main.addEventListener('click',()=>setTimeout(renderCards,0));
// Le renderer/socket réactualise #content : la mutation constitue l'événement canonique.
setTimeout(renderCards,100);
})();