From bafc305503db5c13f31ff5a4a0ad5c6943741285 Mon Sep 17 00:00:00 2001 From: DevEnv nis2-agile Date: Wed, 24 Jun 2026 08:30:10 +0200 Subject: [PATCH] [FEAT] ARIA allineata all'Help contestuale della pagina + pageId canonico (Fase A) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lamentela: "AI non allineata con help online e contesto". Causa: ARIA e Help erano due fonti separate (Help=help.js, ARIA=KB) → divergevano. Fix (fonte unica = help.js, zero duplicazione): - help.js: getContextText(pageId) (testo piano dell'help della pagina) + detectPageId(). - common.js: invia page_id (canonico) + page_help (stesso testo del pannello "?") a /api/ai/ask. - AiController: passa page_id/page_help in userContext. - AIService::askWithRag: inietta "GUIDA CONTESTUALE DELLA PAGINA" nel system prompt (ARIA si allinea all'help mostrato all'utente, non lo contraddice) + usa page_id per il trigger relevanceGrid (più robusto del title). Smoke: ARIA cita "registro dei rischi" dall'help + "Gestione > Rischi". Cache-buster common.js/help.js -> 20260624e (39 pagine). Co-Authored-By: Claude Opus 4.8 (1M context) --- application/controllers/AiController.php | 5 +++ application/services/AIService.php | 20 +++++++++--- public/_app-bi-demo.html | 2 +- public/admin/index.html | 2 +- public/admin/organizations.html | 2 +- public/admin/users.html | 2 +- public/architecture.html | 4 +-- public/assessment.html | 4 +-- public/assets.html | 4 +-- public/calendario.html | 4 +-- public/companies.html | 4 +-- public/competenze.html | 4 +-- public/controlli-periodici.html | 4 +-- public/cross-analysis.html | 2 +- public/dashboard.html | 4 +-- public/guida.html | 4 +-- public/incidents.html | 4 +-- public/integrazioniext.html | 2 +- public/internal-audits.html | 4 +-- public/isms.html | 4 +-- public/js/common.js | 11 ++++++- public/js/help.js | 40 +++++++++++++++++++++++- public/kb.html | 4 +-- public/login.html | 2 +- public/management-review.html | 4 +-- public/misure-requisiti.html | 4 +-- public/mktg-api-doc.html | 2 +- public/normative.html | 4 +-- public/onboarding.html | 2 +- public/organigramma.html | 4 +-- public/policies.html | 4 +-- public/raci.html | 4 +-- public/register.html | 2 +- public/reports.html | 4 +-- public/review-schedule.html | 4 +-- public/risks.html | 4 +-- public/settings.html | 4 +-- public/stakeholder-activities.html | 4 +-- public/stakeholders.html | 4 +-- public/supply-chain.html | 4 +-- public/training.html | 4 +-- public/whistleblowing.html | 4 +-- public/workflow.html | 4 +-- 43 files changed, 138 insertions(+), 74 deletions(-) diff --git a/application/controllers/AiController.php b/application/controllers/AiController.php index d00f734..d54ba95 100644 --- a/application/controllers/AiController.php +++ b/application/controllers/AiController.php @@ -42,11 +42,16 @@ class AiController extends BaseController // Pagina corrente (facoltativa) inviata dal frontend, per dare ad ARIA // contesto sulla schermata da cui l'utente sta scrivendo (ticket #424). $page = mb_substr(trim((string) $this->getParam('page', '')), 0, 120); + // pageId canonico + testo dell'help "?" della pagina (allineamento ARIA↔Help). + $pageId = mb_substr(trim((string) $this->getParam('page_id', '')), 0, 40); + $pageHelp = mb_substr(trim((string) $this->getParam('page_help', '')), 0, 2500); $userContext = [ 'user_id' => $userId, 'organization_id' => $this->getCurrentOrgId(), // può essere null 'consulting_firm_id' => $user['consulting_firm_id'] ?? null, 'page' => $page, + 'page_id' => $pageId, + 'page_help' => $pageHelp, ]; try { diff --git a/application/services/AIService.php b/application/services/AIService.php index 552f76e..caca338 100644 --- a/application/services/AIService.php +++ b/application/services/AIService.php @@ -759,18 +759,30 @@ PROMPT; // Pagina corrente dell'utente (se inviata dal frontend): aiuta ARIA a // dare indicazioni contestuali invece di rimandare a moduli a caso. - $page = trim((string) ($userContext['page'] ?? '')); - if ($page !== '') { + $page = trim((string) ($userContext['page'] ?? '')); + $pageId = trim((string) ($userContext['page_id'] ?? '')); + if ($page !== '' || $pageId !== '') { $systemPrompt .= "\n## CONTESTO UTENTE\n" - . "L'utente si trova attualmente nella pagina: \"" . $page . "\". " + . "L'utente si trova attualmente nella pagina: \"" . ($page !== '' ? $page : $pageId) . "\"" + . ($pageId !== '' ? " (id: {$pageId})" : '') . ". " . "Tieni conto di dove si trova quando dai indicazioni di navigazione.\n"; } + // ALLINEAMENTO ARIA ↔ HELP: inietta il testo dell'help contestuale "?" della + // pagina corrente (stessa fonte help.js che l'utente vede) così le risposte + // sono coerenti con la guida e non divergono. Fonte autorevole per la pagina. + $pageHelp = trim((string) ($userContext['page_help'] ?? '')); + if ($pageHelp !== '') { + $systemPrompt .= "\n## GUIDA CONTESTUALE DELLA PAGINA (stesso testo dell'help «?» che l'utente vede ora)\n" + . "Allinea le tue risposte a questa guida: è la fonte ufficiale per la pagina corrente; non contraddirla.\n" + . $pageHelp . "\n"; + } + // Se l'utente e' nell'Inventario o la domanda riguarda la valutazione di // rilevanza NIS2, inietta la griglia ESATTA delle combo (fonte di verita' // AssetScoringService::GRID) per evitare risposte non corrispondenti alla // finestra reale (ticket #429). - $onInventory = stripos($page, 'asset') !== false || stripos($page, 'inventario') !== false; + $onInventory = $pageId === 'assets' || stripos($page, 'asset') !== false || stripos($page, 'inventario') !== false; if ($onInventory || preg_match('/rilevanz|combo|criticit|punteggio|scoring|valutazione|gv\.?oc/iu', $question)) { $systemPrompt .= $this->relevanceGridBlock(); } diff --git a/public/_app-bi-demo.html b/public/_app-bi-demo.html index 6ee92c0..6c67608 100644 --- a/public/_app-bi-demo.html +++ b/public/_app-bi-demo.html @@ -71,7 +71,7 @@ - + - + diff --git a/public/admin/organizations.html b/public/admin/organizations.html index 20e34c1..18dd929 100644 --- a/public/admin/organizations.html +++ b/public/admin/organizations.html @@ -148,7 +148,7 @@ - + diff --git a/public/admin/users.html b/public/admin/users.html index 77f78dd..4168f72 100644 --- a/public/admin/users.html +++ b/public/admin/users.html @@ -170,7 +170,7 @@ - + diff --git a/public/architecture.html b/public/architecture.html index 650a3ee..ace279b 100644 --- a/public/architecture.html +++ b/public/architecture.html @@ -1091,11 +1091,11 @@ - + - + - + @@ -167,7 +167,7 @@ - + - + @@ -374,7 +374,7 @@ - + - + - + - + - + - + - + diff --git a/public/controlli-periodici.html b/public/controlli-periodici.html index 323a7f1..adcad53 100644 --- a/public/controlli-periodici.html +++ b/public/controlli-periodici.html @@ -137,11 +137,11 @@ - + - + - + diff --git a/public/dashboard.html b/public/dashboard.html index da57aca..d5d1f0d 100644 --- a/public/dashboard.html +++ b/public/dashboard.html @@ -144,7 +144,7 @@ - + @@ -156,7 +156,7 @@ - + - + @@ -1154,7 +1154,7 @@ - + - + @@ -364,7 +364,7 @@ - + + - + - + - + @@ -197,7 +197,7 @@ - + diff --git a/public/js/common.js b/public/js/common.js index 1ea0219..2cf05dc 100644 --- a/public/js/common.js +++ b/public/js/common.js @@ -1132,6 +1132,15 @@ function switchLang(lang) { // Pagina corrente: aiuta ARIA a dare indicazioni contestuali (ticket #424). var pageName = (document.title || '').replace(/\s*[—|·-]\s*NIS2.*$/i, '').trim() || (location.pathname.split('/').pop() || ''); + // pageId canonico + testo dell'help "?" della pagina → ARIA risponde + // ALLINEATA a ciò che l'utente vede nella guida (fonte unica help.js). + var pageId = '', pageHelp = ''; + try { + if (typeof HelpSystem !== 'undefined') { + if (HelpSystem.detectPageId) pageId = HelpSystem.detectPageId() || ''; + if (HelpSystem.getContextText) pageHelp = HelpSystem.getContextText(pageId) || ''; + } + } catch (e) { /* help non disponibile → ARIA prosegue senza */ } var r = await fetch('/api/ai/ask', { method: 'POST', headers: { @@ -1139,7 +1148,7 @@ function switchLang(lang) { 'Authorization': t ? ('Bearer ' + t) : '', 'X-Organization-Id': orgId }, - body: JSON.stringify({ question: text, history: history, page: pageName }) + body: JSON.stringify({ question: text, history: history, page: pageName, page_id: pageId, page_help: pageHelp }) }); var resp = await r.json(); var answer = (resp && resp.success && resp.data diff --git a/public/js/help.js b/public/js/help.js index 56906ac..8254a56 100644 --- a/public/js/help.js +++ b/public/js/help.js @@ -1859,6 +1859,42 @@ const HelpSystem = (function () { ''; } + /** + * Versione testo-piano del contenuto di help della pagina (per ARIA). + * Stessa fonte del pannello "?" → l'AI resta ALLINEATA all'help mostrato all'utente. + * @param {string} [pageId] - se assente, rileva la pagina corrente. + * @param {number} [maxLen] - cap caratteri (default 2200). + * @returns {string} testo piano, '' se la pagina non ha help. + */ + function _stripHtml(s) { + return String(s || '') + .replace(/<[^>]+>/g, '') + .replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&') + .replace(/≥/g, '≥').replace(/≤/g, '≤').replace(/ /g, ' ') + .replace(/\s+/g, ' ').trim(); + } + function getContextText(pageId, maxLen) { + if (!pageId) pageId = _detectPage(); + var d = pageId && _helpContent[pageId]; + if (!d) return ''; + var out = []; + if (d.title) out.push(_stripHtml(d.title).replace(/^Guida\s*-\s*/i, '').trim()); + if (d.intro) out.push(_stripHtml(d.intro)); + (d.sections || []).forEach(function (s) { + var items = (s.items || []).map(_stripHtml).join(' '); + out.push(('• ' + (s.heading || '') + ': ' + items).trim()); + }); + if (d.references && d.references.length) { + out.push('Riferimenti: ' + d.references.map(_stripHtml).join('; ')); + } + var txt = out.join('\n'); + maxLen = maxLen || 2200; + if (txt.length > maxLen) txt = txt.slice(0, maxLen) + '…'; + return txt; + } + /** pageId canonico della pagina corrente (o '' se non mappata). */ + function detectPageId() { return _detectPage() || ''; } + /** * Inizializza il sistema di help: inietta gli stili e aggiunge il pulsante help * nell'header della pagina corrente (se applicabile). @@ -1883,7 +1919,9 @@ const HelpSystem = (function () { showHelp: showHelp, addHelpButton: addHelpButton, inlineHelp: inlineHelp, - getAvailablePages: getAvailablePages + getAvailablePages: getAvailablePages, + getContextText: getContextText, // testo help della pagina per ARIA (allineamento) + detectPageId: detectPageId // pageId canonico corrente }; })(); diff --git a/public/kb.html b/public/kb.html index 6942aa5..406e9b4 100644 --- a/public/kb.html +++ b/public/kb.html @@ -151,7 +151,7 @@ - + @@ -163,7 +163,7 @@ - + - + - + - + - + - + + diff --git a/public/normative.html b/public/normative.html index fd6944b..735d618 100644 --- a/public/normative.html +++ b/public/normative.html @@ -113,7 +113,7 @@ - + @@ -125,7 +125,7 @@ - + - + - + - + diff --git a/public/policies.html b/public/policies.html index df10351..0d91f85 100644 --- a/public/policies.html +++ b/public/policies.html @@ -334,7 +334,7 @@ - + @@ -346,7 +346,7 @@ - + - + - + diff --git a/public/register.html b/public/register.html index ae06c8a..7a5dfa4 100644 --- a/public/register.html +++ b/public/register.html @@ -269,7 +269,7 @@ - + - + @@ -456,7 +456,7 @@ - + - + - + - + @@ -507,7 +507,7 @@ - + - + @@ -701,7 +701,7 @@ - + - + - + - + - + - + @@ -490,7 +490,7 @@ - + - + @@ -305,7 +305,7 @@ - + - + @@ -220,7 +220,7 @@ - + - + - +