149 lines
9.1 KiB
JavaScript
149 lines
9.1 KiB
JavaScript
/* 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;}
|
||
// DONA_MARKDOWN_V1 — ne jamais interpréter le Markdown avec innerHTML brut.
|
||
function rich(tag,className,markdown,inline=false){
|
||
const node=el(tag,className);
|
||
if(window.DonaMarkdown && typeof window.DonaMarkdown.render==='function')
|
||
window.DonaMarkdown.render(node,markdown,{inline});
|
||
else node.textContent=String(markdown??'');
|
||
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'),rich('div','dh-headline',summary.headline,true));
|
||
if(summary.excerpt && summary.excerpt!==summary.headline)primary.append(rich('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'),rich('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);
|
||
})();
|