- public/guida.html, index-en.html, service-continuity.html - public/js/ai-assistant.js, bug-reporter.js (FAB supporto) - public/mobile-conversion.css/js - index.html, common.js, help.js, risks.html: aggiornamenti UI/help Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
549 lines
21 KiB
JavaScript
549 lines
21 KiB
JavaScript
/**
|
|
* AgileHub — AI Assistant Widget (L1 Self-Service)
|
|
* Embeddabile in qualsiasi prodotto della suite.
|
|
*
|
|
* Flusso: L1 AI (KB search) -> feedback -> L2 Formatore -> L3 Ticket
|
|
*
|
|
* Uso:
|
|
* <script src="js/ai-assistant.js"
|
|
* data-product="TRPG"
|
|
* data-tenant-id="3"
|
|
* data-api-url="https://agilehub.agile.software"
|
|
* data-user-name="Mario Rossi"
|
|
* data-user-email="mario@studio.it"
|
|
* data-user-role="consultant"
|
|
* data-lang="it">
|
|
* </script>
|
|
*
|
|
* Prerequisiti backend:
|
|
* - POST /api/support-sessions (crea sessione)
|
|
* - POST /api/support-sessions/:id/message (AI risponde con KB)
|
|
* - POST /api/support-sessions/:id/resolve (feedback positivo)
|
|
* - POST /api/support-sessions/:id/forward-expert (escala L2)
|
|
* - POST /api/support-sessions/:id/escalate (escala L3 ticket)
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
// ==================== CONFIG ====================
|
|
var script = document.currentScript || document.querySelector('script[data-product][data-api-url]');
|
|
var CFG = {
|
|
apiUrl: (script && script.dataset.apiUrl) || '',
|
|
product: (script && script.dataset.product) || 'GENERIC',
|
|
tenantId: (script && script.dataset.tenantId) || '1',
|
|
userName: (script && script.dataset.userName) || '',
|
|
userEmail: (script && script.dataset.userEmail) || '',
|
|
userRole: (script && script.dataset.userRole) || '',
|
|
lang: (script && script.dataset.lang) || 'it'
|
|
};
|
|
|
|
var LABELS = {
|
|
it: {
|
|
title: 'Assistente AgileHub',
|
|
placeholder: 'Scrivi una domanda...',
|
|
viewing: 'Stai guardando',
|
|
suggestions_intro: 'Posso aiutarti con:',
|
|
feedback_yes: 'Si, grazie',
|
|
feedback_no: 'No, aiuto',
|
|
escalation_title: 'Come posso aiutarti meglio?',
|
|
btn_rephrase: 'Riformula la domanda',
|
|
btn_expert: 'Chiedi al formatore',
|
|
btn_bug: 'Segnala un bug',
|
|
expert_sent: 'Domanda inoltrata al formatore. Riceverai una notifica quando la risposta sara pronta.',
|
|
expert_time: 'Tempo stimato: entro 4 ore lavorative.',
|
|
resolved_thanks: 'Grazie per il feedback! La tua valutazione migliora l\'assistente.',
|
|
preparing: 'Mi sto preparando...',
|
|
thinking: 'Sto pensando...',
|
|
source: 'Fonte',
|
|
resume: 'Riprendiamo la conversazione.',
|
|
mic_unsupported: 'Microfono non supportato dal browser.',
|
|
error_generic: 'Errore di connessione. Riprova tra poco.'
|
|
},
|
|
en: {
|
|
title: 'AgileHub Assistant',
|
|
placeholder: 'Ask a question...',
|
|
viewing: 'You are viewing',
|
|
suggestions_intro: 'I can help you with:',
|
|
feedback_yes: 'Yes, thanks',
|
|
feedback_no: 'No, help me',
|
|
escalation_title: 'How can I help you better?',
|
|
btn_rephrase: 'Rephrase question',
|
|
btn_expert: 'Ask an expert',
|
|
btn_bug: 'Report a bug',
|
|
expert_sent: 'Your question has been forwarded to an expert. You\'ll be notified when the answer is ready.',
|
|
expert_time: 'Estimated time: within 4 business hours.',
|
|
resolved_thanks: 'Thanks for the feedback! Your rating improves the assistant.',
|
|
preparing: 'Preparing...',
|
|
thinking: 'Thinking...',
|
|
source: 'Source',
|
|
resume: 'Let\'s pick up where we left off.',
|
|
mic_unsupported: 'Microphone not supported by this browser.',
|
|
error_generic: 'Connection error. Please try again shortly.'
|
|
}
|
|
};
|
|
|
|
function t(key) {
|
|
var lang = CFG.lang;
|
|
return (LABELS[lang] && LABELS[lang][key]) || (LABELS.it[key]) || key;
|
|
}
|
|
|
|
// ==================== STATE ====================
|
|
var sessionId = null;
|
|
var messages = []; // { role: 'user'|'assistant'|'system', content, sources?, feedbackShown? }
|
|
var panelOpen = false;
|
|
var recognition = null;
|
|
var isRecording = false;
|
|
|
|
// ==================== API HELPERS ====================
|
|
function apiHeaders() {
|
|
var h = { 'Content-Type': 'application/json', 'x-tenant-id': CFG.tenantId };
|
|
// Try to get JWT from the host app
|
|
if (typeof Api !== 'undefined' && Api.auth && Api.auth.getToken) {
|
|
var tok = Api.auth.getToken();
|
|
if (tok) h['Authorization'] = 'Bearer ' + tok;
|
|
}
|
|
var stored = typeof localStorage !== 'undefined' ? localStorage.getItem('nexus_token') : null;
|
|
if (!h['Authorization'] && stored) h['Authorization'] = 'Bearer ' + stored;
|
|
return h;
|
|
}
|
|
|
|
function apiPost(path, body) {
|
|
return fetch(CFG.apiUrl + path, {
|
|
method: 'POST', headers: apiHeaders(), body: JSON.stringify(body)
|
|
}).then(function (r) { return r.json(); });
|
|
}
|
|
|
|
function apiGet(path) {
|
|
return fetch(CFG.apiUrl + path, { headers: apiHeaders() }).then(function (r) { return r.json(); });
|
|
}
|
|
|
|
// ==================== CONTEXT DETECTION ====================
|
|
function detectPage() {
|
|
// Try HelpSystem if available
|
|
if (typeof HelpSystem !== 'undefined' && HelpSystem._detectPage) {
|
|
return HelpSystem._detectPage();
|
|
}
|
|
// Fallback: extract from URL hash or path
|
|
var hash = location.hash.replace('#', '').replace('/', '') || '';
|
|
return hash || document.title || '';
|
|
}
|
|
|
|
function getContextualSuggestions() {
|
|
if (typeof HelpSystem === 'undefined') return [];
|
|
var page = typeof HelpSystem._detectPage === 'function' ? HelpSystem._detectPage() : null;
|
|
var helpData = null;
|
|
if (page && HelpSystem._helpContent) helpData = HelpSystem._helpContent[page];
|
|
if (page && HelpSystem._help) {
|
|
var raw = HelpSystem._help[page];
|
|
if (raw) helpData = raw[CFG.lang] || raw.it || raw;
|
|
}
|
|
if (!helpData) return [];
|
|
|
|
var suggestions = [];
|
|
if (helpData.faq) {
|
|
suggestions = helpData.faq.slice(0, 3).map(function (f) {
|
|
return (typeof f === 'string') ? f : (f.question || f.q || '');
|
|
});
|
|
} else if (helpData.sections) {
|
|
suggestions = helpData.sections.slice(0, 3).map(function (s) {
|
|
if (!s.heading) return '';
|
|
if (typeof s.heading === 'object') return s.heading[CFG.lang] || s.heading.it || '';
|
|
return s.heading;
|
|
});
|
|
}
|
|
return suggestions.filter(Boolean);
|
|
}
|
|
|
|
// ==================== SESSION MANAGEMENT ====================
|
|
function createSession(cb) {
|
|
apiPost('/api/support-sessions', {
|
|
product: CFG.product,
|
|
callerEmail: CFG.userEmail,
|
|
callerName: CFG.userName
|
|
}).then(function (res) {
|
|
if (res.success && res.data) {
|
|
sessionId = res.data.sessionId || res.data.id;
|
|
if (res.data.welcomeMessage) {
|
|
messages.push({ role: 'assistant', content: res.data.welcomeMessage });
|
|
}
|
|
// Store session for resume
|
|
try { sessionStorage.setItem('agilehub_ai_session_' + CFG.product, sessionId); } catch (e) {}
|
|
}
|
|
if (cb) cb();
|
|
}).catch(function () {
|
|
messages.push({ role: 'system', content: t('error_generic') });
|
|
if (cb) cb();
|
|
});
|
|
}
|
|
|
|
function tryResumeSession(cb) {
|
|
try {
|
|
var stored = sessionStorage.getItem('agilehub_ai_session_' + CFG.product);
|
|
if (stored) {
|
|
sessionId = stored;
|
|
messages.push({ role: 'system', content: t('resume') });
|
|
cb(true);
|
|
return;
|
|
}
|
|
} catch (e) {}
|
|
cb(false);
|
|
}
|
|
|
|
function sendMessage(text, cb) {
|
|
if (!sessionId) {
|
|
createSession(function () { if (sessionId) sendMessage(text, cb); else cb(); });
|
|
return;
|
|
}
|
|
messages.push({ role: 'user', content: text });
|
|
render();
|
|
|
|
apiPost('/api/support-sessions/' + sessionId + '/message', {
|
|
content: text
|
|
}).then(function (res) {
|
|
if (res.success && res.data) {
|
|
var reply = res.data.reply || res.data.message || res.data.answer || '';
|
|
var sources = res.data.knowledgeArticles || res.data.sources || [];
|
|
messages.push({ role: 'assistant', content: reply, sources: sources, feedbackShown: true });
|
|
} else {
|
|
messages.push({ role: 'assistant', content: (res.error && res.error.message) || t('error_generic') });
|
|
}
|
|
cb();
|
|
}).catch(function () {
|
|
messages.push({ role: 'assistant', content: t('error_generic') });
|
|
cb();
|
|
});
|
|
}
|
|
|
|
function resolveSession() {
|
|
if (!sessionId) return;
|
|
apiPost('/api/support-sessions/' + sessionId + '/resolve', {
|
|
rating: 5, resolutionSummary: 'auto'
|
|
});
|
|
}
|
|
|
|
function forwardToExpert(question, context) {
|
|
if (!sessionId) return;
|
|
return apiPost('/api/support-sessions/' + sessionId + '/forward-expert', {
|
|
question: question, context: context
|
|
});
|
|
}
|
|
|
|
function escalateToTicket(reason) {
|
|
if (!sessionId) return;
|
|
return apiPost('/api/support-sessions/' + sessionId + '/escalate', {
|
|
reason: reason
|
|
});
|
|
}
|
|
|
|
// ==================== VOICE INPUT ====================
|
|
function initVoice() {
|
|
var SR = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
if (!SR) return null;
|
|
recognition = new SR();
|
|
recognition.lang = CFG.lang === 'en' ? 'en-US' : 'it-IT';
|
|
recognition.interimResults = true;
|
|
recognition.continuous = false;
|
|
return recognition;
|
|
}
|
|
|
|
function toggleMic() {
|
|
if (!recognition) {
|
|
if (!initVoice()) {
|
|
messages.push({ role: 'system', content: t('mic_unsupported') });
|
|
render();
|
|
return;
|
|
}
|
|
}
|
|
if (isRecording) {
|
|
recognition.stop();
|
|
isRecording = false;
|
|
render();
|
|
return;
|
|
}
|
|
var input = document.getElementById('aia-input');
|
|
recognition.onresult = function (e) {
|
|
var transcript = '';
|
|
for (var i = e.resultIndex; i < e.results.length; i++) {
|
|
transcript += e.results[i][0].transcript;
|
|
}
|
|
if (input) input.value = transcript;
|
|
if (e.results[e.results.length - 1].isFinal) {
|
|
isRecording = false;
|
|
render();
|
|
if (transcript.trim()) submitInput(transcript.trim());
|
|
}
|
|
};
|
|
recognition.onerror = function () { isRecording = false; render(); };
|
|
recognition.onend = function () { isRecording = false; render(); };
|
|
recognition.start();
|
|
isRecording = true;
|
|
render();
|
|
}
|
|
|
|
// ==================== RENDERING ====================
|
|
function esc(s) {
|
|
return String(s).replace(/[&<>"']/g, function (c) {
|
|
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c];
|
|
});
|
|
}
|
|
|
|
function renderMessages() {
|
|
return messages.map(function (m, idx) {
|
|
var isUser = m.role === 'user';
|
|
var isSystem = m.role === 'system';
|
|
|
|
var bubble = '<div style="margin-bottom:10px;display:flex;justify-content:'
|
|
+ (isUser ? 'flex-end' : 'flex-start') + '">'
|
|
+ '<div style="max-width:82%;padding:10px 14px;border-radius:14px;font-size:13px;line-height:1.5;'
|
|
+ 'background:' + (isUser ? 'linear-gradient(135deg,#7C3AED,#3B82F6)' : isSystem ? '#FEF3C7' : '#f3f4f6')
|
|
+ ';color:' + (isUser ? '#fff' : '#111827')
|
|
+ ';white-space:pre-wrap">'
|
|
+ esc(m.content);
|
|
|
|
// Sources
|
|
if (m.sources && m.sources.length > 0) {
|
|
bubble += '<div style="margin-top:8px;padding-top:6px;border-top:1px solid rgba(0,0,0,.1);font-size:11px;color:#6B7280">';
|
|
m.sources.forEach(function (s, si) {
|
|
bubble += '<div>' + t('source') + ' [' + (si + 1) + '] ' + esc(s.question || s.title || '') + '</div>';
|
|
});
|
|
bubble += '</div>';
|
|
}
|
|
|
|
bubble += '</div></div>';
|
|
|
|
// Feedback buttons
|
|
if (m.feedbackShown && m.role === 'assistant' && idx === messages.length - 1) {
|
|
bubble += '<div id="aia-feedback" style="display:flex;gap:8px;justify-content:flex-start;margin-bottom:10px">'
|
|
+ '<button onclick="window.__aiaFeedback(true)" style="padding:6px 14px;border-radius:8px;border:1px solid #D1D5DB;'
|
|
+ 'background:#fff;cursor:pointer;font-size:12px;color:#059669">👍 ' + esc(t('feedback_yes')) + '</button>'
|
|
+ '<button onclick="window.__aiaFeedback(false)" style="padding:6px 14px;border-radius:8px;border:1px solid #D1D5DB;'
|
|
+ 'background:#fff;cursor:pointer;font-size:12px;color:#DC2626">👎 ' + esc(t('feedback_no')) + '</button>'
|
|
+ '</div>';
|
|
}
|
|
|
|
return bubble;
|
|
}).join('');
|
|
}
|
|
|
|
function renderEscalation() {
|
|
return '<div id="aia-escalation" style="background:#F9FAFB;border-radius:12px;padding:14px;margin-bottom:10px">'
|
|
+ '<div style="font-weight:600;margin-bottom:10px;font-size:13px">' + esc(t('escalation_title')) + '</div>'
|
|
+ '<button onclick="window.__aiaEscalate(\'rephrase\')" style="display:block;width:100%;text-align:left;padding:10px 12px;'
|
|
+ 'border:1px solid #E5E7EB;border-radius:8px;background:#fff;cursor:pointer;margin-bottom:6px;font-size:12px">'
|
|
+ '🔄 ' + esc(t('btn_rephrase')) + '</button>'
|
|
+ '<button onclick="window.__aiaEscalate(\'expert\')" style="display:block;width:100%;text-align:left;padding:10px 12px;'
|
|
+ 'border:1px solid #E5E7EB;border-radius:8px;background:#fff;cursor:pointer;margin-bottom:6px;font-size:12px">'
|
|
+ '👨‍🏫 ' + esc(t('btn_expert')) + '</button>'
|
|
+ '<button onclick="window.__aiaEscalate(\'bug\')" style="display:block;width:100%;text-align:left;padding:10px 12px;'
|
|
+ 'border:1px solid #E5E7EB;border-radius:8px;background:#fff;cursor:pointer;font-size:12px">'
|
|
+ '🐛 ' + esc(t('btn_bug')) + '</button>'
|
|
+ '</div>';
|
|
}
|
|
|
|
function render() {
|
|
var panel = document.getElementById('aia-panel');
|
|
if (!panel) return;
|
|
|
|
var page = detectPage();
|
|
var suggestions = (messages.length === 0) ? getContextualSuggestions() : [];
|
|
var showEscalation = panel.dataset.showEscalation === '1';
|
|
|
|
panel.innerHTML =
|
|
// Header
|
|
'<div style="padding:14px 16px;background:linear-gradient(135deg,#7C3AED,#3B82F6);color:#fff;'
|
|
+ 'display:flex;justify-content:space-between;align-items:center;border-radius:16px 16px 0 0">'
|
|
+ '<div style="display:flex;align-items:center;gap:8px;font-weight:600;font-size:14px">'
|
|
+ '<i class="fa-solid fa-wand-magic-sparkles"></i> ' + esc(t('title'))
|
|
+ '</div>'
|
|
+ '<button onclick="window.__aiaToggle()" style="background:none;border:none;color:#fff;font-size:18px;cursor:pointer">×</button>'
|
|
+ '</div>'
|
|
|
|
// Context bar
|
|
+ (page ? '<div style="padding:8px 16px;background:#EDE9FE;font-size:11px;color:#6D28D9">'
|
|
+ '📍 ' + esc(t('viewing')) + ': <strong>' + esc(page) + '</strong></div>' : '')
|
|
|
|
// Messages area
|
|
+ '<div id="aia-msgs" style="flex:1;overflow-y:auto;padding:14px 16px;background:#fafafa;min-height:200px">'
|
|
+ (messages.length === 0 && suggestions.length > 0
|
|
? '<div style="color:#6B7280;font-size:13px;margin-bottom:12px">' + esc(t('suggestions_intro')) + '</div>'
|
|
+ suggestions.map(function (s) {
|
|
return '<button onclick="window.__aiaSuggest(\'' + esc(s).replace(/'/g, "\\'") + '\')" '
|
|
+ 'style="display:block;width:100%;text-align:left;padding:8px 12px;margin-bottom:6px;'
|
|
+ 'border:1px solid #E5E7EB;border-radius:8px;background:#fff;cursor:pointer;font-size:12px;color:#4B5563">'
|
|
+ '💡 ' + esc(s) + '</button>';
|
|
}).join('')
|
|
: '')
|
|
+ renderMessages()
|
|
+ (showEscalation ? renderEscalation() : '')
|
|
+ '</div>'
|
|
|
|
// Input bar
|
|
+ '<form id="aia-form" onsubmit="return window.__aiaSubmit(event)" '
|
|
+ 'style="display:flex;gap:6px;padding:10px;border-top:1px solid #E5E7EB;background:#fff;border-radius:0 0 16px 16px">'
|
|
+ '<input id="aia-input" type="text" placeholder="' + esc(t('placeholder')) + '" autocomplete="off" '
|
|
+ 'style="flex:1;padding:10px 12px;border:1px solid #D1D5DB;border-radius:10px;font-size:13px;font-family:inherit">'
|
|
+ '<button type="button" onclick="window.__aiaMic()" style="padding:8px;border:none;border-radius:10px;'
|
|
+ 'background:' + (isRecording ? '#EF4444' : '#F3F4F6') + ';cursor:pointer;font-size:16px" title="Voce">'
|
|
+ (isRecording ? '⏹' : '🎤') + '</button>'
|
|
+ '<button type="submit" style="padding:8px 14px;border:none;border-radius:10px;'
|
|
+ 'background:linear-gradient(135deg,#7C3AED,#3B82F6);color:#fff;font-weight:600;cursor:pointer">'
|
|
+ '<i class="fa-solid fa-paper-plane"></i></button>'
|
|
+ '</form>';
|
|
|
|
// Scroll to bottom
|
|
var msgsEl = document.getElementById('aia-msgs');
|
|
if (msgsEl) msgsEl.scrollTop = msgsEl.scrollHeight;
|
|
}
|
|
|
|
// ==================== HANDLERS ====================
|
|
function submitInput(text) {
|
|
if (!text) return;
|
|
var input = document.getElementById('aia-input');
|
|
if (input) input.value = '';
|
|
var panel = document.getElementById('aia-panel');
|
|
if (panel) panel.dataset.showEscalation = '0';
|
|
|
|
// Show thinking
|
|
messages.push({ role: 'assistant', content: t('thinking') });
|
|
render();
|
|
messages.pop(); // remove thinking
|
|
|
|
sendMessage(text, function () { render(); });
|
|
}
|
|
|
|
window.__aiaToggle = function () {
|
|
panelOpen = !panelOpen;
|
|
var panel = document.getElementById('aia-panel');
|
|
var fab = document.getElementById('ai-chat-fab');
|
|
if (panel) panel.style.display = panelOpen ? 'flex' : 'none';
|
|
if (fab) fab.style.display = panelOpen ? 'none' : 'flex';
|
|
if (panelOpen && messages.length === 0) {
|
|
tryResumeSession(function (resumed) {
|
|
if (!resumed) {
|
|
createSession(function () { render(); });
|
|
} else {
|
|
render();
|
|
}
|
|
});
|
|
}
|
|
if (panelOpen) render();
|
|
};
|
|
|
|
window.__aiaSubmit = function (e) {
|
|
e.preventDefault();
|
|
var input = document.getElementById('aia-input');
|
|
var text = (input && input.value || '').trim();
|
|
if (text) submitInput(text);
|
|
return false;
|
|
};
|
|
|
|
window.__aiaSuggest = function (text) { submitInput(text); };
|
|
window.__aiaMic = function () { toggleMic(); };
|
|
|
|
window.__aiaFeedback = function (positive) {
|
|
var fbEl = document.getElementById('aia-feedback');
|
|
if (fbEl) fbEl.remove();
|
|
|
|
if (positive) {
|
|
resolveSession();
|
|
messages.push({ role: 'system', content: t('resolved_thanks') });
|
|
render();
|
|
} else {
|
|
// Show escalation options
|
|
var panel = document.getElementById('aia-panel');
|
|
if (panel) panel.dataset.showEscalation = '1';
|
|
render();
|
|
}
|
|
};
|
|
|
|
window.__aiaEscalate = function (action) {
|
|
var panel = document.getElementById('aia-panel');
|
|
if (panel) panel.dataset.showEscalation = '0';
|
|
|
|
if (action === 'rephrase') {
|
|
render();
|
|
var input = document.getElementById('aia-input');
|
|
if (input) input.focus();
|
|
return;
|
|
}
|
|
|
|
if (action === 'expert') {
|
|
var lastUserMsg = '';
|
|
var lastAiMsg = '';
|
|
for (var i = messages.length - 1; i >= 0; i--) {
|
|
if (!lastAiMsg && messages[i].role === 'assistant') lastAiMsg = messages[i].content;
|
|
if (!lastUserMsg && messages[i].role === 'user') lastUserMsg = messages[i].content;
|
|
if (lastUserMsg && lastAiMsg) break;
|
|
}
|
|
forwardToExpert(lastUserMsg, lastAiMsg).then(function () {
|
|
messages.push({ role: 'system', content: t('expert_sent') + '\n' + t('expert_time') });
|
|
render();
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (action === 'bug') {
|
|
var reason = '';
|
|
for (var j = messages.length - 1; j >= 0; j--) {
|
|
if (messages[j].role === 'user') { reason = messages[j].content; break; }
|
|
}
|
|
escalateToTicket(reason).then(function () {
|
|
messages.push({ role: 'system', content: 'Ticket tecnico creato. Il team di sviluppo lo prendera in carico.' });
|
|
render();
|
|
});
|
|
return;
|
|
}
|
|
};
|
|
|
|
// ==================== INIT ====================
|
|
function init() {
|
|
// Ensure FAB exists (may have been created by product's app.js)
|
|
var fab = document.getElementById('ai-chat-fab');
|
|
if (!fab) {
|
|
fab = document.createElement('button');
|
|
fab.id = 'ai-chat-fab';
|
|
fab.setAttribute('aria-label', 'Chiedi ad ARIA');
|
|
fab.style.cssText = 'position:fixed;bottom:24px;right:24px;width:56px;height:56px;'
|
|
+ 'border-radius:50%;border:none;cursor:pointer;z-index:9998;'
|
|
+ 'background:linear-gradient(135deg,#7C3AED,#3B82F6);color:#fff;'
|
|
+ 'box-shadow:0 6px 20px rgba(124,58,237,.4);font-size:22px;'
|
|
+ 'display:flex;align-items:center;justify-content:center';
|
|
fab.innerHTML = '<i class="fa-solid fa-wand-magic-sparkles"></i>';
|
|
document.body.appendChild(fab);
|
|
} else {
|
|
// Rimuovi il vecchio pannello AI del prodotto (evita doppia finestra)
|
|
var oldPanel = document.getElementById('ai-chat-panel');
|
|
if (oldPanel) oldPanel.remove();
|
|
|
|
// Clona il FAB per eliminare TUTTI gli addEventListener precedenti
|
|
var cleanFab = fab.cloneNode(true);
|
|
fab.parentNode.replaceChild(cleanFab, fab);
|
|
fab = cleanFab;
|
|
}
|
|
fab.style.display = 'flex';
|
|
fab.style.alignItems = 'center';
|
|
fab.style.justifyContent = 'center';
|
|
fab.onclick = window.__aiaToggle;
|
|
|
|
// Ensure panel exists
|
|
var panel = document.getElementById('aia-panel');
|
|
if (!panel) {
|
|
panel = document.createElement('div');
|
|
panel.id = 'aia-panel';
|
|
panel.style.cssText = 'position:fixed;bottom:90px;right:24px;width:380px;max-height:560px;'
|
|
+ 'background:#fff;border-radius:16px;box-shadow:0 12px 32px rgba(0,0,0,.2);'
|
|
+ 'display:none;z-index:9999;overflow:hidden;flex-direction:column;'
|
|
+ 'font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif';
|
|
document.body.appendChild(panel);
|
|
}
|
|
panel.dataset.showEscalation = '0';
|
|
}
|
|
|
|
// Auto-init when DOM is ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
})();
|