[FIX] ARIA chat: indicatore 'sta scrivendo' + input multi-riga (Shift+Invio)

Risolve #418/#420 (ARIA sembrava non rispondere / serviva riaprire il pannello):
l'attesa della risposta /api/ai/ask (RAG+Claude, alcuni secondi) non mostrava
alcun indicatore -> ora compare 'ARIA sta scrivendo...' con input disabilitato
(anti doppio-invio). Indicatore rispetta prefers-reduced-motion (AGID/WCAG).

Risolve #425: il campo di scrittura era <input> a riga singola -> ora <textarea>
con Invio=invia, Shift+Invio=a capo, auto-crescita e aria-label.

common.js e' statico (no PHP/USR2): bump cache-buster ?v=20260623 su 39 pagine
(public + admin) + version.json 1.24.6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-06-23 18:13:38 +02:00
co-authored by Claude Opus 4.8
parent 4e2e56ec0b
commit bd1e95542c
41 changed files with 92 additions and 46 deletions
+52 -6
View File
@@ -996,8 +996,24 @@ function switchLang(lang) {
var STORAGE_KEY = 'nis2_ai_chat';
var history = [];
var pending = false; // ARIA sta elaborando una risposta (mostra indicatore "sta scrivendo")
try { history = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || '[]'); } catch (e) {}
// Stile una-tantum per l'indicatore "ARIA sta scrivendo…" (puntini animati,
// rispetta prefers-reduced-motion per accessibilita AGID/WCAG).
if (!document.getElementById('nis2-aria-style')) {
var ariaStyle = document.createElement('style');
ariaStyle.id = 'nis2-aria-style';
ariaStyle.textContent =
'@keyframes nis2AriaBlink{0%,80%,100%{opacity:.2}40%{opacity:1}}'
+ '.nis2-aria-dot{display:inline-block;width:6px;height:6px;margin:0 1px;'
+ 'border-radius:50%;background:#7C3AED;animation:nis2AriaBlink 1.2s infinite both}'
+ '.nis2-aria-dot:nth-child(2){animation-delay:.2s}'
+ '.nis2-aria-dot:nth-child(3){animation-delay:.4s}'
+ '@media (prefers-reduced-motion:reduce){.nis2-aria-dot{animation:none;opacity:.6}}';
document.head.appendChild(ariaStyle);
}
function escHtml(s) {
return String(s).replace(/[&<>"']/g, function (c) {
return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c];
@@ -1043,31 +1059,60 @@ function switchLang(lang) {
+ (list || '<div style="color:#9ca3af;font-size:13px;text-align:center;'
+ 'margin-top:24px">Ciao! Chiedimi qualcosa su NIS2: misure di '
+ 'sicurezza, audit, fornitori, incident response…</div>')
+ (pending
? '<div id="ai-chat-typing" aria-live="polite" '
+ 'style="margin-bottom:10px;display:flex;justify-content:flex-start">'
+ '<div style="padding:8px 12px;border-radius:14px;font-size:13px;'
+ 'background:#f3f4f6;color:#6b7280">ARIA sta scrivendo '
+ '<span class="nis2-aria-dot"></span><span class="nis2-aria-dot"></span>'
+ '<span class="nis2-aria-dot"></span></div></div>'
: '')
+ '</div>'
+ '<form id="ai-chat-form" style="display:flex;gap:6px;padding:10px;'
+ 'border-top:1px solid #e5e7eb;background:#fff">'
+ '<input id="ai-chat-input" type="text" placeholder="Scrivi…" '
+ 'autocomplete="off" style="flex:1;padding:10px 12px;border:1px solid '
+ '#d1d5db;border-radius:10px;font-size:14px;font-family:inherit">'
+ '<button type="submit" style="padding:10px 14px;border:none;'
+ 'border-top:1px solid #e5e7eb;background:#fff;align-items:flex-end">'
+ '<textarea id="ai-chat-input" rows="1" '
+ 'placeholder="Scrivi… (Invio per inviare, Shift+Invio per andare a capo)" '
+ 'aria-label="Scrivi un messaggio ad ARIA" autocomplete="off"'
+ (pending ? ' disabled' : '')
+ ' style="flex:1;padding:10px 12px;border:1px solid #d1d5db;border-radius:10px;'
+ 'font-size:14px;font-family:inherit;resize:none;max-height:120px;'
+ 'overflow-y:auto;line-height:1.4"></textarea>'
+ '<button type="submit"' + (pending ? ' disabled' : '')
+ ' style="padding:10px 14px;border:none;'
+ 'border-radius:10px;background:linear-gradient(135deg,#7C3AED,#3B82F6);'
+ 'color:#fff;font-weight:600;cursor:pointer">'
+ 'color:#fff;font-weight:600;cursor:' + (pending ? 'not-allowed' : 'pointer')
+ ';opacity:' + (pending ? '.6' : '1') + '">'
+ '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>'
+ '</button>'
+ '</form>';
document.getElementById('ai-chat-close').onclick = closePanel;
document.getElementById('ai-chat-form').onsubmit = onSubmit;
var inputEl = document.getElementById('ai-chat-input');
if (inputEl) {
// Invio = invia · Shift+Invio = vai a capo (textarea multi-riga)
inputEl.addEventListener('keydown', function (e) {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); onSubmit(e); }
});
// Auto-crescita altezza della textarea fino a max-height
inputEl.addEventListener('input', function () {
inputEl.style.height = 'auto';
inputEl.style.height = Math.min(inputEl.scrollHeight, 120) + 'px';
});
if (!pending) inputEl.focus();
}
var msgs = document.getElementById('ai-chat-msgs');
msgs.scrollTop = msgs.scrollHeight;
}
async function onSubmit(ev) {
ev.preventDefault();
if (pending) return; // evita invii doppi mentre ARIA elabora
var input = document.getElementById('ai-chat-input');
var text = (input.value || '').trim();
if (!text) return;
history.push({ role: 'user', content: text });
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(history));
pending = true;
render();
try {
// AI nativa NIS2 (RAG con KB Multi-Livello già attivo)
@@ -1094,6 +1139,7 @@ function switchLang(lang) {
content: '⚠️ ARIA non risponde in questo momento. Riprova tra poco.'
});
}
pending = false;
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(history));
render();
}