Nessun oggetto della modifica
Nessun oggetto della modifica
Riga 196: Riga 196:


/** INVIO PROMPT → OpenAI proxy */
/** INVIO PROMPT → OpenAI proxy */
/** INVIO PROMPT → OpenAI proxy (versione robusta) */
window.sendPrompt = async function(){
window.sendPrompt = async function(){
   const ta = $mp('#mpai-input');
   try{
  if (!ta) return;
    const ta = document.querySelector('#mpai-input');
  const model = ($mp('#mpai-model')?.value || 'gpt-4o-2024-05-13').trim();
    if (!ta) return;
  const temp  = parseFloat($mp('#mpai-temp')?.value || '0.7') || 0.7;
 
  const sanitizedOnly = !!($mp('#mpai-sanitized-only')?.checked);
    const model = (document.querySelector('#mpai-model')?.value || 'gpt-4o-2024-05-13').trim();
    const temp  = parseFloat(document.querySelector('#mpai-temp')?.value || '0.7') || 0.7;
    const sanitizedOnly = !!(document.querySelector('#mpai-sanitized-only')?.checked);


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


  // Se non c'è progetto, attiva bozza locale
    // Se non c'è progetto, attiva bozza locale
  if (!mpaiState.currentProject && !mpaiState.sessionId){  
    if (!mpaiState.currentProject && !mpaiState.sessionId){
    mpaiEnsureSession();
      mpaiEnsureSession?.();
    mpaiLoadSession(mpaiState.sessionId);
      mpaiLoadSession?.(mpaiState.sessionId);
  }
    }


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


  // Contesto
    // Contesto
  const contextName = mpaiState.currentProject
    const contextName = mpaiState.currentProject
    ? `Progetto: ${mpaiState.currentProject}`
      ? `Progetto: ${mpaiState.currentProject}`
    : `Sessione: ${mpaiState.sessionMeta?.title || 'Nuova conversazione'}`;
      : `Sessione: ${mpaiState.sessionMeta?.title || 'Nuova conversazione'}`;


  const fileNames = mpaiState.attachments.map(a => a.name).join(', ');
    const fileNames = (mpaiState.attachments||[]).map(a => a.name).join(', ');
  const preface = `${contextName}
    const preface = `${contextName}
File allegati${sanitizedOnly ? ' (sanificati)' : ''}: ${fileNames || 'nessuno'}
File allegati${sanitizedOnly ? ' (sanificati)' : ''}: ${fileNames || 'nessuno'}


Riga 231: Riga 234:
${content}`;
${content}`;


  // Placeholder “Elaboro…”
    // Placeholder “Elaboro…”
  const pending = document.createElement('div');
    const pending = document.createElement('div');
  pending.className = 'mpai-msg';
    pending.className = 'mpai-msg';
  pending.innerHTML = '<em class="muted">Elaboro…</em>';
    pending.innerHTML = '<em class="muted">Elaboro…</em>';
  const chatEl = $mp('#mpai-chat');
    const chatEl = document.querySelector('#mpai-chat');
  chatEl && chatEl.appendChild(pending);
    chatEl && chatEl.appendChild(pending);


  try{
    // Chiamata al proxy PHP
     const r = await fetch('/dashboard/api/openai_project_gpt.php', {
     const r = await fetch('/dashboard/api/openai_project_gpt.php', {
       method: 'POST',
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
       headers: { 'Content-Type': 'application/json' },
       credentials: 'include',
       credentials: 'include',
      mode: 'same-origin',
       cache: 'no-store',
       cache: 'no-store',
       body: JSON.stringify({ model, prompt: preface })
       body: JSON.stringify({ model, prompt: preface })
     });
     });


     const raw = await r.text();
     const raw = await r.text(); // leggiamo SEMPRE testo
     pending.remove?.();
     pending.remove?.();


Riga 254: Riga 256:
       console.error('GPT proxy error', r.status, r.statusText, raw);
       console.error('GPT proxy error', r.status, r.statusText, raw);
       mpaiAppendMsg('error', `HTTP ${r.status} ${r.statusText}\n\n${raw || '(nessun body)'}`);
       mpaiAppendMsg('error', `HTTP ${r.status} ${r.statusText}\n\n${raw || '(nessun body)'}`);
       mpaiSaveLocal(); return;
       mpaiSaveLocal?.();
      return;
     }
     }


    // Il server a volte manda JSON “dentro una stringa”.
    // Parse robusto: prova una volta, se esce una stringa prova di nuovo.
     let j;
     let j;
     try { j = JSON.parse(raw); }
     try {
     catch(parseErr){
      j = JSON.parse(raw);
       console.error('JSON parse error', parseErr, raw);
      if (typeof j === 'string') {
        try { j = JSON.parse(j); } catch {}
      }
     } catch (e) {
       console.error('JSON parse error', e, raw);
       mpaiAppendMsg('error', `Risposta non in JSON:\n\n${raw.slice(0,1500)}`);
       mpaiAppendMsg('error', `Risposta non in JSON:\n\n${raw.slice(0,1500)}`);
       mpaiSaveLocal(); return;
       mpaiSaveLocal?.();
      return;
     }
     }


     if (j.status === 'ok' && j.result){
     if (j && j.status === 'ok' && j.result){
       // Se è bozza, rinomina titolo alla prima risposta
       // Se è bozza, rinomina titolo alla prima risposta
       if (!mpaiState.currentProject && mpaiState.sessionMeta && mpaiState.sessionMeta.title === 'Nuova conversazione'){
       if (!mpaiState.currentProject && mpaiState.sessionMeta && mpaiState.sessionMeta.title === 'Nuova conversazione'){
         mpaiState.sessionMeta.title = (content.slice(0,48) || 'Nuova conversazione');
         mpaiState.sessionMeta.title = (content.slice(0,48) || 'Nuova conversazione');
         mpaiSaveSession();
         mpaiSaveSession?.();
       }
       }
       mpaiState.history.push({role:'assistant', content: j.result});
       mpaiState.history.push({role:'assistant', content: j.result});
       mpaiAppendMsg('assistant', j.result);
       mpaiAppendMsg('assistant', j.result);
       mpaiSaveLocal();
       mpaiSaveLocal?.();
     } else {
     } else {
       const err = j.error || 'Errore sconosciuto';
       const err = (j && (j.error || j.message)) || 'Errore sconosciuto';
       mpaiAppendMsg('error', `API error: ${err}\n\nRAW:\n${(j.raw||'').slice(0,1500)}`);
       mpaiAppendMsg('error', `API error: ${err}\n\nRAW:\n${raw.slice(0,1500)}`);
       mpaiSaveLocal();
       mpaiSaveLocal?.();
     }
     }
   } catch(e){
   } catch(e){
    pending.remove?.();
     console.error('Network/JS error', e);
     console.error('Network/JS error', e);
     mpaiAppendMsg('error', `Errore di rete/JS: ${e.message}`);
     mpaiAppendMsg('error', `Errore di rete/JS: ${e.message}`);
     mpaiSaveLocal();
     mpaiSaveLocal?.();
   }
   }
};
};


/** Bind UI quando la pagina è pronta (solo se la UI AI esiste) */
/** Bind UI quando la pagina è pronta (solo se la UI AI esiste) */

Versione delle 07:40, 30 set 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;
}

// 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 */
/** INVIO PROMPT → OpenAI proxy (versione robusta) */
window.sendPrompt = async function(){
  try{
    const ta = document.querySelector('#mpai-input');
    if (!ta) return;

    const model = (document.querySelector('#mpai-model')?.value || 'gpt-4o-2024-05-13').trim();
    const temp  = parseFloat(document.querySelector('#mpai-temp')?.value || '0.7') || 0.7;
    const sanitizedOnly = !!(document.querySelector('#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 = document.querySelector('#mpai-chat');
    chatEl && chatEl.appendChild(pending);

    // Chiamata al proxy PHP
    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({ model, prompt: preface })
    });

    const raw = await r.text(); // leggiamo SEMPRE testo
    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;
    }

    // Il server a volte manda JSON “dentro una stringa”.
    // Parse robusto: prova una volta, se esce una stringa prova di nuovo.
    let j;
    try {
      j = JSON.parse(raw);
      if (typeof j === 'string') {
        try { j = JSON.parse(j); } catch {}
      }
    } catch (e) {
      console.error('JSON parse error', e, raw);
      mpaiAppendMsg('error', `Risposta non in JSON:\n\n${raw.slice(0,1500)}`);
      mpaiSaveLocal?.();
      return;
    }

    if (j && 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 && (j.error || j.message)) || 'Errore sconosciuto';
      mpaiAppendMsg('error', `API error: ${err}\n\nRAW:\n${raw.slice(0,1500)}`);
      mpaiSaveLocal?.();
    }
  } catch(e){
    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.');
})();