Nota: dopo aver pubblicato, potrebbe essere necessario pulire la cache del proprio browser per vedere i cambiamenti.
- Firefox / Safari: tieni premuto il tasto delle maiuscole Shift e fai clic su Ricarica, oppure premi Ctrl-F5 o Ctrl-R (⌘-R su Mac)
- Google Chrome: premi Ctrl-Shift-R (⌘-Shift-R su un Mac)
- Edge: tieni premuto il tasto Ctrl e fai clic su Aggiorna, oppure premi Ctrl-F5.
/* ===================== MPAI – CHAT APP (no inline script) ===================== */
// Guardia anti-doppio-caricamento (strumentata)
if (window.__MP_DASHBOARD_LOADED__) {
const src = (document.currentScript && document.currentScript.src) || '(inline)';
try {
const u = new URL(src, location.origin);
console.warn('MP Dashboard già caricata – stop. Source:', src, 'from=', u.searchParams.get('from') || '(n/a)');
} catch {
console.warn('MP Dashboard già caricata – stop. Source:', src);
}
console.trace('Stack doppio load');
throw new Error('MP Dashboard già caricata');
}
window.__MP_DASHBOARD_LOADED__ = true;
// Se qualche script ha rotto $, lo ri-aggancio a jQuery
if (!(window.$ && window.$.fn && typeof window.$.Deferred === 'function')) {
window.$ = window.jQuery;
}
// Evita duplicato $mp: esponilo solo se non esiste
window.$mp = window.$mp || (s => document.querySelector(s));
// 4) WRAPPER: tutto il codice MPAI sta DENTRO questa IIFE.
// Qui dentro puoi dichiarare tranquillamente qs/qsa: sono LOCALI (non globali).
(function () {
console.log('🧭 MPAI: init…');
// Helper VANILLA **LOCALI** (non globali, niente conflitti)
const qs = (sel, root=document) => root.querySelector(sel);
const qsa = (sel, root=document) => Array.from(root.querySelectorAll(sel));
/* ============================================================
MPAI – CHAT APP (no inline script)
=============================================================== */
/** Mostra la UI AI e nasconde i box legacy */
window.showMpAI = function(){
['api-settings','project-status','chatgpt-plus','test-tools','activity-log']
.forEach(id => { const el=document.getElementById(id); if(el) el.style.display='none'; });
const ai = document.getElementById('mpAI');
if (ai){ ai.style.display='block'; ai.scrollIntoView({behavior:'smooth'}); }
};
/** Stato locale della chat */
const mpaiState = {
currentProject: null, // “progetto reale” (opzionale)
sessionId: null, // id bozza locale
sessionMeta: null, // {title, updated}
history: [],
attachments: []
};
/** Shortcuts */
const $mp = (s)=>document.querySelector(s);
/** UI helpers */
function mpaiAppendMsg(role, content){
const chatEl = $mp('#mpai-chat');
if (!chatEl) return;
const div = document.createElement('div');
div.className = 'mpai-msg ' + (role === 'user' ? 'user' : (role === 'assistant' ? 'assistant' : 'error'));
div.innerHTML = `
<div class="role">${role==='user'?'Tu':role==='assistant'?'GPT':'Errore'}</div>
<div class="content" style="white-space:pre-wrap">${content}</div>`;
chatEl.appendChild(div);
chatEl.scrollTop = chatEl.scrollHeight;
}
function mpaiRenderFiles(){
const fileListEl = $mp('#mpai-file-list');
if (!fileListEl) return;
fileListEl.innerHTML = '';
mpaiState.attachments.forEach((a,i)=>{
const pill = document.createElement('span');
pill.className = 'mpai-filepill';
pill.innerHTML = `<span title="Allegato">${a.name}</span><button title="Rimuovi">✕</button>`;
pill.querySelector('button').onclick = ()=>{ mpaiState.attachments.splice(i,1); mpaiRenderFiles(); };
fileListEl.appendChild(pill);
});
}
/** Persistenza “per progetto” (cronologia locale) */
function mpaiSaveLocal(){
if (!mpaiState.currentProject) return;
localStorage.setItem('mpai.hist.'+mpaiState.currentProject, JSON.stringify(mpaiState.history.slice(-200)));
}
function mpaiLoadLocal(project){
const chatEl = $mp('#mpai-chat');
if (!chatEl) return;
const raw = localStorage.getItem('mpai.hist.'+project);
mpaiState.history = raw ? JSON.parse(raw) : [];
chatEl.innerHTML = '';
if (!mpaiState.history.length){
mpaiAppendMsg('assistant', 'Pronto! Dimmi cosa vuoi fare su "'+project+'". Allegami anche file sanificati se servono.');
} else {
mpaiState.history.forEach(m => mpaiAppendMsg(m.role, m.content));
}
}
/** Bozze locali (se non c’è progetto) */
function mpaiEnsureSession(){
if (!mpaiState.sessionId){
mpaiState.sessionId = 'sess-' + Date.now();
mpaiState.sessionMeta = { title: 'Nuova conversazione', updated: Date.now() };
localStorage.setItem('mpai.session.meta.'+mpaiState.sessionId, JSON.stringify(mpaiState.sessionMeta));
}
}
function mpaiSaveSession(){
if (!mpaiState.sessionId) return;
const key = 'mpai.session.hist.' + mpaiState.sessionId;
localStorage.setItem(key, JSON.stringify(mpaiState.history.slice(-200)));
mpaiState.sessionMeta.updated = Date.now();
localStorage.setItem('mpai.session.meta.'+mpaiState.sessionId, JSON.stringify(mpaiState.sessionMeta));
}
function mpaiLoadSession(id){
const chatEl = $mp('#mpai-chat');
if (!chatEl) return;
mpaiState.sessionId = id;
const key = 'mpai.session.hist.' + id;
const raw = localStorage.getItem(key);
mpaiState.history = raw ? JSON.parse(raw) : [];
const metaRaw = localStorage.getItem('mpai.session.meta.'+id);
mpaiState.sessionMeta = metaRaw ? JSON.parse(metaRaw) : {title:'Nuova conversazione', updated:Date.now()};
chatEl.innerHTML = '';
if (!mpaiState.history.length){
mpaiAppendMsg('assistant', 'Pronto! Dimmi cosa vuoi fare. Puoi allegare file sanificati.');
} else {
mpaiState.history.forEach(m => mpaiAppendMsg(m.role, m.content));
}
}
/** Lista progetti (sidebar AI) – opzionale ma comoda */
async function mpaiLoadProjects(){
const box = $mp('#mpai-project-list');
if (!box) return; // se non c’è la UI AI, non fare nulla
box.innerHTML = '<em class="muted">Carico progetti…</em>';
try{
const r = await fetch('/dashboard/api/project_list.php', {cache:'no-store', credentials:'include'});
const j = await r.json();
const arr = Array.isArray(j) ? j : (Array.isArray(j.projects) ? j.projects : []);
if (!arr.length){ box.innerHTML='<em class="muted">Nessun progetto</em>'; return; }
box.innerHTML='';
arr.forEach(item=>{
const name = (typeof item === 'string') ? item : (item.name || item);
const row = document.createElement('div');
row.className = 'row';
row.innerHTML = `<span class="title">${name}</span>
<span><button class="mpai-btn" data-open="${name}">Apri</button></span>`;
row.querySelector('[data-open]').onclick = ()=>{
mpaiState.currentProject = name;
mpaiLoadLocal(name);
};
box.appendChild(row);
});
if (!mpaiState.currentProject){
const first = (typeof arr[0]==='string')?arr[0]:(arr[0].name||arr[0]);
mpaiState.currentProject = first;
mpaiLoadLocal(first);
}
}catch(e){
box.innerHTML = '<span class="muted">Errore caricamento: '+e.message+'</span>';
}
}
async function mpaiCreateProject(){
const inp = $mp('#mpai-project-name');
const name = (inp?.value||'').trim();
if (!name){ alert('Inserisci un nome progetto'); return; }
try{
const r = await fetch('/dashboard/api/project_create.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
credentials:'include',
body: JSON.stringify({name})
});
const txt = await r.text(); let j; try{ j=JSON.parse(txt) }catch{ j={ok:false,raw:txt}; }
if (j.ok!==false){ await mpaiLoadProjects(); mpaiState.currentProject=name; mpaiState.history=[]; mpaiLoadLocal(name); inp.value=''; }
else { alert('Errore creazione: '+(j.error||'')); }
}catch(e){ alert('Errore rete: '+e.message); }
}
/** INVIO PROMPT → OpenAI proxy */
window.sendPrompt = async function(){
const ta = $mp('#mpai-input');
if (!ta) return;
const model = ($mp('#mpai-model')?.value || 'gpt-4o-2024-05-13').trim();
const temp = parseFloat($mp('#mpai-temp')?.value || '0.7') || 0.7;
const sanitizedOnly = !!($mp('#mpai-sanitized-only')?.checked);
const content = (ta.value || '').trim();
if (!content){ ta.focus(); return; }
// Se non c'è progetto, attiva bozza locale
if (!mpaiState.currentProject && !mpaiState.sessionId){
mpaiEnsureSession();
mpaiLoadSession(mpaiState.sessionId);
}
// Mostra subito il messaggio utente
mpaiState.history.push({role:'user', content});
mpaiAppendMsg('user', content);
ta.value = '';
// Contesto
const contextName = mpaiState.currentProject
? `Progetto: ${mpaiState.currentProject}`
: `Sessione: ${mpaiState.sessionMeta?.title || 'Nuova conversazione'}`;
const fileNames = mpaiState.attachments.map(a => a.name).join(', ');
const preface = `${contextName}
File allegati${sanitizedOnly ? ' (sanificati)' : ''}: ${fileNames || 'nessuno'}
Istruzioni: rispondi in ITALIANO, struttura chiara (titoli, punti elenco), sii operativo. Temperatura: ${temp}.
---
Utente:
${content}`;
// Placeholder “Elaboro…”
const pending = document.createElement('div');
pending.className = 'mpai-msg';
pending.innerHTML = '<em class="muted">Elaboro…</em>';
const chatEl = $mp('#mpai-chat');
chatEl && chatEl.appendChild(pending);
try{
const r = await fetch('/dashboard/api/openai_project_gpt.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
mode: 'same-origin',
cache: 'no-store',
body: JSON.stringify({ model, prompt: preface })
});
const raw = await r.text();
pending.remove?.();
if (!r.ok){
console.error('GPT proxy error', r.status, r.statusText, raw);
mpaiAppendMsg('error', `HTTP ${r.status} ${r.statusText}\n\n${raw || '(nessun body)'}`);
mpaiSaveLocal(); return;
}
let j;
try { j = JSON.parse(raw); }
catch(parseErr){
console.error('JSON parse error', parseErr, raw);
mpaiAppendMsg('error', `Risposta non in JSON:\n\n${raw.slice(0,1500)}`);
mpaiSaveLocal(); return;
}
if (j.status === 'ok' && j.result){
// Se è bozza, rinomina titolo alla prima risposta
if (!mpaiState.currentProject && mpaiState.sessionMeta && mpaiState.sessionMeta.title === 'Nuova conversazione'){
mpaiState.sessionMeta.title = (content.slice(0,48) || 'Nuova conversazione');
mpaiSaveSession();
}
mpaiState.history.push({role:'assistant', content: j.result});
mpaiAppendMsg('assistant', j.result);
mpaiSaveLocal();
} else {
const err = j.error || 'Errore sconosciuto';
mpaiAppendMsg('error', `API error: ${err}\n\nRAW:\n${(j.raw||'').slice(0,1500)}`);
mpaiSaveLocal();
}
} catch(e){
pending.remove?.();
console.error('Network/JS error', e);
mpaiAppendMsg('error', `Errore di rete/JS: ${e.message}`);
mpaiSaveLocal();
}
};
/** Bind UI quando la pagina è pronta (solo se la UI AI esiste) */
document.addEventListener('DOMContentLoaded', ()=>{
if (!$mp('#mpAI')) return; // non siamo nella dashboard AI
// Upload locale (solo elenco)
const dz = $mp('#mpai-dropzone');
const fi = $mp('#mpai-file-input');
dz && dz.addEventListener('dragover', e => { e.preventDefault(); dz.style.opacity = .85; });
dz && dz.addEventListener('dragleave', () => { dz.style.opacity = 1; });
dz && dz.addEventListener('drop', e => {
e.preventDefault(); dz.style.opacity = 1;
Array.from(e.dataTransfer.files||[]).forEach(f=> mpaiState.attachments.push({file:f,name:f.name}));
mpaiRenderFiles();
});
fi && fi.addEventListener('change', () => {
Array.from(fi.files||[]).forEach(f=> mpaiState.attachments.push({file:f,name:f.name}));
fi.value=''; mpaiRenderFiles();
});
// Bottoni
$mp('#mpai-send')?.addEventListener('click', window.sendPrompt);
$mp('#mpai-project-create')?.addEventListener('click', mpaiCreateProject);
$mp('#mpai-new-chat')?.addEventListener('click', ()=>{
mpaiState.history=[]; mpaiState.attachments=[]; mpaiRenderFiles();
const chatEl = $mp('#mpai-chat'); if (chatEl){ chatEl.innerHTML=''; }
if (mpaiState.currentProject){ mpaiAppendMsg('assistant','Nuova chat per "'+mpaiState.currentProject+'".'); mpaiSaveLocal(); }
else { mpaiState.sessionId=null; mpaiState.sessionMeta=null; mpaiEnsureSession(); mpaiAppendMsg('assistant','Nuova conversazione.'); mpaiSaveSession(); }
});
$mp('#mpai-clear')?.addEventListener('click', ()=>{
if (!mpaiState.currentProject){ alert('Crea o seleziona un progetto'); return; }
if (!confirm('Svuotare la conversazione locale?')) return;
mpaiState.history=[]; mpaiState.attachments=[]; mpaiRenderFiles();
const chatEl = $mp('#mpai-chat'); if (chatEl){ chatEl.innerHTML=''; }
mpaiAppendMsg('assistant','Conversazione pulita.'); mpaiSaveLocal();
});
// Invio con Cmd/Ctrl+Enter nella textarea
document.addEventListener('keydown', (e)=>{
if ((e.metaKey||e.ctrlKey) && e.key==='Enter'){ const ta=$mp('#mpai-input'); if (ta && ta===document.activeElement){ e.preventDefault(); window.sendPrompt(); } }
});
// Boot
mpaiLoadProjects();
});
console.log('✅ MPAI: pronta.');
})();