/* DONA_INLINE_INDEX_V2 - Réponses dans l'index, sans modal ni carte opaque. * V2 : orbe/vagues plus petits et à gauche, statut unifié dans la zone existante. */ (() => { 'use strict'; if (window.__donaInlineIndexV2) return; window.__donaInlineIndexV2 = true; const MAX = 24000; const $ = id => document.getElementById(id); const node = (tag, cls, value) => { const n = document.createElement(tag); if (cls) n.className = cls; if (value !== undefined && value !== null) n.textContent = String(value); return n; }; const cap = (text, len = MAX) => String(text ?? '').slice(0, len); const state = { kind: '', id: null, text: '', rich: false, recentRich: 0, shifted: null, socket: null, active: false, displayObserver: null, scanTimer: 0, statusNode: null, statusOriginal: '', statusCaptured: false }; function setup() { if ($('donaInlineScene')) return; const scene = node('section', 'dona-inline-scene'); scene.id = 'donaInlineScene'; scene.hidden = true; scene.setAttribute('aria-label', 'Réponse de Dona dans l’accueil'); const side = node('div', 'dona-inline-left'); side.append(node('div', 'dona-inline-left-label', 'Dona')); const right = node('article', 'dona-inline-right'); const top = node('div', 'dona-inline-top'); const eyebrow = node('span', 'dona-inline-eyebrow', 'RÉPONSE · DONA'); const close = node('button', 'dona-inline-close', '×'); close.type = 'button'; close.title = 'Masquer la réponse'; close.setAttribute('aria-label', 'Masquer la réponse et revenir à l’accueil'); close.addEventListener('click', hide); top.append(eyebrow, close); const heading = node('h2', 'dona-inline-heading'); heading.id = 'donaInlineHeading'; const content = node('div', 'dona-inline-content'); content.id = 'donaInlineContent'; content.setAttribute('role', 'region'); content.setAttribute('aria-label', 'Contenu de la réponse'); right.append(top, heading, content); scene.append(side, right); document.body.append(scene); window.addEventListener('resize', () => { if (state.active) positionIdentity(); }, {passive:true}); document.addEventListener('keydown', e => { if (e.key === 'Escape' && state.active && !document.querySelector('[role="dialog"]:not([hidden])')) hide(); }); attachEvents(); observeStandaloneResponse(); } function setHeading(title) { const h = $('donaInlineHeading'); if (!h) return; const clean = cap(title || '', 140).trim(); h.textContent = clean; h.hidden = !clean; } function statusCandidates() { const selectors = [ '#donaStatusText', '#hermesStatus', '#voiceStatus', '#assistantStatus', '#statusText', '.dona-status-text', '.assistant-status', '.status-text', '.voice-status', '.hermes-status', '[data-dona-status]', '[data-hermes-status]', '[data-status-role="assistant"]' ]; for (const sel of selectors) { const el = document.querySelector(sel); if (el && !el.closest('#donaInlineScene, .dv9-overlay, .d8-overlay') && getComputedStyle(el).display !== 'none') return el; } const all = [...document.querySelectorAll('div,span,p,small,strong')]; return all.find(el => { if (el.closest('#donaInlineScene, .dv9-overlay, .d8-overlay')) return false; if (getComputedStyle(el).display === 'none') return false; const t = (el.textContent || '').trim(); return /Hermes prépare sa réponse|Dona répond|Dona parle|Réponse reçue|Prête?/i.test(t); }) || null; } function setStatus(text) { const el = statusCandidates(); if (!el) return; if (!state.statusCaptured || state.statusNode !== el) { state.statusNode = el; state.statusOriginal = el.textContent || ''; state.statusCaptured = true; } el.textContent = String(text || '').trim(); } function restoreStatus() { if (state.statusNode && state.statusCaptured) { state.statusNode.textContent = state.statusOriginal; } } function show(kind, title) { const scene = $('donaInlineScene'); if (!scene) return; scene.hidden = false; state.active = true; state.kind = kind; document.body.classList.add('dona-inline-active'); setHeading(title); requestAnimationFrame(positionIdentity); } function clearContent() { $('donaInlineContent').replaceChildren(); } function hide() { state.active = false; $('donaInlineScene').hidden = true; document.body.classList.remove('dona-inline-active'); if (state.shifted) { state.shifted.classList.remove('dona-inline-identity-shift'); state.shifted.style.removeProperty('--dona-inline-shift-x'); state.shifted.style.removeProperty('--dona-inline-shift-y'); state.shifted.style.removeProperty('--dona-inline-scale'); state.shifted = null; } restoreStatus(); for (const el of document.querySelectorAll('.dona-inline-suppressed-modal')) el.classList.remove('dona-inline-suppressed-modal'); } function identityCandidate() { const root = $('alfredMainV1') || document.body; const preferred = [ '#donaWave', '#donaWaves', '#donaOrb', '#alfredOrb', '#alfredWave', '.av-orb-wrap', '.av-orb', '.av-wave-wrap', '.av-waves', '.dona-wave-stage', '.dona-waves', '.dona-orb', '.orb-container', '[data-dona-visualizer]', '[data-dona-orb]', '[data-dona-waves]' ]; const candidates = preferred.flatMap(sel => [...root.querySelectorAll(sel)]); if (!candidates.length) { const all = [...root.querySelectorAll('[class], [id]')].slice(0, 800); candidates.push(...all.filter(el => { const label = String(el.className?.baseVal || el.className || '') + ' ' + (el.id || ''); return /(?:^|[\s_-])(?:dona|alfred|av)?[\s_-]?(?:orb|orbe|waves?|waveform)(?:[\s_-]|$)/i.test(label); })); } for (const el of candidates) { if (el.closest('#donaInlineScene, .dv9-overlay, .d8-overlay, #dona-hermes-live-v1')) continue; if (el.matches('button, input, textarea, dialog')) continue; const rect = el.getBoundingClientRect(); if (rect.width < 50 || rect.height < 20 || rect.width > innerWidth * .98) continue; if (getComputedStyle(el).display === 'none') continue; return el; } return null; } function positionIdentity() { if (!state.active) return; const candidate = identityCandidate(); if (!candidate) return; if (state.shifted && state.shifted !== candidate) { state.shifted.classList.remove('dona-inline-identity-shift'); state.shifted.style.removeProperty('--dona-inline-shift-x'); state.shifted.style.removeProperty('--dona-inline-shift-y'); state.shifted.style.removeProperty('--dona-inline-scale'); } const label = String(candidate.className?.baseVal || candidate.className || '') + ' ' + (candidate.id || ''); const waveLike = /wave|vague/i.test(label); const oldX = parseFloat(candidate.style.getPropertyValue('--dona-inline-shift-x')) || 0; const oldY = parseFloat(candidate.style.getPropertyValue('--dona-inline-shift-y')) || 0; const rect = candidate.getBoundingClientRect(); const centreX = rect.left + rect.width / 2 - oldX; const centreY = rect.top + rect.height / 2 - oldY; const portrait = matchMedia('(max-width: 840px)').matches; const desiredX = portrait ? innerWidth * .50 : innerWidth * .18; const desiredY = portrait ? innerHeight * .27 : innerHeight * .50; const x = Math.max(-innerWidth, Math.min(innerWidth, desiredX - centreX)); const y = Math.max(-innerHeight, Math.min(innerHeight, desiredY - centreY)); const scale = portrait ? (waveLike ? .46 : .34) : (waveLike ? .54 : .32); candidate.style.setProperty('--dona-inline-shift-x', x.toFixed(1) + 'px'); candidate.style.setProperty('--dona-inline-shift-y', y.toFixed(1) + 'px'); candidate.style.setProperty('--dona-inline-scale', String(scale)); candidate.classList.add('dona-inline-identity-shift'); state.shifted = candidate; } function paragraph(text, cls) { const p = node('p', cls || 'dona-inline-paragraph'); const chunks = cap(text, 12000).split(/(\*\*[^*]+\*\*|`[^`]+`)/g); for (const chunk of chunks) { if (/^\*\*[^*]+\*\*$/.test(chunk)) p.append(node('strong', '', chunk.slice(2, -2))); else if (/^`[^`]+`$/.test(chunk)) p.append(node('code', '', chunk.slice(1, -1))); else p.append(document.createTextNode(chunk)); } return p; } function appendProse(text) { const container = $('donaInlineContent'); const lines = cap(text).replace(/\r/g, '').split(/\n{2,}/); for (const item of lines.slice(0, 90)) { const p = item.trim(); if (!p) continue; if (/^#{1,3}\s+/.test(p)) container.append(node('h3', 'dona-inline-subheading', p.replace(/^#{1,3}\s+/, ''))); else container.append(paragraph(p)); } } function renderText(text, heading = '') { state.rich = false; state.text = cap(text); show('text', heading); clearContent(); appendProse(state.text); setStatus('Dona répond…'); } function renderStream(event) { if (!event || !event.type || featureOverlayOpen()) return; if (event.type === 'start') { state.id = event.id; state.rich = false; state.text = ''; show('stream', ''); clearContent(); setStatus('Hermes prépare sa réponse…'); } else if (state.id && event.id !== state.id) return; else if (event.type === 'delta') { if (state.rich) return; state.text = cap(state.text + String(event.text || '')); show('stream', ''); setStatus('Dona répond…'); clearContent(); appendProse(state.text); } else if (event.type === 'tool') { if (state.rich) return; show('stream', ''); setStatus('Hermes prépare sa réponse…'); if (!state.text) { clearContent(); $('donaInlineContent').append(paragraph('Consultation : ' + cap(event.name, 90))); } } else if (event.type === 'error') { if (!state.rich) { show('stream', ''); setStatus('Une erreur est survenue'); } } } function list(title, items, ordered = false) { if (!Array.isArray(items) || !items.length) return; const wrap = node('section', 'dona-inline-section'); wrap.append(node('h3', 'dona-inline-subheading', title)); const ul = node(ordered ? 'ol' : 'ul', 'dona-inline-list'); items.slice(0, 50).forEach(item => ul.append(node('li', '', typeof item === 'string' ? cap(item, 500) : cap(item?.title || item?.name || item?.label || JSON.stringify(item), 500)))); wrap.append(ul); $('donaInlineContent').append(wrap); } function featureOverlayOpen() { const ids = ['donaSuiteV9', 'donaDynamicV8']; return ids.some(id => { const el = $(id); return el && !el.hidden && getComputedStyle(el).display !== 'none'; }); } function renderRecipe(recipe) { const name = cap(recipe.title || recipe.name || 'Recette', 160); show('suite', name); setStatus('Dona répond…'); clearContent(); const d = recipe.duration || recipe.time || recipe.minutes || recipe.duration_min || recipe.duration_minutes; if (d) $('donaInlineContent').append(paragraph('Durée : ' + cap(d, 50) + (typeof d === 'number' ? ' min' : ''), 'dona-inline-meta')); list('Ingrédients', recipe.ingredients); let steps = recipe.steps || recipe.preparation || recipe.instructions || recipe.method; if (typeof steps === 'string') steps = steps.split(/\n+/).filter(Boolean); list('Préparation', steps, true); } function renderSuite(result) { if (!result || typeof result !== 'object' || featureOverlayOpen()) return; state.recentRich = Date.now(); state.rich = true; const kind = String(result.kind || ''); if (kind === 'recipe') { const recipes = Array.isArray(result.recipes) ? result.recipes : []; if (recipes.length) { renderRecipe(recipes[0]); if (recipes.length > 1) { const summary = node('details', 'dona-inline-more'); summary.append(node('summary', '', `Voir ${recipes.length - 1} autre(s) recette(s)`)); const ul = node('ul', 'dona-inline-list'); recipes.slice(1, 12).forEach(rec => ul.append(node('li', '', cap(rec.title || rec.name || 'Recette', 140)))); summary.append(ul); $('donaInlineContent').append(summary); } } else renderText(result.speech || 'Aucune recette trouvée.', result.title || 'Recettes'); return; } show('suite', result.title || 'Réponse de Dona'); setStatus('Dona répond…'); clearContent(); if (result.speech) appendProse(result.speech); if (kind === 'menu' && result.menu) { const m = result.menu; if (Array.isArray(m)) list('Menus', m); else { list('Menus', m.days || m.meals || m.menu || []); list('Liste de courses', m.shopping_list || m.groceries || []); } } else if (kind === 'briefing' || kind === 'daily') { list('Événements', result.briefing?.events); if (result.briefing?.weather) appendProse(JSON.stringify(result.briefing.weather)); } else if (kind === 'documents') list('Documents', result.documents?.found); else if (kind === 'memories') list('Souvenirs', result.memories); else if (kind === 'monitor') list('Supervision', result.monitor?.items || result.monitor?.warnings || []); else if (kind === 'child') list('Questions', result.quiz?.questions || result.quiz || []); else if (kind === 'routine') list('Actions prévues', result.routine?.actions || result.routine || []); } function renderVisual(card) { if (!card || card.schema !== 'dona.visual.v1' || featureOverlayOpen()) return; if (Date.now() - state.recentRich < 1200 && card.type === 'recipe') return; if (card.type === 'recipe') { state.rich = true; renderRecipe(card); } else { state.rich = true; show('visual', card.title || 'Dona'); setStatus('Dona répond…'); clearContent(); if (card.subtitle) appendProse(card.subtitle); list('Informations', card.details); } } function renderFinal(data) { if (!data || typeof data !== 'object' || featureOverlayOpen()) return; if (data.type === 'dona_dynamic') return; if (data.type === 'dona_visual_card') return renderVisual(data); if (Date.now() - state.recentRich < 2800) return; const body = data.content && typeof data.content === 'object' ? data.content : data; const text = body.text || body.speech || (typeof body.content === 'string' ? body.content : ''); if (typeof text !== 'string' || !text.trim()) return; if (state.active && state.text && text.trim() === state.text.trim()) return; renderText(text, ''); } function observeStandaloneResponse() { if (state.displayObserver) return; function inspect() { for (const el of document.querySelectorAll('[role="dialog"], .dona-response-overlay, .dona-display-overlay, [class*="response-overlay"], [class*="dona-display"], [id*="donaDisplay"], [id*="donaResponse"]')) { if (el.closest('#donaInlineScene') || el.classList.contains('dona-inline-suppressed-modal')) continue; if (el.hidden || getComputedStyle(el).display === 'none') continue; const text = (el.textContent || '').slice(0, 3000); if (!/R[ÉE]PONSE\s*[·•-]\s*DONA/i.test(text) || !/Hermes|réponse complète/i.test(text)) continue; if (!state.active) { const parts = [...el.querySelectorAll('h1,h2,h3,p')].map(e => e.textContent.trim()).filter(Boolean); renderText(parts.join('\n\n').slice(0, MAX), ''); } el.classList.add('dona-inline-suppressed-modal'); } } state.displayObserver = new MutationObserver(() => { if (state.scanTimer) return; state.scanTimer = setTimeout(() => { state.scanTimer = 0; inspect(); }, 300); }); state.displayObserver.observe(document.body, {childList:true, subtree:true}); inspect(); } function attachEvents() { const start = Date.now(); function connect() { const socket = window.alfredSocket || (typeof window.io === 'function' ? window.io({reconnection:true}) : null); if (!socket) { if (Date.now() - start < 15000) setTimeout(connect, 150); return; } state.socket = socket; socket.on('dona_suite_v9', renderSuite); socket.on('dona_visual_card', renderVisual); socket.on('alfred_render', renderFinal); socket.on('dona_hermes_stream', renderStream); } connect(); } window.DonaInlineIndexV2 = {hide, showText: renderText}; if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', setup, {once:true}); else setup(); })();