Nessun oggetto della modifica
Nessun oggetto della modifica
Riga 85: Riga 85:
     #mpAI .mpai-msg .meta{ margin-top:6px; font-size:.72em; color:#9aa4b2 }
     #mpAI .mpai-msg .meta{ margin-top:6px; font-size:.72em; color:#9aa4b2 }


     /* Input textarea: dimensioni più leggibili */
     /* Input textarea */
     #mpAI #mpai-input{
     #mpAI #mpai-input{
       font-size:16px;
       font-size:16px;
Riga 100: Riga 100:
       background:#9aa4b2; opacity:.55; animation:mpai-bounce 1.2s infinite
       background:#9aa4b2; opacity:.55; animation:mpai-bounce 1.2s infinite
     }
     }
     #mpAI .mpai-typing .dot:nth-child(2){ animation-delay:.2s }
     #MPAI .mpai-typing .dot:nth-child(2){ animation-delay:.2s }
     #mpAI .mpai-typing .dot:nth-child(3){ animation-delay:.4s }
     #MPAI .mpai-typing .dot:nth-child(3){ animation-delay:.4s }
     @keyframes mpai-bounce{ 0%,80%,100%{ transform:translateY(0) } 40%{ transform:translateY(-4px) } }
     @keyframes mpai-bounce{ 0%,80%,100%{ transform:translateY(0) } 40%{ transform:translateY(-4px) } }
     `;
     `;
     const s = document.createElement('style');
     const s = document.createElement('style');
     s.id = 'mpai-decor-css';
     s.id = 'mpai-decor-css';
    s.textContent = css;
    document.head.appendChild(s);
  })();
  /* ===================== STILI: barra anteprime (ChatGPT-like) ===================== */
  (function attachMpaiAttachCss(){
    if (document.getElementById('mpai-attach-css')) return;
    const css = `
      #mpai-attach-previews{
        margin:8px 0 0; display:flex; flex-wrap:wrap; gap:8px;
      }
      .mpai-pill{
        display:inline-flex; align-items:center; gap:8px;
        border:1px solid #e5e7eb; border-radius:10px; padding:6px 8px; background:#fff;
        box-shadow:0 1px 0 rgba(0,0,0,.02); font-size:13px;
      }
      .mpai-thumb{ width:44px; height:44px; border-radius:8px; object-fit:cover; border:1px solid #e5e7eb; }
      .mpai-icon{ width:28px; height:28px; border-radius:6px; display:inline-flex; align-items:center; justify-content:center; border:1px solid #e5e7eb; font-size:11px; color:#374151 }
      .mpai-icon.pdf{ background:#fff0f0 } .mpai-icon.doc{ background:#f0f6ff }
      .mpai-pill .name{ max-width:220px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
      .mpai-pill button{ border:0; background:transparent; cursor:pointer; font-size:16px; line-height:1; color:#6b7280 }
      #mpai-input.drop-target{ outline:2px dashed #94a3b8; outline-offset:4px; background:#f8fafc }
    `;
    const s = document.createElement('style');
    s.id = 'mpai-attach-css';
     s.textContent = css;
     s.textContent = css;
     document.head.appendChild(s);
     document.head.appendChild(s);
Riga 112: Riga 137:
   /* ===================== STATO ===================== */
   /* ===================== STATO ===================== */
   const mpaiState = {
   const mpaiState = {
     currentProject: null,   // nome progetto (se selezionato)
     currentProject: null,
     sessionId: null,       // id bozza locale
     sessionId: null,
     sessionMeta: null,      // {title, updated}
     sessionMeta: null,      // {title, updated}
     history: [],
     history: [],
     attachments: []
     attachments: []        // [{file, name, (dataUrl?)}]
   };
   };


Riga 124: Riga 149:
   }
   }
   function renderMarkdown(src){
   function renderMarkdown(src){
    // 1) escape tutto
     let s = escapeHtml(String(src || ''));
     let s = escapeHtml(String(src || ''));
    // 2) code block ```...```
     s = s.replace(/```([\s\S]*?)```/g, (_m, code) => `<pre><code>${code.trim()}</code></pre>`);
     s = s.replace(/```([\s\S]*?)```/g, (_m, code) => `<pre><code>${code.trim()}</code></pre>`);
    // 3) inline code `...`
     s = s.replace(/`([^`]+?)`/g, (_m, code) => `<code>${code}</code>`);
     s = s.replace(/`([^`]+?)`/g, (_m, code) => `<code>${code}</code>`);
    // 4) grassetto **...**
     s = s.replace(/\*\*([^*]+?)\*\*/g, '<strong>$1</strong>');
     s = s.replace(/\*\*([^*]+?)\*\*/g, '<strong>$1</strong>');
    // 5) corsivo *...*
     s = s.replace(/(^|[^\*])\*([^*]+?)\*(?!\*)/g, (_m, pre, it) => `${pre}<em>${it}</em>`);
     s = s.replace(/(^|[^\*])\*([^*]+?)\*(?!\*)/g, (_m, pre, it) => `${pre}<em>${it}</em>`);
    // 6) link [testo](url)
     s = s.replace(/\[([^\]]+?)\]\((https?:\/\/[^\s)]+)\)/g, `<a href="$2" target="_blank" rel="nofollow noopener">$1</a>`);
     s = s.replace(/\[([^\]]+?)\]\((https?:\/\/[^\s)]+)\)/g, `<a href="$2" target="_blank" rel="nofollow noopener">$1</a>`);
    // 7) intestazioni #, ##, ###
     s = s.replace(/^\s*###\s+(.+)$/gm, '<h3>$1</h3>');
     s = s.replace(/^\s*###\s+(.+)$/gm, '<h3>$1</h3>');
     s = s.replace(/^\s*##\s+(.+)$/gm, '<h2>$1</h2>');
     s = s.replace(/^\s*##\s+(.+)$/gm, '<h2>$1</h2>');
     s = s.replace(/^\s*#\s+(.+)$/gm, '<h1>$1</h1>');
     s = s.replace(/^\s*#\s+(.+)$/gm, '<h1>$1</h1>');
    // 8) liste non ordinate (blocchi con righe che iniziano con "- ")
     s = s.replace(/(?:^|\n)(?:-\s+[^\n]+)(?:\n-\s+[^\n]+)*\n?/g, block => {
     s = s.replace(/(?:^|\n)(?:-\s+[^\n]+)(?:\n-\s+[^\n]+)*\n?/g, block => {
       const items = block.trim().split('\n').map(line => line.replace(/^\-\s+/, '').trim());
       const items = block.trim().split('\n').map(line => line.replace(/^\-\s+/, '').trim());
       return `<ul>${items.map(it => `<li>${it}</li>`).join('')}</ul>`;
       return `<ul>${items.map(it => `<li>${it}</li>`).join('')}</ul>`;
     });
     });
    // 9) paragrafi
     s = s.split(/\n{2,}/).map(par => {
     s = s.split(/\n{2,}/).map(par => {
       if (/^<h\d|^<ul>|^<pre>|^<blockquote>|^<table>/.test(par.trim())) return par;
       if (/^<h\d|^<ul>|^<pre>|^<blockquote>|^<table>/.test(par.trim())) return par;
       return `<p>${par.trim().replace(/\n/g,'<br>')}</p>`;
       return `<p>${par.trim().replace(/\n/g,'<br>')}</p>`;
     }).join('\n');
     }).join('\n');
     return s;
     return s;
   }
   }
Riga 171: Riga 178:
     const chatEl = $mp('#mpai-chat');
     const chatEl = $mp('#mpai-chat');
     if (!chatEl) return;
     if (!chatEl) return;
 
     const html = renderMarkdown(content);
     const html = renderMarkdown(content); // markdown safe
 
     const div = document.createElement('div');
     const div = document.createElement('div');
     div.className = 'mpai-msg ' + (role === 'user' ? 'user' : (role === 'assistant' ? 'assistant' : 'error'));
     div.className = 'mpai-msg ' + (role === 'user' ? 'user' : (role === 'assistant' ? 'assistant' : 'error'));
Riga 197: Riga 202:
   function mpaiHideTyping(el){ try{ el && el.remove(); }catch{} }
   function mpaiHideTyping(el){ try{ el && el.remove(); }catch{} }


   function mpaiRenderFiles() {
  /* ===================== PREVIEW BAR (nuova) ===================== */
     const fileListEl = $mp('#mpai-file-list');
   function mpaiEnsurePreviewBar(){
     if (!fileListEl) return;
     const ta = document.getElementById('mpai-input');
     fileListEl.innerHTML = '';
    if (!ta) return null;
     mpaiState.attachments.forEach((a, i) => {
    let bar = document.getElementById('mpai-attach-previews');
       const pill = document.createElement('span');
     if (!bar) {
       pill.className = 'mpai-filepill';
      bar = document.createElement('div');
       pill.innerHTML = `<span title="Allegato">${a.name}</span><button title="Rimuovi">✕</button>`;
      bar.id = 'mpai-attach-previews';
       pill.querySelector('button').onclick = () => { mpaiState.attachments.splice(i, 1); mpaiRenderFiles(); };
      ta.insertAdjacentElement('afterend', bar);
       fileListEl.appendChild(pill);
    }
    return bar;
  }
 
  function mpaiRenderFiles(){
     const bar = mpaiEnsurePreviewBar(); if (!bar) return;
    bar.innerHTML = '';
     (mpaiState.attachments || []).forEach((a,i)=>{
      const f = a?.file; const name = (a?.name || f?.name || 'file').toString();
      const type = (f?.type || '').toLowerCase();
 
       const pill = document.createElement('div'); pill.className = 'mpai-pill';
 
       if (type.startsWith('image/')) {
        const img = document.createElement('img'); img.className='mpai-thumb';
        if (a.dataUrl) img.src = a.dataUrl;
        else {
          try { const r = new FileReader(); r.onload = e=> img.src = e.target.result; r.readAsDataURL(f); }
          catch {}
        }
        pill.appendChild(img);
       } else {
        const ic = document.createElement('div'); ic.className='mpai-icon';
        if (type === 'application/pdf' || name.endsWith('.pdf')) { ic.classList.add('pdf'); ic.textContent='PDF'; }
        else if (name.endsWith('.doc') || name.endsWith('.docx')) { ic.classList.add('doc'); ic.textContent='DOC'; }
        else { ic.textContent='FILE'; }
        pill.appendChild(ic);
      }
 
      const span = document.createElement('span'); span.className='name'; span.title = name; span.textContent = name;
       const btn  = document.createElement('button'); btn.type='button'; btn.title='Rimuovi'; btn.textContent='✕';
      btn.onclick = ()=>{ mpaiState.attachments.splice(i,1); mpaiRenderFiles(); };
 
      pill.appendChild(span); pill.appendChild(btn);
       bar.appendChild(pill);
     });
     });
   }
   }


   /* ===================== FILE HELPERS ===================== */
   /* ===================== FILE HELPERS ===================== */
  // File -> dataURL (base64)
 
 
   async function fileToDataURL(file){
   async function fileToDataURL(file){
     return new Promise((resolve, reject)=>{
     return new Promise((resolve, reject)=>{
Riga 223: Riga 259:
   }
   }


  // File di testo -> stringa UTF-8
   async function fileToText(file){
   async function fileToText(file){
     return new Promise((resolve, reject)=>{
     return new Promise((resolve, reject)=>{
Riga 232: Riga 267:
     });
     });
   }
   }
 
 
  // Raccoglie gli allegati in due gruppi:
// - files:  immagini (png/jpg/webp) + pdf/doc/docx  -> { name, type, dataUrl }
// - texts:  file di testo (txt/md)                  -> stringhe UTF-8
async function collectAttachedPayload(){
  const files = [];
  const texts = [];


   for (const att of (mpaiState.attachments || [])) {
   // Raccoglie: img/pdf/doc/docx → files[], txt/md → texts[]
    const f = att?.file;
  async function collectAttachedPayload(){
    if (!f) continue;
    const files = [];
    const texts = [];
    for (const att of (mpaiState.attachments || [])) {
      const f = att?.file; if (!f) continue;
      const name = (f.name || '').toLowerCase();
      const type = (f.type || '').toLowerCase();


    const name = (f.name || '').toLowerCase();
      const MAX_TEXT = 2 * 1024 * 1024;  // 2 MB (txt/md)
    const type = (f.type || '').toLowerCase();
      const MAX_BIN  = 12 * 1024 * 1024;  // 12 MB (img/pdf/doc/docx)


    // limiti sicurezza
      // TXT / MD
    const MAX_TEXT = 2 * 1024 * 1024;  // 2 MB (txt/md)
      if (type === 'text/plain' || name.endsWith('.txt') || name.endsWith('.md')) {
    const MAX_BIN  = 12 * 1024 * 1024; // 12 MB (img/pdf/doc/docx)
        if (f.size > MAX_TEXT) { console.warn('Testo troppo grande, salto:', name); continue; }
        const t = await fileToText(f);
        if (t && t.trim()) texts.push(t);
        continue;
      }


    // --- TXT / MD -> texts[] come stringhe ---
      // Immagini
    if (type === 'text/plain' || name.endsWith('.txt') || name.endsWith('.md')) {
      if (['image/png','image/jpeg','image/webp'].includes(type)) {
      if (f.size > MAX_TEXT) { console.warn('Testo troppo grande, salto:', name); continue; }
        if (f.size > MAX_BIN) { console.warn('Immagine troppo grande, salto:', name); continue; }
      const t = await fileToText(f);
        const dataUrl = await fileToDataURL(f);
      if (t && t.trim()) texts.push(t);
        files.push({ name: f.name, type, dataUrl });
      continue;
        continue;
    }
      }


    // --- Immagini -> files[] come dataURL ---
      // PDF
    if (['image/png','image/jpeg','image/webp'].includes(type)) {
      if (type === 'application/pdf' || name.endsWith('.pdf')) {
      if (f.size > MAX_BIN) { console.warn('Immagine troppo grande, salto:', name); continue; }
        if (f.size > MAX_BIN) { console.warn('PDF troppo grande, salto:', name); continue; }
      const dataUrl = await fileToDataURL(f);
        const dataUrl = await fileToDataURL(f);
      files.push({ name: f.name, type, dataUrl });
        files.push({ name: f.name, type: 'application/pdf', dataUrl });
      continue;
        continue;
    }
      }


    // --- PDF -> files[] come dataURL ---
      // DOC / DOCX
    if (type === 'application/pdf' || name.endsWith('.pdf')) {
      const isDoc  = type === 'application/msword' || name.endsWith('.doc');
      if (f.size > MAX_BIN) { console.warn('PDF troppo grande, salto:', name); continue; }
      const isDocx = type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || name.endsWith('.docx');
      const dataUrl = await fileToDataURL(f);
      if (isDoc || isDocx) {
      files.push({ name: f.name, type: 'application/pdf', dataUrl });
        if (f.size > MAX_BIN) { console.warn('DOC/DOCX troppo grande, salto:', name); continue; }
      continue;
        const dataUrl = await fileToDataURL(f);
    }
        files.push({
          name: f.name,
          type: isDoc ? 'application/msword'
                      : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
          dataUrl
        });
        continue;
      }


    // --- DOC / DOCX -> files[] come dataURL ---
       console.warn('Tipo non supportato:', f.name, f.type);
    const isDoc  = type === 'application/msword' || name.endsWith('.doc');
    const isDocx = type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || name.endsWith('.docx');
    if (isDoc || isDocx) {
       if (f.size > MAX_BIN) { console.warn('DOC/DOCX troppo grande, salto:', name); continue; }
      const dataUrl = await fileToDataURL(f);
      files.push({
        name: f.name,
        type: isDoc ? 'application/msword'
                    : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        dataUrl
      });
      continue;
     }
     }
 
     return { files, texts };
     console.warn('Tipo non supportato:', f.name, f.type);
   }
   }


   return { files, texts };
   // (opzionale) raccolta testi diagnostica
}
  async function collectAttachedTexts(){
    const out = [];
    for (const att of (mpaiState.attachments || [])) {
      const f = att.file; if (!f) continue;
      const name = (f.name || '').toLowerCase();
      const type = (f.type || '').toLowerCase();


      const MAX_TEXT = 2 * 1024 * 1024;
      const MAX_DOC  = 12 * 1024 * 1024;


 
      if (type === 'text/plain' || name.endsWith('.txt') || name.endsWith('.md')) {
// raccoglie .txt/.md (→ testo puro), .pdf/.doc/.docx (→ dataURL) dagli allegati
        if (f.size > MAX_TEXT) { console.warn('Testo troppo grande, salto:', name); continue; }
 
        const text = await fileToText(f);
async function collectAttachedTexts(){
        out.push({ kind:'text', name, data:text });
  const out = [];  // array di { kind:'text'|'pdf'|'doc', name, data }
        continue;
  for (const att of (mpaiState.attachments || [])) {
      }
    const f = att.file; if (!f) continue;
      if (type === 'application/pdf' || name.endsWith('.pdf')) {
 
        if (f.size > MAX_DOC) { console.warn('PDF troppo grande, salto:', name); continue; }
    const name = (f.name || '').toLowerCase();
        const dataUrl = await fileToDataURL(f);
    const type = (f.type || '').toLowerCase();
        out.push({ kind:'pdf', name, data:dataUrl });
 
        continue;
    const MAX_TEXT = 2 * 1024 * 1024;    // 2 MB
      }
    const MAX_DOC  = 12 * 1024 * 1024;  // 12 MB per pdf/doc/docx
      const isDoc  = type === 'application/msword' || name.endsWith('.doc');
 
      const isDocx = type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || name.endsWith('.docx');
    // --- TXT / MD ---
      if (isDoc || isDocx) {
    if (type === 'text/plain' || name.endsWith('.txt') || name.endsWith('.md')) {
        if (f.size > MAX_DOC) { console.warn('DOC/DOCX troppo grande, salto:', name); continue; }
      if (f.size > MAX_TEXT) { console.warn('Testo troppo grande, salto:', name); continue; }
        const dataUrl = await fileToDataURL(f);
      const text = await fileToText(f);
        out.push({ kind:'doc', name, data:dataUrl });
      out.push({ kind:'text', name, data:text });
        continue;
      continue;
      }
    }
 
    // --- PDF ---
    if (type === 'application/pdf' || name.endsWith('.pdf')) {
      if (f.size > MAX_DOC) { console.warn('PDF troppo grande, salto:', name); continue; }
      const dataUrl = await fileToDataURL(f);
      out.push({ kind:'pdf', name, data:dataUrl });
      continue;
    }
 
    // --- DOC / DOCX ---
    const isDoc  = type === 'application/msword' || name.endsWith('.doc');
    const isDocx = type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || name.endsWith('.docx');
    if (isDoc || isDocx) {
      if (f.size > MAX_DOC) { console.warn('DOC/DOCX troppo grande, salto:', name); continue; }
      const dataUrl = await fileToDataURL(f);
      out.push({ kind:'doc', name, data:dataUrl });
      continue;
     }
     }
    return out;
   }
   }
  return out;
}


   /* ===================== PERSISTENZA ===================== */
   /* ===================== PERSISTENZA ===================== */
Riga 348: Riga 365:
   }
   }
   function mpaiLoadLocal(project) {
   function mpaiLoadLocal(project) {
     const chatEl = $mp('#mpai-chat');
     const chatEl = $mp('#mpai-chat'); if (!chatEl) return;
    if (!chatEl) return;
     const raw = localStorage.getItem('mpai.hist.' + project);
     const raw = localStorage.getItem('mpai.hist.' + project);
     mpaiState.history = raw ? JSON.parse(raw) : [];
     mpaiState.history = raw ? JSON.parse(raw) : [];
Riga 375: Riga 391:
   }
   }
   function mpaiLoadSession(id) {
   function mpaiLoadSession(id) {
     const chatEl = $mp('#mpai-chat');
     const chatEl = $mp('#mpai-chat'); if (!chatEl) return;
    if (!chatEl) return;
     mpaiState.sessionId = id;
     mpaiState.sessionId = id;
     const key = 'mpai.session.hist.' + id;
     const key = 'mpai.session.hist.' + id;
Riga 471: Riga 486:


       console.log('[MPAI] Invio', files.length, 'file (img/pdf) e', texts.length, 'testi');
       console.log('[MPAI] Invio', files.length, 'file (img/pdf) e', texts.length, 'testi');
      const payload = { model, prompt: content, files, texts, temperature: 0.7 };


       const r = await fetch('/dashboard/api/openai_project_gpt.php', {
       const r = await fetch('/dashboard/api/openai_project_gpt.php', {
Riga 477: Riga 494:
         credentials: 'include',
         credentials: 'include',
         cache: 'no-store',
         cache: 'no-store',
         body: JSON.stringify({ model, prompt: content, files, texts })
         body: JSON.stringify(payload)
       });
       });


Riga 504: Riga 521:
         mpaiAppendMsg('error', `API error: ${(j && (j.error || j.message)) || 'sconosciuto'}\n\nRAW:\n${text.slice(0, 1500)}`);
         mpaiAppendMsg('error', `API error: ${(j && (j.error || j.message)) || 'sconosciuto'}\n\nRAW:\n${text.slice(0, 1500)}`);
       }
       }
      // dopo l’invio pulisco gli allegati e la barra (come ChatGPT)
      mpaiState.attachments = [];
      mpaiRenderFiles();
     } catch (e) {
     } catch (e) {
       mpaiAppendMsg('error', `Errore di rete/JS: ${e.message}`);
       mpaiAppendMsg('error', `Errore di rete/JS: ${e.message}`);
Riga 515: Riga 537:
     const ai = document.getElementById('mpAI');
     const ai = document.getElementById('mpAI');
     if (ai) { ai.style.display = 'block'; ai.scrollIntoView({ behavior: 'smooth' }); }
     if (ai) { ai.style.display = 'block'; ai.scrollIntoView({ behavior: 'smooth' }); }
     mpaiBindUI(); // assicura i listener una sola volta
     mpaiBindUI();
   };
   };


   function mpaiBindUI() {
   function mpaiBindUI() {
     if (window.__MP_AI_BOUND__) return;   // evita doppi binding
     if (window.__MP_AI_BOUND__) return;
     window.__MP_AI_BOUND__ = true;
     window.__MP_AI_BOUND__ = true;


     if (!$mp('#mpAI')) return; // UI non presente
     if (!$mp('#mpAI')) return;


     // Upload locale (solo elenco)
     // Nascondo la vecchia dropzone/lista (restano compat per chi già li usa)
     const dz = $mp('#mpai-dropzone');
     const oldDz = document.getElementById('mpai-dropzone');
     const fi = $mp('#mpai-file-input');
    if (oldDz) oldDz.style.display = 'none';
     dz && dz.addEventListener('dragover', e => { e.preventDefault(); dz.style.opacity = .85; });
     const oldList = document.getElementById('mpai-file-list');
    dz && dz.addEventListener('dragleave', () => { dz.style.opacity = 1; });
    if (oldList) oldList.style.display = 'none';
    dz && dz.addEventListener('drop', e => {
 
      e.preventDefault(); dz.style.opacity = 1;
    // Assicuro la barra anteprime sotto la textarea
      Array.from(e.dataTransfer.files || []).forEach(f=> mpaiState.attachments.push({file:f,name:f.name}));
    mpaiEnsurePreviewBar();
      mpaiRenderFiles();
 
     });
    // Drag & Drop direttamente nella textarea
     fi && fi.addEventListener('change', () => {
    const ta = document.getElementById('mpai-input');
       Array.from(fi.files||[]).forEach(f=> mpaiState.attachments.push({file:f,name:f.name}));
     if (ta) {
       fi.value=''; mpaiRenderFiles();
      ta.addEventListener('dragover', (e)=>{ e.preventDefault(); ta.classList.add('drop-target'); });
     });
      ta.addEventListener('dragleave', ()=> ta.classList.remove('drop-target'));
      ta.addEventListener('drop', (e)=>{
        e.preventDefault(); ta.classList.remove('drop-target');
        const files = Array.from(e.dataTransfer.files || []);
        files.forEach(f=> mpaiState.attachments.push({file:f, name:f.name}));
        mpaiRenderFiles();
      });
     }
 
    // Input file nascosto per click sulla barra anteprime
    let hiddenInput = document.getElementById('mpai-file-input');
     if (!hiddenInput) {
      hiddenInput = document.createElement('input');
      hiddenInput.type = 'file'; hiddenInput.id = 'mpai-file-input'; hiddenInput.multiple = true; hiddenInput.style.display='none';
      document.body.appendChild(hiddenInput);
    }
    hiddenInput.onchange = ()=>{
       Array.from(hiddenInput.files||[]).forEach(f=> mpaiState.attachments.push({file:f,name:f.name}));
       hiddenInput.value=''; mpaiRenderFiles();
     };
 
    const bar = document.getElementById('mpai-attach-previews');
    if (bar) { bar.addEventListener('click', ()=> hiddenInput.click()); }


     // Bottoni
     // Bottoni
Riga 545: Riga 589:
       mpaiState.history=[]; mpaiState.attachments=[]; mpaiRenderFiles();
       mpaiState.history=[]; mpaiState.attachments=[]; mpaiRenderFiles();
       const chatEl = $mp('#mpai-chat'); if (chatEl){ chatEl.innerHTML=''; }
       const chatEl = $mp('#mpai-chat'); if (chatEl){ chatEl.innerHTML=''; }
       if (mpaiState.currentProject){ mpaiAppendMsg('assistant','Nuova chat per "'+mpaiState.currentProject+'".'); mpaiSaveLocal(); }
       if (mpaiState.currentProject){
       else { mpaiState.sessionId=null; mpaiState.sessionMeta=null; mpaiEnsureSession(); mpaiAppendMsg('assistant','Nuova conversazione.'); mpaiSaveSession(); }
        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', ()=>{
     $mp('#mpai-clear')?.addEventListener('click', ()=>{

Versione delle 17:11, 5 ott 2025

/* ===================== 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;
}

/** Shortcut locale */
window.$mp = window.$mp || (s => document.querySelector(s));

(function () {
  console.log('🧭 MPAI: init…');

  /* ===================== STILI “ChatGPT vibe” + tipografia ===================== */
  (function attachMpaiDecorCss(){
    if (document.getElementById('mpai-decor-css')) return;
    const css = `
    /* Scoping forte alla dashboard AI */
    #mpAI{ font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; font-size:15px; color:#111827 }
    #mpAI .mpai-msg{
      position:relative;
      border:1px solid #e5e7eb;
      border-radius:12px;
      padding:12px 14px;
      background:#fff; /* default bianco */
      line-height:1.6;
    }
    #mpAI .mpai-msg + .mpai-msg{ margin-top:10px }

    /* GPT (assistant) a sinistra → bianco dentro, bordo verde chiaro */
    #mpAI .mpai-msg.assistant{
      padding-left:56px;
      background:#ffffff !important;
      border-color:#a7f3d0 !important;
    }
    #mpAI .mpai-msg.assistant::before{
      content:"";
      position:absolute; left:-8px; top:8px;
      width:36px; height:36px; border-radius:50%;
      background:#fff url('/resources/assets/change-your-logo.svg') center/70% no-repeat;
      border:1px solid #e5e7eb;
      box-shadow:0 1px 0 rgba(0,0,0,.02);
    }

    /* Utente a destra, azzurrino compatto */
    #mpAI .mpai-msg.user{
      background:#eaf2ff;
      border-color:#d7e6ff;
      max-width:78%;
      margin-left:auto;
    }
    #mpAI .mpai-msg.user .role,
    #mpAI .mpai-msg.user .meta{ text-align:right }

    /* Testo e meta */
    #mpAI .mpai-msg .role{ font-weight:600; margin-bottom:4px; color:#374151 }
    #mpAI .mpai-msg .content{ color:#111827; white-space:normal }
    #mpAI .mpai-msg .content p{ margin: .45em 0; }
    #mpAI .mpai-msg .content ul{ margin:.4em 0 .4em 1.2em; list-style:disc; }
    #mpAI .mpai-msg .content ol{ margin:.4em 0 .4em 1.2em; }
    #mpAI .mpai-msg .content h1,
    #mpAI .mpai-msg .content h2,
    #mpAI .mpai-msg .content h3{ margin:.6em 0 .35em; line-height:1.35 }
    #mpAI .mpai-msg .content h1{ font-size:1.25rem }
    #mpAI .mpai-msg .content h2{ font-size:1.15rem }
    #mpAI .mpai-msg .content h3{ font-size:1.05rem }
    #mpAI .mpai-msg .content code{
      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
      font-size:.92em; background:#f5f7fa; padding:.1em .35em; border-radius:6px; border:1px solid #e5e7eb;
    }
    #mpAI .mpai-msg .content pre{
      background:#f8fafc; border:1px solid #e5e7eb; border-radius:10px; padding:10px; overflow:auto;
    }
    #mpAI .mpai-msg .meta{ margin-top:6px; font-size:.72em; color:#9aa4b2 }

    /* Input textarea */
    #mpAI #mpai-input{
      font-size:16px;
      line-height:1.5;
      font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
      color:#111827;
    }

    /* “Typing dots” */
    #mpAI .mpai-typing .content{ color:#6b7280 }
    #mpAI .mpai-typing .dots{ display:inline-flex; gap:6px; vertical-align:middle }
    #mpAI .mpai-typing .dot{
      width:6px; height:6px; border-radius:50%;
      background:#9aa4b2; opacity:.55; animation:mpai-bounce 1.2s infinite
    }
    #MPAI .mpai-typing .dot:nth-child(2){ animation-delay:.2s }
    #MPAI .mpai-typing .dot:nth-child(3){ animation-delay:.4s }
    @keyframes mpai-bounce{ 0%,80%,100%{ transform:translateY(0) } 40%{ transform:translateY(-4px) } }
    `;
    const s = document.createElement('style');
    s.id = 'mpai-decor-css';
    s.textContent = css;
    document.head.appendChild(s);
  })();

  /* ===================== STILI: barra anteprime (ChatGPT-like) ===================== */
  (function attachMpaiAttachCss(){
    if (document.getElementById('mpai-attach-css')) return;
    const css = `
      #mpai-attach-previews{
        margin:8px 0 0; display:flex; flex-wrap:wrap; gap:8px;
      }
      .mpai-pill{
        display:inline-flex; align-items:center; gap:8px;
        border:1px solid #e5e7eb; border-radius:10px; padding:6px 8px; background:#fff;
        box-shadow:0 1px 0 rgba(0,0,0,.02); font-size:13px;
      }
      .mpai-thumb{ width:44px; height:44px; border-radius:8px; object-fit:cover; border:1px solid #e5e7eb; }
      .mpai-icon{ width:28px; height:28px; border-radius:6px; display:inline-flex; align-items:center; justify-content:center; border:1px solid #e5e7eb; font-size:11px; color:#374151 }
      .mpai-icon.pdf{ background:#fff0f0 } .mpai-icon.doc{ background:#f0f6ff }
      .mpai-pill .name{ max-width:220px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
      .mpai-pill button{ border:0; background:transparent; cursor:pointer; font-size:16px; line-height:1; color:#6b7280 }
      #mpai-input.drop-target{ outline:2px dashed #94a3b8; outline-offset:4px; background:#f8fafc }
    `;
    const s = document.createElement('style');
    s.id = 'mpai-attach-css';
    s.textContent = css;
    document.head.appendChild(s);
  })();

  /* ===================== STATO ===================== */
  const mpaiState = {
    currentProject: null,
    sessionId: null,
    sessionMeta: null,      // {title, updated}
    history: [],
    attachments: []         // [{file, name, (dataUrl?)}]
  };

  /* ===================== RENDER “MARKDOWN SAFE” ===================== */
  function escapeHtml(s){
    return s.replace(/[&<>"]/g, ch => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[ch]));
  }
  function renderMarkdown(src){
    let s = escapeHtml(String(src || ''));
    s = s.replace(/```([\s\S]*?)```/g, (_m, code) => `<pre><code>${code.trim()}</code></pre>`);
    s = s.replace(/`([^`]+?)`/g, (_m, code) => `<code>${code}</code>`);
    s = s.replace(/\*\*([^*]+?)\*\*/g, '<strong>$1</strong>');
    s = s.replace(/(^|[^\*])\*([^*]+?)\*(?!\*)/g, (_m, pre, it) => `${pre}<em>${it}</em>`);
    s = s.replace(/\[([^\]]+?)\]\((https?:\/\/[^\s)]+)\)/g, `<a href="$2" target="_blank" rel="nofollow noopener">$1</a>`);
    s = s.replace(/^\s*###\s+(.+)$/gm, '<h3>$1</h3>');
    s = s.replace(/^\s*##\s+(.+)$/gm, '<h2>$1</h2>');
    s = s.replace(/^\s*#\s+(.+)$/gm, '<h1>$1</h1>');
    s = s.replace(/(?:^|\n)(?:-\s+[^\n]+)(?:\n-\s+[^\n]+)*\n?/g, block => {
      const items = block.trim().split('\n').map(line => line.replace(/^\-\s+/, '').trim());
      return `<ul>${items.map(it => `<li>${it}</li>`).join('')}</ul>`;
    });
    s = s.split(/\n{2,}/).map(par => {
      if (/^<h\d|^<ul>|^<pre>|^<blockquote>|^<table>/.test(par.trim())) return par;
      return `<p>${par.trim().replace(/\n/g,'<br>')}</p>`;
    }).join('\n');
    return s;
  }

  /* ===================== UI HELPERS ===================== */
  function mpaiTime(ts){
    try { return new Date(ts||Date.now()).toLocaleTimeString('it-IT',{hour:'2-digit',minute:'2-digit'}); }
    catch { return ''; }
  }

  function mpaiAppendMsg(role, content) {
    const chatEl = $mp('#mpai-chat');
    if (!chatEl) return;
    const html = renderMarkdown(content);
    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">${html}</div>
      <div class="meta">${mpaiTime()}</div>`;
    chatEl.appendChild(div);
    chatEl.scrollTop = chatEl.scrollHeight;
  }

  function mpaiShowTyping(role='assistant'){
    const chatEl = $mp('#mpai-chat'); if (!chatEl) return null;
    const div = document.createElement('div');
    div.className = 'mpai-msg mpai-typing ' + (role==='user'?'user':'assistant');
    div.innerHTML = `
      <div class="role">${role==='user'?'Tu':'GPT'}</div>
      <div class="content"><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>`;
    chatEl.appendChild(div);
    chatEl.scrollTop = chatEl.scrollHeight;
    return div;
  }
  function mpaiHideTyping(el){ try{ el && el.remove(); }catch{} }

  /* ===================== PREVIEW BAR (nuova) ===================== */
  function mpaiEnsurePreviewBar(){
    const ta = document.getElementById('mpai-input');
    if (!ta) return null;
    let bar = document.getElementById('mpai-attach-previews');
    if (!bar) {
      bar = document.createElement('div');
      bar.id = 'mpai-attach-previews';
      ta.insertAdjacentElement('afterend', bar);
    }
    return bar;
  }

  function mpaiRenderFiles(){
    const bar = mpaiEnsurePreviewBar(); if (!bar) return;
    bar.innerHTML = '';
    (mpaiState.attachments || []).forEach((a,i)=>{
      const f = a?.file; const name = (a?.name || f?.name || 'file').toString();
      const type = (f?.type || '').toLowerCase();

      const pill = document.createElement('div'); pill.className = 'mpai-pill';

      if (type.startsWith('image/')) {
        const img = document.createElement('img'); img.className='mpai-thumb';
        if (a.dataUrl) img.src = a.dataUrl;
        else {
          try { const r = new FileReader(); r.onload = e=> img.src = e.target.result; r.readAsDataURL(f); }
          catch {}
        }
        pill.appendChild(img);
      } else {
        const ic = document.createElement('div'); ic.className='mpai-icon';
        if (type === 'application/pdf' || name.endsWith('.pdf')) { ic.classList.add('pdf'); ic.textContent='PDF'; }
        else if (name.endsWith('.doc') || name.endsWith('.docx')) { ic.classList.add('doc'); ic.textContent='DOC'; }
        else { ic.textContent='FILE'; }
        pill.appendChild(ic);
      }

      const span = document.createElement('span'); span.className='name'; span.title = name; span.textContent = name;
      const btn  = document.createElement('button'); btn.type='button'; btn.title='Rimuovi'; btn.textContent='✕';
      btn.onclick = ()=>{ mpaiState.attachments.splice(i,1); mpaiRenderFiles(); };

      pill.appendChild(span); pill.appendChild(btn);
      bar.appendChild(pill);
    });
  }

  /* ===================== FILE HELPERS ===================== */
  async function fileToDataURL(file){
    return new Promise((resolve, reject)=>{
      const r = new FileReader();
      r.onload = () => resolve(r.result);
      r.onerror = reject;
      r.readAsDataURL(file);
    });
  }

  async function fileToText(file){
    return new Promise((resolve, reject)=>{
      const r = new FileReader();
      r.onload = () => resolve(String(r.result || ''));
      r.onerror = reject;
      r.readAsText(file, 'utf-8');
    });
  }

  // Raccoglie: img/pdf/doc/docx → files[], txt/md → texts[]
  async function collectAttachedPayload(){
    const files = [];
    const texts = [];
    for (const att of (mpaiState.attachments || [])) {
      const f = att?.file; if (!f) continue;
      const name = (f.name || '').toLowerCase();
      const type = (f.type || '').toLowerCase();

      const MAX_TEXT = 2 * 1024 * 1024;   // 2 MB (txt/md)
      const MAX_BIN  = 12 * 1024 * 1024;  // 12 MB (img/pdf/doc/docx)

      // TXT / MD
      if (type === 'text/plain' || name.endsWith('.txt') || name.endsWith('.md')) {
        if (f.size > MAX_TEXT) { console.warn('Testo troppo grande, salto:', name); continue; }
        const t = await fileToText(f);
        if (t && t.trim()) texts.push(t);
        continue;
      }

      // Immagini
      if (['image/png','image/jpeg','image/webp'].includes(type)) {
        if (f.size > MAX_BIN) { console.warn('Immagine troppo grande, salto:', name); continue; }
        const dataUrl = await fileToDataURL(f);
        files.push({ name: f.name, type, dataUrl });
        continue;
      }

      // PDF
      if (type === 'application/pdf' || name.endsWith('.pdf')) {
        if (f.size > MAX_BIN) { console.warn('PDF troppo grande, salto:', name); continue; }
        const dataUrl = await fileToDataURL(f);
        files.push({ name: f.name, type: 'application/pdf', dataUrl });
        continue;
      }

      // DOC / DOCX
      const isDoc  = type === 'application/msword' || name.endsWith('.doc');
      const isDocx = type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || name.endsWith('.docx');
      if (isDoc || isDocx) {
        if (f.size > MAX_BIN) { console.warn('DOC/DOCX troppo grande, salto:', name); continue; }
        const dataUrl = await fileToDataURL(f);
        files.push({
          name: f.name,
          type: isDoc ? 'application/msword'
                      : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
          dataUrl
        });
        continue;
      }

      console.warn('Tipo non supportato:', f.name, f.type);
    }
    return { files, texts };
  }

  // (opzionale) raccolta testi diagnostica
  async function collectAttachedTexts(){
    const out = [];
    for (const att of (mpaiState.attachments || [])) {
      const f = att.file; if (!f) continue;
      const name = (f.name || '').toLowerCase();
      const type = (f.type || '').toLowerCase();

      const MAX_TEXT = 2 * 1024 * 1024;
      const MAX_DOC  = 12 * 1024 * 1024;

      if (type === 'text/plain' || name.endsWith('.txt') || name.endsWith('.md')) {
        if (f.size > MAX_TEXT) { console.warn('Testo troppo grande, salto:', name); continue; }
        const text = await fileToText(f);
        out.push({ kind:'text', name, data:text });
        continue;
      }
      if (type === 'application/pdf' || name.endsWith('.pdf')) {
        if (f.size > MAX_DOC) { console.warn('PDF troppo grande, salto:', name); continue; }
        const dataUrl = await fileToDataURL(f);
        out.push({ kind:'pdf', name, data:dataUrl });
        continue;
      }
      const isDoc  = type === 'application/msword' || name.endsWith('.doc');
      const isDocx = type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || name.endsWith('.docx');
      if (isDoc || isDocx) {
        if (f.size > MAX_DOC) { console.warn('DOC/DOCX troppo grande, salto:', name); continue; }
        const dataUrl = await fileToDataURL(f);
        out.push({ kind:'doc', name, data:dataUrl });
        continue;
      }
    }
    return out;
  }

  /* ===================== PERSISTENZA ===================== */
  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));
    }
  }

  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));
    }
  }

  /* ===================== PROGETTI (sidebar) ===================== */
  async function mpaiLoadProjects() {
    const box = $mp('#mpai-project-list');
    if (!box) return;
    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 → PROXY ===================== */
  window.sendPrompt = async function () {
    try {
      const ta = document.querySelector('#mpai-input');
      if (!ta) return;

      const content = (ta.value || '').trim();
      if (!content) { ta.focus(); return; }

      // mostra subito il messaggio utente
      mpaiState.history.push({ role: 'user', content });
      mpaiAppendMsg('user', content);
      ta.value = '';

      const model = (document.querySelector('#mpai-model')?.value || 'gpt-4o-2024-05-13').trim();

      // typing dots stile ChatGPT
      const pending = mpaiShowTyping('assistant');

      // raccogli allegati (img/pdf -> files[], txt -> texts[])
      let files = [], texts = [];
      try {
        const p = await collectAttachedPayload();
        files = p.files;
        texts = p.texts;
      } catch(e){
        console.warn('collectAttachedPayload failed', e);
      }

      console.log('[MPAI] Invio', files.length, 'file (img/pdf) e', texts.length, 'testi');

      const payload = { model, prompt: content, files, texts, temperature: 0.7 };

      const r = await fetch('/dashboard/api/openai_project_gpt.php', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        cache: 'no-store',
        body: JSON.stringify(payload)
      });

      const text = await r.text();
      mpaiHideTyping(pending);

      if (!r.ok) {
        mpaiAppendMsg('error', `HTTP ${r.status} ${r.statusText}\n\n${text || '(nessun body)'}`);
        return;
      }

      let j;
      try {
        j = JSON.parse(text);
        if (typeof j === 'string') { try { j = JSON.parse(j); } catch {} }
      } catch (e) {
        mpaiAppendMsg('error', `Risposta non in JSON:\n\n${text.slice(0, 1500)}`);
        return;
      }

      if (j && j.status === 'ok' && j.result) {
        mpaiState.history.push({ role: 'assistant', content: j.result });
        mpaiAppendMsg('assistant', j.result);
        mpaiSaveLocal();
      } else {
        mpaiAppendMsg('error', `API error: ${(j && (j.error || j.message)) || 'sconosciuto'}\n\nRAW:\n${text.slice(0, 1500)}`);
      }

      // dopo l’invio pulisco gli allegati e la barra (come ChatGPT)
      mpaiState.attachments = [];
      mpaiRenderFiles();

    } catch (e) {
      mpaiAppendMsg('error', `Errore di rete/JS: ${e.message}`);
    }
  };

  /* ===================== SHOW UI + BIND UNA SOLA VOLTA ===================== */
  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' }); }
    mpaiBindUI();
  };

  function mpaiBindUI() {
    if (window.__MP_AI_BOUND__) return;
    window.__MP_AI_BOUND__ = true;

    if (!$mp('#mpAI')) return;

    // Nascondo la vecchia dropzone/lista (restano compat per chi già li usa)
    const oldDz = document.getElementById('mpai-dropzone');
    if (oldDz) oldDz.style.display = 'none';
    const oldList = document.getElementById('mpai-file-list');
    if (oldList) oldList.style.display = 'none';

    // Assicuro la barra anteprime sotto la textarea
    mpaiEnsurePreviewBar();

    // Drag & Drop direttamente nella textarea
    const ta = document.getElementById('mpai-input');
    if (ta) {
      ta.addEventListener('dragover', (e)=>{ e.preventDefault(); ta.classList.add('drop-target'); });
      ta.addEventListener('dragleave', ()=> ta.classList.remove('drop-target'));
      ta.addEventListener('drop', (e)=>{
        e.preventDefault(); ta.classList.remove('drop-target');
        const files = Array.from(e.dataTransfer.files || []);
        files.forEach(f=> mpaiState.attachments.push({file:f, name:f.name}));
        mpaiRenderFiles();
      });
    }

    // Input file nascosto per click sulla barra anteprime
    let hiddenInput = document.getElementById('mpai-file-input');
    if (!hiddenInput) {
      hiddenInput = document.createElement('input');
      hiddenInput.type = 'file'; hiddenInput.id = 'mpai-file-input'; hiddenInput.multiple = true; hiddenInput.style.display='none';
      document.body.appendChild(hiddenInput);
    }
    hiddenInput.onchange = ()=>{
      Array.from(hiddenInput.files||[]).forEach(f=> mpaiState.attachments.push({file:f,name:f.name}));
      hiddenInput.value=''; mpaiRenderFiles();
    };

    const bar = document.getElementById('mpai-attach-previews');
    if (bar) { bar.addEventListener('click', ()=> hiddenInput.click()); }

    // 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 listeners collegati.');
  }

  // Bind on ready (se la UI è già presente)
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => { if ($mp('#mpAI')) mpaiBindUI(); });
  } else {
    if ($mp('#mpAI')) mpaiBindUI();
  }

  console.log('✅ MPAI: pronta.');
})();