Nessun oggetto della modifica
Nessun oggetto della modifica
Riga 1: Riga 1:
<html>
<html>
<h2>🔧 Dashboard Operativa – Masticationpedia</h2>
  <h2>🔧 Dashboard Operativa – Masticationpedia</h2>
<p><em>Centro di comando per progetti, API, file e backup</em></p>
  <p><em>Centro di comando per progetti, API, file e backup</em></p>


<a href="/dashboard/api/log_view.php" target="_blank">🧾 Apri Log Dashboard</a>
  <a href="/dashboard/api/log_view.php" target="_blank">🧾 Apri Log Dashboard</a>
<button onclick="clearServerLog()">🧹 Svuota Log</button>
  <button onclick="clearServerLog()">🧹 Svuota Log</button>


<!-- 🔘 Pulsanti -->
  <!-- 🔘 Pulsanti -->
<div style="margin: 2rem 0; display: flex; flex-wrap: wrap; gap: 1rem;">
  <div style="margin: 2rem 0; display: flex; flex-wrap: wrap; gap: 1rem;">
  <button class="dashboard-toggle" onclick="toggleDashboardBox('api-settings')">⚙️ Connessione API</button>
    <button class="dashboard-toggle" onclick="toggleDashboardBox('api-settings')">⚙️ Connessione API</button>
  <button class="dashboard-toggle" onclick="toggleDashboardBox('project-status')">📊 Stato Progetti</button>
    <button class="dashboard-toggle" onclick="toggleDashboardBox('project-status')">📊 Stato Progetti</button>
  <button class="dashboard-toggle" onclick="toggleDashboardBox('chatgpt-plus')">🤖 ChatGPT plus</button>
    <button class="dashboard-toggle" onclick="toggleDashboardBox('chatgpt-plus')">🤖 ChatGPT plus</button>
  <button class="dashboard-toggle" onclick="toggleDashboardBox('test-tools')">🧪 Strumenti di Test</button>
    <button class="dashboard-toggle" onclick="toggleDashboardBox('test-tools')">🧪 Strumenti di Test</button>
  <button class="dashboard-toggle" onclick="toggleDashboardBox('activity-log')">📘 Registro Attività</button>
    <button class="dashboard-toggle" onclick="toggleDashboardBox('activity-log')">📘 Registro Attività</button>
    <button class="dashboard-toggle" onclick="showMpAI()">🤖 Masticationpedia AI</button>
  </div>


   <button class="dashboard-toggle"
   <!-- ============== MASTICATIONPEDIA AI (schermata unica) ============== -->
           onclick="(window.showMpAI ? showMpAI() : (function(){var ai=document.getElementById('mpAI'); if(ai) ai.style.display='block';})())">
  <div id="mpAI" class="mpai-root" style="display:none">
    🤖 Masticationpedia AI
    <!-- Header -->
  </button>
    <div class="mpai-header">
</div>
      <div class="mpai-title">🤖 Masticationpedia <b>AI</b></div>
      <div class="mpai-actions">
        <button id="mpai-new-chat" class="mpai-btn ghost">Nuova chat</button>
        <button id="mpai-clear" class="mpai-btn danger">Pulisci conversazione</button>
        <button id="mpai-save-as-project" class="mpai-btn">💾 Salva come progetto</button>
      </div>
    </div>
 
    <div class="mpai-grid">
      <!-- Sidebar sinistra: Progetti -->
      <aside class="mpai-col mpai-left">
        <h4>📂 Progetti</h4>
        <div id="mpai-project-list" class="mpai-projects">
           <em class="muted">Carico progetti…</em>
        </div>
        <div class="mpai-new-project">
          <input id="mpai-project-name" type="text" placeholder="Nuovo progetto (A-Z 0-9 _ -)" />
          <button id="mpai-project-create" class="mpai-btn">Crea</button>
        </div>
        <div class="mpai-help muted">
          Ogni progetto mantiene la sua cronologia locale (browser).
        </div>
      </aside>


<script>
      <!-- Chat centrale -->
/* ===== Fallback/utility sempre caricati ===== */
      <main class="mpai-col mpai-center">
function toggleDashboardBox(id){
        <!-- Allegati (solo elenco locale per ora) -->
  var el = document.getElementById(id);
        <div id="mpai-uploads" class="mpai-uploads" data-state="idle">
  if (!el) return;
          <div class="mpai-dropzone" id="mpai-dropzone">
  var ai = document.getElementById('mpAI'); if (ai) ai.style.display='none';
            <div>
  el.style.display = (el.style.display === 'none' || !el.style.display) ? 'block' : 'none';
              <div class="muted" style="margin-bottom:6px;">Trascina qui file <b>sanificati</b> oppure</div>
}
              <label class="mpai-file-label">
async function clearServerLog(){
                <input id="mpai-file-input" type="file" multiple style="display:none;">
  try{
                <span class="mpai-btn">📂 Aggiungi file</span>
    const r = await fetch('/dashboard/api/log_clear.php',{method:'POST',credentials:'include'});
              </label>
    const j = await r.json().catch(()=>({ok:false}));
            </div>
    alert(j && j.ok ? 'Log svuotato.' : 'Impossibile svuotare il log.');
          </div>
  }catch(e){ alert('Errore rete: '+e.message); }
          <div id="mpai-file-list" class="mpai-filelist"></div>
}
        </div>
function uploadToOpenAI(){ alert('Prossimo step: upload verso OpenAI.'); }
window.showMpAI = function(){
  ['api-settings','project-status','chatgpt-plus','test-tools','activity-log']
    .forEach(function(id){ var el=document.getElementById(id); if(el) el.style.display='none'; });
  var ai = document.getElementById('mpAI');
  if (ai) ai.style.display='block';
};


/* ========= MPAI APP ========= */
        <!-- Messaggi -->
document.addEventListener('DOMContentLoaded', function(){
        <div id="mpai-chat" class="mpai-chat">
          <div class="mpai-msg mpai-hint">
            Benvenuto. Seleziona un progetto a sinistra, oppure scrivi subito: se non c’è un progetto creo una conversazione locale. Le risposte appariranno qui sotto in ordine, come in ChatGPT.
          </div>
        </div>


(function(){
        <!-- Prompt -->
  // Stato
        <div class="mpai-composer">
  let currentProject = null;    // progetto “reale” (server) o null
          <textarea id="mpai-input" rows="3" placeholder="Scrivi qui la tua domanda..."></textarea>
  let sessionId = null;          // id bozza locale se non c'è progetto
          <button id="mpai-send" class="mpai-btn primary">Invia</button>
  let sessionMeta = null;        // {title, updated}
        </div>
  let history = [];
      </main>
  let attachments = [];


  // Shortcuts
      <!-- Sidebar destra: Impostazioni -->
  const $ = s => document.querySelector(s);
      <aside class="mpai-col mpai-right">
  const chatEl = $('#mpai-chat');
        <h4>⚙️ Impostazioni</h4>
  const fileListEl = $('#mpai-file-list');
        <label class="mpai-field">Modello
          <select id="mpai-model">
            <option value="gpt-4o-2024-05-13" selected>gpt-4o-2024-05-13</option>
            <option value="gpt-4-turbo-2024-04-09">gpt-4-turbo-2024-04-09</option>
          </select>
        </label>
        <label class="mpai-field">Temperatura
          <input id="mpai-temp" type="number" min="0" max="1" step="0.1" value="0.7">
        </label>
        <label class="mpai-check">
          <input id="mpai-sanitized-only" type="checkbox" checked>
          Usa solo file <b>sanificati</b> come contesto
        </label>
        <div class="mpai-help muted">
          Le API key restano lato server (file sicuro). Il browser non vede mai le chiavi.
        </div>
      </aside>
    </div>
  </div>


   // UI helpers
   <!-- ============ STILI (CSS) ============ -->
   function appendMsg(role, content){
   <style>
     const div = document.createElement('div');
    .mpai-root{--bg:#f7f8fa;--card:#fff;--muted:#6b7280;--line:#e5e7eb;--text:#111827;
     div.className = 'mpai-msg ' + (role === 'user' ? 'user' : (role === 'assistant' ? 'assistant' : 'error'));
      --primary:#0a7cff;--danger:#e74c3c;--accent:#eef2ff;
     div.innerHTML = `<div class="role">${role==='user'?'Tu':role==='assistant'?'GPT':'Errore'}</div>
      font-family: system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif; color:var(--text);}
                    <div class="content" style="white-space:pre-wrap">${content}</div>`;
    .mpai-header{display:flex;align-items:center;justify-content:space-between;margin:8px 0 12px}
     chatEl.appendChild(div);
    .mpai-title{font-size:20px;font-weight:700}
     chatEl.scrollTop = chatEl.scrollHeight;
    .mpai-actions{display:flex;gap:8px}
  }
    .mpai-btn{border:1px solid var(--line);background:var(--card);padding:.45rem .8rem;border-radius:8px;cursor:pointer}
  function saveLocal(){
    .mpai-btn:hover{background:#f3f4f6}
     if (!currentProject) return;
     .mpai-btn.primary{background:var(--primary);border-color:var(--primary);color:#fff}
     localStorage.setItem('mpai.hist.'+currentProject, JSON.stringify(history.slice(-200)));
     .mpai-btn.danger{background:var(--danger);border-color:var(--danger);color:#fff}
  }
    .mpai-btn.ghost{background:transparent}
  function loadLocal(project){
    .muted{color:var(--muted)}
     const raw = localStorage.getItem('mpai.hist.'+project);
    .mpai-grid{display:grid;grid-template-columns:260px minmax(0,1fr) 260px;gap:12px}
     history = raw ? JSON.parse(raw) : [];
     .mpai-col{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:12px}
     chatEl.innerHTML = '';
    .mpai-left,.mpai-right{max-height:75vh;overflow:auto}
     if (!history.length){
    .mpai-projects{display:flex;flex-direction:column;gap:6px;margin:8px 0}
      appendMsg('assistant', 'Pronto! Dimmi cosa vuoi fare su "'+project+'". Puoi allegare file sanificati.');
     .mpai-projects .row{display:flex;align-items:center;gap:6px;justify-content:space-between;border:1px solid var(--line);border-radius:8px;padding:6px 8px}
     } else {
     .mpai-projects .row .title{font-family:monospace;font-size:12px}
       history.forEach(m => appendMsg(m.role, m.content));
    .mpai-new-project{display:flex;gap:6px;margin-top:8px}
    .mpai-new-project input{flex:1 1 auto;padding:.45rem .6rem;border:1px solid var(--line);border-radius:8px}
    .mpai-field{display:flex;flex-direction:column;gap:4px;margin:8px 0}
     .mpai-field input,.mpai-field select{padding:.45rem .6rem;border:1px solid var(--line);border-radius:8px}
     .mpai-check{display:flex;align-items:center;gap:8px;margin:8px 0}
    .mpai-center{display:flex;flex-direction:column;gap:10px}
    .mpai-uploads{border:1px dashed var(--line);border-radius:12px;padding:10px;background:#fafafa}
    .mpai-dropzone{display:flex;align-items:center;justify-content:center;border-radius:10px;padding:12px;background:var(--accent);text-align:center}
    .mpai-filelist{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
    .mpai-filepill{display:flex;align-items:center;gap:6px;border:1px solid var(--line);border-radius:999px;padding:4px 8px;background:#fff;font-size:12px}
    .mpai-filepill button{border:none;background:transparent;cursor:pointer;color:#c00}
     .mpai-chat{display:flex;flex-direction:column;gap:10px;max-height:50vh;overflow:auto;border:1px solid var(--line);border-radius:12px;padding:10px;background:#fff}
    .mpai-msg{border:1px solid var(--line);border-radius:12px;padding:10px;background:#fff}
    .mpai-msg.user{border-color:#bee3f8;background:#eff6ff}
     .mpai-msg.assistant{border-color:#d1fae5;background:#f0fdf4}
     .mpai-msg.error{border-color:#fecaca;background:#fff1f2}
     .mpai-msg .role{font-weight:600;margin-bottom:4px}
    .mpai-hint{border-style:dashed;color:var(--muted)}
    .mpai-composer{display:flex;gap:8px;align-items:flex-end}
    .mpai-composer textarea{flex:1 1 auto;min-height:90px;border:1px solid var(--line);border-radius:12px;padding:10px}
     @media (max-width: 1100px){
       .mpai-grid{grid-template-columns:1fr}
      .mpai-left,.mpai-right{max-height:none}
     }
     }
   }
   </style>
  function renderFiles(){
    fileListEl.innerHTML = '';
    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 = ()=>{ attachments.splice(i,1); renderFiles(); };
      fileListEl.appendChild(pill);
    });
  }


   /* ===== Sessioni locali (bozze) ===== */
   <!-- ============ JS (tutto qui) ============ -->
  function ensureSession(){
  <script>
    if (!sessionId){
    // ---- Funzioni base / legacy ----
       sessionId = 'sess-' + Date.now();
    function toggleDashboardBox(id){
      sessionMeta = { title: 'Nuova conversazione', updated: Date.now() };
      var el = document.getElementById(id);
       localStorage.setItem('mpai.session.meta.'+sessionId, JSON.stringify(sessionMeta));
      if (!el) return;
       var ai = document.getElementById('mpAI'); if (ai) ai.style.display='none';
       el.style.display = (el.style.display === 'none' || !el.style.display) ? 'block' : 'none';
     }
     }
  }
    async function clearServerLog(){
  function saveSession(){
      try{
    if (!sessionId) return;
        const r = await fetch('/dashboard/api/log_clear.php',{method:'POST',credentials:'include'});
    const key = 'mpai.session.hist.' + sessionId;
        const j = await r.json().catch(()=>({ok:false}));
    localStorage.setItem(key, JSON.stringify(history.slice(-200)));
        alert(j && j.ok ? 'Log svuotato.' : 'Impossibile svuotare il log.');
    sessionMeta.updated = Date.now();
      }catch(e){ alert('Errore rete: '+e.message); }
    localStorage.setItem('mpai.session.meta.'+sessionId, JSON.stringify(sessionMeta));
  }
  function loadSession(id){
    sessionId = id;
    const key = 'mpai.session.hist.' + id;
    const raw = localStorage.getItem(key);
    history = raw ? JSON.parse(raw) : [];
    const metaRaw = localStorage.getItem('mpai.session.meta.'+id);
    sessionMeta = metaRaw ? JSON.parse(metaRaw) : {title:'Nuova conversazione', updated:Date.now()};
    chatEl.innerHTML = '';
    if (!history.length){
      appendMsg('assistant', 'Pronto! Dimmi cosa vuoi fare. Puoi allegare file sanificati.');
    } else {
      history.forEach(m => appendMsg(m.role, m.content));
     }
     }
  }
    function uploadToOpenAI(){ alert('Prossimo step: upload verso OpenAI.'); }
  function resetToNewChat(){
     function showMpAI(){
    history = [];
       ['api-settings','project-status','chatgpt-plus','test-tools','activity-log']
    attachments = [];
        .forEach(id => { var el = document.getElementById(id); if (el) el.style.display='none'; });
    renderFiles();
       var ai = document.getElementById('mpAI');
    chatEl.innerHTML = '';
       if (ai) ai.style.display = 'block';
     if (currentProject){
       appendMsg('assistant','Nuova chat per "'+currentProject+'".');
      saveLocal();
    } else {
      sessionId = null; sessionMeta = null;
      ensureSession();
       appendMsg('assistant','Nuova conversazione.');
       saveSession();
     }
     }
  }


  /* ===== Progetti (lista/crea) ===== */
    /* ========= MPAI APP ========= */
  async function loadProjects(){
    (function(){
     const box = $('#mpai-project-list');
      // Stato
    if (!box) return;
      let currentProject = null;     // progetto “reale” (server) o null
    box.innerHTML = '<em class="muted">Carico progetti…</em>';
      let sessionId = null;          // id bozza locale se non c'è progetto
    try{
      let sessionMeta = null;        // {title, updated}
      const r = await fetch('/dashboard/api/project_list.php', {cache:'no-store', credentials:'include'});
      let history = [];
      const j = await r.json();
      let attachments = [];
      const arr = Array.isArray(j) ? j : (Array.isArray(j.projects) ? j.projects : []);
 
      if (!arr.length){ box.innerHTML='<em class="muted">Nessun progetto</em>'; return; }
      // Shortcuts
      box.innerHTML='';
      const $ = s => document.querySelector(s);
      arr.forEach(item=>{
      const chatEl = $('#mpai-chat');
        const name = (typeof item === 'string') ? item : (item.name || item);
      const fileListEl = $('#mpai-file-list');
        const row = document.createElement('div');
 
        row.className = 'row';
      // UI helpers
        row.innerHTML = `<span class="title">${name}</span>
      function appendMsg(role, content){
                        <span><button class="mpai-btn" data-open="${name}">Apri</button></span>`;
        const div = document.createElement('div');
        row.querySelector('[data-open]').onclick = ()=>{ currentProject=name; loadLocal(name); };
        div.className = 'mpai-msg ' + (role === 'user' ? 'user' : (role === 'assistant' ? 'assistant' : 'error'));
        box.appendChild(row);
        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 saveLocal(){
        if (!currentProject) return;
        localStorage.setItem('mpai.hist.'+currentProject, JSON.stringify(history.slice(-200)));
      }
      function loadLocal(project){
        const raw = localStorage.getItem('mpai.hist.'+project);
        history = raw ? JSON.parse(raw) : [];
        chatEl.innerHTML = '';
        if (!history.length){
          appendMsg('assistant', 'Pronto! Dimmi cosa vuoi fare su "'+project+'". Allegami anche file sanificati se servono.');
        } else {
          history.forEach(m => appendMsg(m.role, m.content));
        }
      }
      function renderFiles(){
        fileListEl.innerHTML = '';
        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 = ()=>{ attachments.splice(i,1); renderFiles(); };
          fileListEl.appendChild(pill);
        });
      }
 
      // ======== Sessioni locali (bozze senza progetto) ========
      function ensureSession(){
        if (!sessionId){
          sessionId = 'sess-' + Date.now();
          sessionMeta = { title: 'Nuova conversazione', updated: Date.now() };
          localStorage.setItem('mpai.session.meta.'+sessionId, JSON.stringify(sessionMeta));
        }
      }
      function saveSession(){
        if (!sessionId) return;
        const key = 'mpai.session.hist.' + sessionId;
        localStorage.setItem(key, JSON.stringify(history.slice(-200)));
        sessionMeta.updated = Date.now();
        localStorage.setItem('mpai.session.meta.'+sessionId, JSON.stringify(sessionMeta));
      }
      function loadSession(id){
        sessionId = id;
        const key = 'mpai.session.hist.' + id;
        const raw = localStorage.getItem(key);
        history = raw ? JSON.parse(raw) : [];
        const metaRaw = localStorage.getItem('mpai.session.meta.'+id);
        sessionMeta = metaRaw ? JSON.parse(metaRaw) : {title:'Nuova conversazione', updated:Date.now()};
        chatEl.innerHTML = '';
        if (!history.length){
          appendMsg('assistant', 'Pronto! Dimmi cosa vuoi fare. Puoi allegare file sanificati.');
        } else {
          history.forEach(m => appendMsg(m.role, m.content));
        }
      }
      function resetToNewChat(){
        history = [];
        attachments = [];
        renderFiles();
        chatEl.innerHTML = '';
        if (currentProject){
          appendMsg('assistant','Nuova chat per "'+currentProject+'".');
          saveLocal();
        } else {
          sessionId = null; sessionMeta = null;
          ensureSession();
          appendMsg('assistant','Nuova conversazione.');
          saveSession();
        }
      }
 
      // ======== Progetti (sinistra) ========
      async function loadProjects(){
        const box = $('#mpai-project-list');
        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 = ()=>{ currentProject=name; loadLocal(name); };
            box.appendChild(row);
          });
          if (!currentProject){
            const first = (typeof arr[0]==='string')?arr[0]:(arr[0].name||arr[0]);
            currentProject = first;
            loadLocal(first);
          }
        }catch(e){
          box.innerHTML = '<span class="muted">Errore caricamento: '+e.message+'</span>';
        }
      }
      async function createProject(){
        const inp = $('#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 loadProjects(); currentProject=name; history=[]; loadLocal(name); inp.value=''; }
          else { alert('Errore creazione: '+(j.error||'')); }
        }catch(e){ alert('Errore rete: '+e.message); }
      }
 
      // ======== Upload locale (solo elenco) ========
      const dz = $('#mpai-dropzone');
      const fi = $('#mpai-file-input');
      dz.addEventListener('dragover', e => { e.preventDefault(); dz.style.opacity = .85; });
      dz.addEventListener('dragleave', () => { dz.style.opacity = 1; });
      dz.addEventListener('drop', e => {
        e.preventDefault(); dz.style.opacity = 1;
        Array.from(e.dataTransfer.files||[]).forEach(f=> attachments.push({file:f,name:f.name}));
        renderFiles();
       });
       });
       if (!currentProject){
       fi.addEventListener('change', () => {
         const first = (typeof arr[0]==='string')?arr[0]:(arr[0].name||arr[0]);
         Array.from(fi.files||[]).forEach(f=> attachments.push({file:f,name:f.name}));
        currentProject = first;
        fi.value=''; renderFiles();
        loadLocal(first);
      }
    }catch(e){
      box.innerHTML = '<span class="muted">Errore caricamento: '+e.message+'</span>';
    }
  }
  async function createProject(){
    const inp = $('#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 loadProjects(); currentProject=name; history=[]; loadLocal(name); inp.value=''; }
      else { alert('Errore creazione: '+(j.error||'')); }
    }catch(e){ alert('Errore rete: '+e.message); }
  }


  /* ===== Upload locale (visuale) ===== */
      // ======== Invio prompt via proxy sicuro ========
  const dz = $('#mpai-dropzone');
      async function sendPrompt(){
  const fi = $('#mpai-file-input');
        const ta = $('#mpai-input');
  if (dz){
        const model = ($('#mpai-model')?.value || 'gpt-4o-2024-05-13').trim();
    dz.addEventListener('dragover', e => { e.preventDefault(); dz.style.opacity = .85; });
        const temp  = parseFloat($('#mpai-temp')?.value || '0.7') || 0.7;
    dz.addEventListener('dragleave', () => { dz.style.opacity = 1; });
        const sanitizedOnly = !!($('#mpai-sanitized-only')?.checked);
    dz.addEventListener('drop', e => {
      e.preventDefault(); dz.style.opacity = 1;
      Array.from(e.dataTransfer.files||[]).forEach(f=> attachments.push({file:f,name:f.name}));
      renderFiles();
    });
  }
  if (fi){
    fi.addEventListener('change', () => {
      Array.from(fi.files||[]).forEach(f=> attachments.push({file:f,name:f.name}));
      fi.value=''; renderFiles();
    });
  }


  /* ===== Invio prompt (via proxy sicuro) ===== */
        const content = (ta.value || '').trim();
  async function sendPrompt(){
        if (!content){ ta.focus(); return; }
    const ta = $('#mpai-input');
    const model = ($('#mpai-model')?.value || 'gpt-4o-2024-05-13').trim();
    const temp  = parseFloat($('#mpai-temp')?.value || '0.7') || 0.7;
    const sanitizedOnly = !!($('#mpai-sanitized-only')?.checked);


    const content = (ta.value || '').trim();
        // abilita bozza locale se nessun progetto
    if (!content){ ta.focus(); return; }
        if (!currentProject && !sessionId){
          ensureSession();
          loadSession(sessionId);
        }


    // Se non c'è un progetto, abilita la bozza locale
        // mostra subito il messaggio dell’utente
    if (!currentProject && !sessionId){ ensureSession(); loadSession(sessionId); }
        history.push({role:'user', content});
        appendMsg('user', content);
        ta.value = '';


    // Mostra subito il messaggio dell'utente
        // contesto: progetto o sessione locale
    history.push({role:'user', content});
        const contextName = currentProject
    appendMsg('user', content);
          ? ('Progetto: ' + currentProject)
    ta.value = '';
          : ('Sessione: ' + (sessionMeta && sessionMeta.title ? sessionMeta.title : 'Nuova conversazione'));


    // Contesto: progetto o sessione locale
        const fileNames = attachments.map(a => a.name).join(', ');
    const contextName = currentProject
      ? `Progetto: ${currentProject}`
      : `Sessione: ${sessionMeta?.title || 'Nuova conversazione'}`;


    const fileNames = 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 235: Riga 356:
${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>';
    chatEl.appendChild(pending);
        chatEl.appendChild(pending);
    chatEl.scrollTop = chatEl.scrollHeight;
        chatEl.scrollTop = chatEl.scrollHeight;


    try{
        try{
      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', // per Basic Auth
            credentials: 'include',
        body: JSON.stringify({ model, prompt: preface })
            body: JSON.stringify({ model, prompt: preface })
      });
          });
 
          // gestione 401 (Basic Auth)
          if (r.status === 401){
            pending.remove();
            appendMsg('error','Autenticazione richiesta (401). Riprova: comparirà il popup credenziali.');
            return;
          }


      const txt = await r.text();
          const txt = await r.text();
      let j; try{ j = JSON.parse(txt); } catch { j = { status:'error', raw: txt }; }
          let j; try{ j = JSON.parse(txt); } catch { j = { status:'error', raw: txt }; }


      pending.remove();
          pending.remove();


      if (j.status === 'ok' && j.result){
          if (j.status === 'ok' && j.result){
        if (!currentProject && sessionMeta && sessionMeta.title === 'Nuova conversazione'){
            if (!currentProject && sessionMeta && sessionMeta.title === 'Nuova conversazione'){
          sessionMeta.title = (content.slice(0, 48) || 'Nuova conversazione');
              sessionMeta.title = (content.slice(0, 48) || 'Nuova conversazione');
           saveSession();
              saveSession();
            }
            history.push({role:'assistant', content: j.result});
            appendMsg('assistant', j.result);
            saveLocal();
          } else {
            const err = j.error || 'Errore sconosciuto';
            history.push({role:'error', content: err});
            appendMsg('error', err + (j.raw ? '\n\nRAW:\n' + j.raw : ''));
            saveLocal();
           }
        } catch(e){
          pending.remove();
          history.push({role:'error', content: e.message});
          appendMsg('error', e.message);
          saveLocal();
         }
         }
        history.push({role:'assistant', content: j.result});
        appendMsg('assistant', j.result);
        saveLocal();
      } else {
        const err = j.error || 'Errore sconosciuto';
        history.push({role:'error', content: err});
        appendMsg('error', err + (j.raw ? '\n\nRAW:\n' + j.raw : ''));
        saveLocal();
       }
       }
    } catch(e){
      pending.remove();
      history.push({role:'error', content: e.message});
      appendMsg('error', e.message);
      saveLocal();
    }
  }


  /* ===== Bind UI ===== */
      // ======== Bind ========
  document.addEventListener('click', (e)=>{
      document.addEventListener('click', (e)=>{
    if (e.target && e.target.id === 'mpai-send') sendPrompt();
        if (e.target && e.target.id==='mpai-send') sendPrompt();
    if (e.target && e.target.id === 'mpai-project-create') createProject();
        if (e.target && e.target.id==='mpai-project-create') createProject();
    if (e.target && e.target.id === 'mpai-new-chat') resetToNewChat();
        if (e.target && e.target.id==='mpai-new-chat') resetToNewChat();
    if (e.target && e.target.id === 'mpai-clear') resetToNewChat();
        if (e.target && e.target.id==='mpai-clear'){
    // (in futuro: mpai-save-as-project)
          if (!confirm('Svuotare la conversazione locale?')) return;
  });
          resetToNewChat();
  document.addEventListener('keydown', (e)=>{
        }
    if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
        if (e.target && e.target.id==='mpai-save-as-project'){
      const ta = $('#mpai-input');
          const name = prompt('Nome progetto da creare e salvare questa chat:');
      if (ta && ta === document.activeElement) { e.preventDefault(); sendPrompt(); }
          if (!name) return;
    }
          $('#mpai-project-name').value = name;
  });
          createProject();
 
        }
  /* ===== Boot ===== */
      });
  loadProjects();
      document.addEventListener('keydown', (e)=>{
  if (!currentProject) { ensureSession(); loadSession(sessionId); }
        const ta = $('#mpai-input');
        if ((e.metaKey||e.ctrlKey) && e.key==='Enter' && document.activeElement===ta){
          e.preventDefault(); sendPrompt();
        }
      });


})(); // chiude l’IIFE
      // ======== Boot ========
}); // fine DOMContentLoaded
      loadProjects();
      if (!currentProject){ ensureSession(); loadSession(sessionId); }
    })();
  </script>


</script>
  <!-- ========== SEZIONI LEGACY (nascoste di default) ========== -->


<!-- ============== MASTICATIONPEDIA AI (schermata unica) ============== -->
  <!-- 📦 Connessione API -->
<div id="mpAI" class="mpai-root" style="display:none">
  <div id="api-settings" style="display:none; padding:1rem; border:1px solid #ccc; border-radius:8px; background:#f9f9f9;">
  <!-- Header -->
    <strong>Connessione API (protetta dal server)</strong><br><br>
  <div class="mpai-header">
    <label>Modello</label>
    <div class="mpai-title">🤖 Masticationpedia <b>AI</b></div>
    <select id="model-select" style="width:100%; margin-bottom:0.5rem;">
     <div class="mpai-actions">
      <option value="gpt-4o-2024-05-13" selected>gpt-4o-2024-05-13</option>
      <button id="mpai-new-chat" class="mpai-btn ghost">Nuova chat</button>
      <option value="gpt-4-turbo-2024-04-09">gpt-4-turbo-2024-04-09</option>
      <button id="mpai-clear" class="mpai-btn danger">Pulisci conversazione</button>
    </select>
      <button id="mpai-save-as-project" class="mpai-btn">💾 Salva come progetto</button>
    <label>Prompt di test</label>
    </div>
     <textarea id="test-prompt" rows="3" style="width:100%; margin-bottom:0.5rem;">Dimmi una curiosità sulla mandibola</textarea><br>
    <button onclick="(async()=>{try{const r=await fetch('/dashboard/api/openai_project_gpt.php',{method:'POST',headers:{'Content-Type':'application/json'},credentials:'include',body:JSON.stringify({model:document.getElementById('model-select').value,prompt:document.getElementById('test-prompt').value})}); const j=await r.json(); document.getElementById('api-result').textContent=JSON.stringify(j,null,2);}catch(e){document.getElementById('api-result').textContent=e.message;}})()">▶️ Esegui</button>
    <pre id="api-result" style="background:#f0f0f0; padding:1rem; border:1px solid #ccc; margin-top:1rem; white-space:pre-wrap;"></pre>
   </div>
   </div>


   <div class="mpai-grid">
   <!-- 📊 Stato Progetti (demo) -->
    <!-- Sidebar sinistra: Progetti -->
   <div id="project-status" style="display:none; padding:1rem; border:1px solid #ccc; border-radius:8px; background:#f9f9f9;">
    <aside class="mpai-col mpai-left">
    <strong>📊 Stato Progetti</strong><br><br>
      <h4>📂 Progetti</h4>
      <div id="mpai-project-list" class="mpai-projects">
        <em class="muted">Carico progetti…</em>
      </div>
      <div class="mpai-new-project">
        <input id="mpai-project-name" type="text" placeholder="Nuovo progetto (A-Z 0-9 _ -)" />
        <button id="mpai-project-create" class="mpai-btn">Crea</button>
      </div>
      <div class="mpai-help muted">
        Ogni progetto mantiene la sua cronologia locale (browser).
      </div>
    </aside>
 
    <!-- Chat centrale -->
    <main class="mpai-col mpai-center">
      <div id="mpai-uploads" class="mpai-uploads" data-state="idle">
        <div class="mpai-dropzone" id="mpai-dropzone">
          <div>
            <div class="muted" style="margin-bottom:6px;">Trascina qui file <b>sanificati</b> oppure</div>
            <label class="mpai-file-label">
              <input id="mpai-file-input" type="file" multiple style="display:none;">
              <span class="mpai-btn">📂 Aggiungi file</span>
            </label>
          </div>
        </div>
        <div id="mpai-file-list" class="mpai-filelist"></div>
      </div>
 
      <div id="mpai-chat" class="mpai-chat">
        <div class="mpai-msg mpai-hint">
          Benvenuto. Seleziona un progetto a sinistra, oppure scrivi subito: se non c’è un progetto
          creo una conversazione locale. Le risposte appariranno qui sotto in ordine, come in ChatGPT.
        </div>
      </div>
 
      <div class="mpai-composer">
        <textarea id="mpai-input" rows="3" placeholder="Scrivi qui la tua domanda..."></textarea>
        <button id="mpai-send" class="mpai-btn primary">Invia</button>
      </div>
    </main>
 
    <!-- Sidebar destra: Impostazioni -->
    <aside class="mpai-col mpai-right">
      <h4>⚙️ Impostazioni</h4>
      <label class="mpai-field">Modello
        <select id="mpai-model">
          <option value="gpt-4o-2024-05-13" selected>gpt-4o-2024-05-13</option>
          <option value="gpt-4-turbo-2024-04-09">gpt-4-turbo-2024-04-09</option>
        </select>
      </label>
      <label class="mpai-field">Temperatura
        <input id="mpai-temp" type="number" min="0" max="1" step="0.1" value="0.7">
      </label>
      <label class="mpai-check">
        <input id="mpai-sanitized-only" type="checkbox" checked>
        Usa solo file <b>sanificati</b> come contesto
      </label>
      <div class="mpai-help muted">
        Le API key restano lato server (file sicuro). Il browser non vede mai le chiavi.
      </div>
    </aside>
   </div>
</div>
 
<!-- ========== SEZIONI LEGACY (nascoste di default) ========== -->
 
<!-- 📦 Connessione API -->
<div id="api-settings" style="display:none; padding:1rem; border:1px solid #ccc; border-radius:8px; background:#f9f9f9;">
  <strong>Connessione API (protetta dal server)</strong><br><br>
  <label>Modello</label>
  <select id="model-select" style="width:100%; margin-bottom:0.5rem;">
    <option value="gpt-4o-2024-05-13" selected>gpt-4o-2024-05-13</option>
    <option value="gpt-4-turbo-2024-04-09">gpt-4-turbo-2024-04-09</option>
  </select>
  <label>Prompt di test</label>
  <textarea id="test-prompt" rows="3" style="width:100%; margin-bottom:0.5rem;">Dimmi una curiosità sulla mandibola</textarea><br>
  <button onclick="(async()=>{try{const r=await fetch('/dashboard/api/openai_project_gpt.php',{method:'POST',headers:{'Content-Type':'application/json'},credentials:'include',body:JSON.stringify({model:document.getElementById('model-select').value,prompt:document.getElementById('test-prompt').value})}); const j=await r.json(); document.getElementById('api-result').textContent=JSON.stringify(j,null,2);}catch(e){document.getElementById('api-result').textContent=e.message;}})()">▶️ Esegui</button>
  <pre id="api-result" style="background:#f0f0f0; padding:1rem; border:1px solid #ccc; margin-top:1rem; white-space:pre-wrap;"></pre>
</div>
 
<!-- 📊 Stato Progetti -->
<div id="project-status" style="display:none; padding:1rem; border:1px solid #ccc; border-radius:8px; background:#f9f9f9;">
  <strong>📊 Stato Progetti</strong><br><br>
  <div style="margin-top: 1rem;">
    <label><strong>🧠 Risposta GPT – Analisi progetto:</strong></label>
     <div id="gptResponse" class="gpt-card" style="margin-top:8px; background:#fff; border:1px solid #e6e6e6; border-radius:10px; padding:16px; font: 16px/1.6 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; color:#1f2328; box-shadow: 0 1px 2px rgba(0,0,0,.04);">
     <div id="gptResponse" class="gpt-card" style="margin-top:8px; background:#fff; border:1px solid #e6e6e6; border-radius:10px; padding:16px; font: 16px/1.6 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; color:#1f2328; box-shadow: 0 1px 2px rgba(0,0,0,.04);">
       <em style="opacity:.7">Qui apparirà la risposta generata da GPT sull’analisi del progetto…</em>
       <em style="opacity:.7">Qui apparirà la risposta generata da GPT sull’analisi del progetto…</em>
     </div>
     </div>
    <div id="gptParams" style="margin-top:14px;padding:10px;border:1px dashed #ccc;border-radius:8px;background:#fcfcfc;">
  </div>
      <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
        <input id="p_goal"        placeholder="🎯 Obiettivo (es. mini-corso AI)" />
        <input id="p_audience"    placeholder="👥 Pubblico (es. medici, studenti)" />
        <input id="p_deliverable" placeholder="📦 Output atteso (es. 6 capitoli + quiz)" />
        <input id="p_constraints" placeholder="⏱️ Vincoli (tempo, budget, tool)" />
      </div>
      <div style="margin-top:10px;">
        <button id="btnAnalyze" style="background:#0a7cff;color:#fff;border:none;padding:.5rem .9rem;border-radius:8px;font-weight:600;"
          onclick="(function(){const c={goal:document.getElementById('p_goal').value,audience:document.getElementById('p_audience').value,deliverable:document.getElementById('p_deliverable').value,constraints:document.getElementById('p_constraints').value}; const fake=`# Sommario introduttivo
Obiettivo: ${c.goal||'—'}
Pubblico: ${c.audience||'—'}


## Step
  <!-- 📘 Registro Attività -->
- Pianificazione
  <div id="activity-log" style="display:none; padding:1rem; border:1px solid #ccc; border-radius:8px; background:#fdfdfd;">
- Esecuzione
    <strong>📘 Registro attività:</strong>
- Revisione
    <button onclick="(function(){const box=document.getElementById('activityLogContent'); if(box) box.textContent=''; })()" style="float:right; background:#e74c3c; color:white; border:none; padding:0.4rem 1rem; border-radius:6px; font-weight:bold;">🧹 Svuota</button>
 
    <div id="activityLogContent" style="margin-top:1rem; max-height:250px; overflow-y:auto; background:#f0f0f0; padding:1rem; border:1px solid #ccc; border-radius:6px; font-family:monospace; font-size:0.85rem;">
## Output atteso
      <em><span style="color:#888;">Registro avviato...</span></em>
${c.deliverable||'—'}
 
## Vincoli
${c.constraints||'—'}`; (function renderGpt(t){const box=document.getElementById('gptResponse'); if(!box){return} let html=(t||'').replace(/\r\n/g,'\n').replace(/^### (.*)$/gm,'<h3 style=\'margin:14px 0 6px;font-size:17px;\'>$1</h3>').replace(/^## (.*)$/gm,'<h2 style=\'margin:16px 0 8px;font-size:19px;\'>$1</h2>').replace(/^# (.*)$/gm,'<h1 style=\'margin:18px 0 10px;font-size:21px;\'>$1</h1>').replace(/^\- (.*)$/gm,'<li>$1</li>'); html=html.replace(/(<li>.*<\/li>\n?)+/gs, m=>`<ul style=\'margin:8px 0 14px 22px;\'>${m}</ul>`); html=html.split('\n').map(line=>{ if(/^<h\d|^<ul|^<li|^<\/ul>/.test(line)) return line; if(line.trim()==='') return ''; return `<p style='margin:6px 0;line-height:1.5'>${line}</p>`; }).join(''); box.innerHTML=html; })(fake); })()">Analizza con GPT</button>
      </div>
     </div>
     </div>
   </div>
   </div>
  <div class="projects-controls" style="margin-top:1.5rem; padding:1rem; border:1px solid #ddd; border-radius:6px; background:#fff;">
    <h4>📂 Gestione Progetti</h4>
    <input id="newProjectName" type="text" placeholder="Nome progetto (A-Z 0-9 _ -)" style="margin-right:6px; padding:4px;">
    <button id="btnCreateProject" style="background:#28a745; color:white; border:none; padding:0.3rem 0.8rem; border-radius:4px;"
      onclick="(async()=>{const name=(document.getElementById('newProjectName').value||'').trim(); if(!name){alert('Inserisci un nome'); 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 t=await r.text(); alert(t.includes('ok')?'Creato':'Vedi log/console');}catch(e){alert(e.message)}})()">Crea progetto</button>
    <ul id="projectsList" class="projects-list" style="margin-top:1rem; list-style:none; padding:0;"></ul>
  </div>
</div>
<!-- 📘 Registro Attività -->
<div id="activity-log" style="display:none; padding:1rem; border:1px solid #ccc; border-radius:8px; background:#fdfdfd;">
  <strong>📘 Registro attività:</strong>
  <button onclick="(function(){const box=document.getElementById('activityLogContent'); if(box) box.textContent=''; })()" style="float:right; background:#e74c3c; color:white; border:none; padding:0.4rem 1rem; border-radius:6px; font-weight:bold;">🧹 Svuota</button>
  <div id="activityLogContent" style="margin-top:1rem; max-height:250px; overflow-y:auto; background:#f0f0f0; padding:1rem; border:1px solid #ccc; border-radius:6px; font-family:monospace; font-size:0.85rem;">
    <em><span style="color:#888;">Registro avviato...</span></em>
  </div>
</div>
</html>
</html>

Versione delle 16:17, 27 set 2025

🔧 Dashboard Operativa – Masticationpedia

Centro di comando per progetti, API, file e backup

🧾 Apri Log Dashboard