Nessun oggetto della modifica
Etichetta: Annullato
Nessun oggetto della modifica
Etichetta: Annullato
Riga 9: Riga 9:
   <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('chatgpt-plus')">🤖 ChatGPT plus</button>
  <button class="dashboard-toggle"
    <button class="dashboard-toggle" onclick="toggleDashboardBox('test-tools')">🧪 Strumenti di Test</button>
  onclick="(window.showMpAI ? showMpAI() : (function(){var el=document.getElementById('mpAI'); if(el){ el.style.display='block'; el.scrollIntoView({behavior:'smooth'});} else { alert('#mpAI non trovato'); } })())">
     <button class="dashboard-toggle" onclick="toggleDashboardBox('activity-log')">📘 Registro Attività</button>
  🤖 Masticationpedia AI
    <button class="dashboard-toggle" onclick="showMpAI()">🤖 Masticationpedia AI</button>
</button>
<script>
  // Definito presto, prima di altri script
  function showMpAI(){
    // nascondi eventuali box legacy
    ['api-settings','project-status','chatgpt-plus','test-tools','activity-log']
      .forEach(function(id){ var el=document.getElementById(id); if(el) el.style.display='none'; });
    // mostra la UI AI
    var ai = document.getElementById('mpAI');
    if (ai){ ai.style.display='block'; ai.scrollIntoView({behavior:'smooth'}); }
    else { alert('Elemento #mpAI non trovato'); }
  }
</script>
 
 
  </div>
 
  <!-- ============== MASTICATIONPEDIA AI (schermata unica) ============== -->
  <div id="mpAI" class="mpai-root" style="display:none">
     <!-- Header -->
    <div class="mpai-header">
      <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>
 
      <!-- Chat centrale -->
      <main class="mpai-col mpai-center">
        <!-- Allegati (solo elenco locale per ora) -->
        <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>
 
        <!-- Messaggi -->
        <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>
 
        <!-- Prompt -->
        <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>
   </div>


Riga 160: Riga 64:
   <!-- ============ JS (tutto qui) ============ -->
   <!-- ============ JS (tutto qui) ============ -->
   <script>
   <script>
     // ---- Funzioni base / legacy ----
     // Utilità base
     function toggleDashboardBox(id){
     function toggleDashboardBox(id){
       var el = document.getElementById(id);
       var el = document.getElementById(id);
Riga 174: Riga 78:
       }catch(e){ alert('Errore rete: '+e.message); }
       }catch(e){ alert('Errore rete: '+e.message); }
     }
     }
    function uploadToOpenAI(){ alert('Prossimo step: upload verso OpenAI.'); }
     function showMpAI(){
     function showMpAI(){
       ['api-settings','project-status','chatgpt-plus','test-tools','activity-log']
       ['api-settings','project-status','chatgpt-plus','test-tools','activity-log']
         .forEach(id => { var el = document.getElementById(id); if (el) el.style.display='none'; });
         .forEach(id => { var el=document.getElementById(id); if (el) el.style.display='none'; });
       var ai = document.getElementById('mpAI');
       var ai = document.getElementById('mpAI');
       if (ai) ai.style.display = 'block';
       if (ai) ai.style.display='block';
     }
     }


     /* ========= MPAI APP ========= */
     // ========= MPAI APP =========
     (function(){
     document.addEventListener('DOMContentLoaded', function(){
 
       // Stato
       // Stato
       let currentProject = null;    // progetto “reale” (server) o null
       let currentProject = null;    // progetto server o null
       let sessionId = null;          // id bozza locale se non c'è progetto
       let sessionId = null;          // id bozza locale
       let sessionMeta = null;        // {title, updated}
       let sessionMeta = null;        // {title, updated}
       let history = [];
       let history = [];
Riga 200: Riga 104:
         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'));
         div.innerHTML = '<div class="role">'+(role==='user'?'Tu':role==='assistant'?'GPT':'Errore')+'</div>'
         div.innerHTML = '<div class="role">'
                      + '<div class="content" style="white-space:pre-wrap">'+content+'</div>';
          + (role==='user'?'Tu':role==='assistant'?'GPT':'Errore')
          + '</div><div class="content" style="white-space:pre-wrap">'
          + (content||'') + '</div>';
         chatEl.appendChild(div);
         chatEl.appendChild(div);
         chatEl.scrollTop = chatEl.scrollHeight;
         chatEl.scrollTop = chatEl.scrollHeight;
Riga 230: Riga 136:
       }
       }


       // ======== Sessioni locali (bozze senza progetto) ========
       // Sessioni locali (bozze)
       function ensureSession(){
       function ensureSession(){
         if (!sessionId){
         if (!sessionId){
Riga 257: Riga 163:
         } else {
         } else {
           history.forEach(m => appendMsg(m.role, m.content));
           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) ========
       // Progetti
       async function loadProjects(){
       async function loadProjects(){
         const box = $('#mpai-project-list');
         const box = $('#mpai-project-list');
        if (!box) return;
         box.innerHTML = '<em class="muted">Carico progetti…</em>';
         box.innerHTML = '<em class="muted">Carico progetti…</em>';
         try{
         try{
Riga 290: Riga 182:
             row.className = 'row';
             row.className = 'row';
             row.innerHTML = '<span class="title">'+name+'</span>'
             row.innerHTML = '<span class="title">'+name+'</span>'
                          + '<span><button class="mpai-btn" data-open="'+name+'">Apri</button></span>';
              + '<span><button class="mpai-btn" data-open="'+name+'">Apri</button></span>';
             row.querySelector('[data-open]').onclick = ()=>{ currentProject=name; loadLocal(name); };
             row.querySelector('[data-open]').onclick = ()=>{ currentProject=name; loadLocal(name); };
             box.appendChild(row);
             box.appendChild(row);
Riga 309: Riga 201:
         try{
         try{
           const r = await fetch('/dashboard/api/project_create.php', {
           const r = await fetch('/dashboard/api/project_create.php', {
             method:'POST',
             method:'POST', headers:{'Content-Type':'application/json'}, credentials:'include',
            headers:{'Content-Type':'application/json'},
            credentials:'include',
             body: JSON.stringify({name})
             body: JSON.stringify({name})
           });
           });
           const txt = await r.text(); let j; try{ j=JSON.parse(txt) }catch{ j={ok:false,raw:txt}; }
           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=''; }
           if (j.ok!==false){
           else { alert('Errore creazione: '+(j.error||'')); }
            await loadProjects(); currentProject=name; history=[]; loadLocal(name); inp.value='';
           } else { alert('Errore creazione: '+(j.error||'')); }
         }catch(e){ alert('Errore rete: '+e.message); }
         }catch(e){ alert('Errore rete: '+e.message); }
       }
       }


       // ======== Upload locale (solo elenco) ========
       // Upload locale (solo elenco)
       const dz = $('#mpai-dropzone');
       const dz = $('#mpai-dropzone');
       const fi = $('#mpai-file-input');
       const fi = $('#mpai-file-input');
       dz.addEventListener('dragover', e => { e.preventDefault(); dz.style.opacity = .85; });
       if (dz){
      dz.addEventListener('dragleave', () => { dz.style.opacity = 1; });
        dz.addEventListener('dragover', e => { e.preventDefault(); dz.style.opacity = .85; });
      dz.addEventListener('drop', e => {
        dz.addEventListener('dragleave', () => { dz.style.opacity = 1; });
        e.preventDefault(); dz.style.opacity = 1;
        dz.addEventListener('drop', e => {
        Array.from(e.dataTransfer.files||[]).forEach(f=> attachments.push({file:f,name:f.name}));
          e.preventDefault(); dz.style.opacity = 1;
        renderFiles();
          Array.from(e.dataTransfer.files||[]).forEach(f=> attachments.push({file:f,name:f.name}));
      });
          renderFiles();
       fi.addEventListener('change', () => {
        });
        Array.from(fi.files||[]).forEach(f=> attachments.push({file:f,name:f.name}));
       }
        fi.value=''; renderFiles();
      if (fi){
       });
        fi.addEventListener('change', () => {
          Array.from(fi.files||[]).forEach(f=> attachments.push({file:f,name:f.name}));
          fi.value=''; renderFiles();
        });
       }
 
      // === sendPrompt GLOBALE ===
      window.sendPrompt = async function(){
        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);


      // ======== Invio prompt via proxy sicuro ========
        const content = (ta.value || '').trim();
      <script>
        if (!content){ ta.focus(); return; }
// --- PATCH: sendPrompt con errori visibili ---
<script>
// --- PATCH: sendPrompt con errori visibili ---
async function sendPrompt(){
  const ta = document.querySelector('#mpai-input');
  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 (!currentProject && !sessionId){ ensureSession(); loadSession(sessionId); }
  if (!content){ ta.focus(); return; }


  // Se non c'è un progetto, usa la bozza locale
        history.push({role:'user', content});
  if (!window.currentProject && !window.sessionId){
        appendMsg('user', content);
    ensureSession();
        ta.value = '';
    loadSession(sessionId);
  }


  // Messaggio utente
        const contextName = currentProject
  history.push({role:'user', content});
          ? ('Progetto: ' + currentProject)
  appendMsg('user', content);
          : ('Sessione: ' + (sessionMeta?.title || 'Nuova conversazione'));
  ta.value = '';


  // Contesto
        const fileNames = attachments.map(a => a.name).join(', ');
  const contextName = (window.currentProject)
    ? `Progetto: ${window.currentProject}`
    : `Sessione: ${window.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 374: Riga 260:
${content}`;
${content}`;


  // Placeholder
        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.scrollTop = chatEl.scrollHeight;
  document.querySelector('#mpai-chat').appendChild(pending);
 
        try{
          const r = await fetch('/dashboard/api/openai_project_gpt.php', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            credentials: 'include',
            body: JSON.stringify({ model, prompt: preface })
          });
 
          const txt = await r.text();
          let j; try{ j = JSON.parse(txt); } catch { j = { status:'error', raw: txt }; }
 
          pending.remove();
 
          if (j.status === 'ok' && j.result){
            if (!currentProject && sessionMeta && sessionMeta.title === 'Nuova conversazione'){
              sessionMeta.title = (content.slice(0, 48) || 'Nuova conversazione');
              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();
        }
      };
 
      // Bind
      document.addEventListener('click', (e)=>{
        if (e.target && e.target.id==='mpai-send') window.sendPrompt();
        if (e.target && e.target.id==='mpai-project-create') createProject();
        if (e.target && e.target.id==='mpai-new-chat'){
          if (!currentProject && !sessionId){ ensureSession(); loadSession(sessionId); }
          history=[]; attachments=[]; renderFiles(); chatEl.innerHTML='';
          appendMsg('assistant', currentProject ? ('Nuova chat per "'+currentProject+'".') : 'Nuova conversazione.');
          if (currentProject) saveLocal(); else saveSession();
        }
        if (e.target && e.target.id==='mpai-clear'){
          if (!currentProject && !sessionId){ ensureSession(); loadSession(sessionId); }
          if (!confirm('Svuotare la conversazione locale?')) return;
          history=[]; attachments=[]; renderFiles(); chatEl.innerHTML='';
          appendMsg('assistant','Conversazione pulita.');
          if (currentProject) saveLocal(); else saveSession();
        }
      });
      document.addEventListener('keydown', (e)=>{
        if ((e.metaKey||e.ctrlKey) && e.key==='Enter'){
          const ta=$('#mpai-input');
          if (ta && ta===document.activeElement){ e.preventDefault(); window.sendPrompt(); }
        }
      });


  try{
       // Boot
    const r = await fetch('/dashboard/api/openai_project_gpt.php', {
       loadProjects();
      method: 'POST',
       if (!currentProject){ ensureSession(); loadSession(sessionId); }
      headers: { 'Content-Type': 'application/json' },
       credentials: 'include',      // manda i cookie/credenziali per Basic Auth
       mode: 'same-origin',
       cache: 'no-store',
      body: JSON.stringify({ model, prompt: preface })
     });
     });
  </script>


    const raw = await r.text();
  <!-- ============== MASTICATIONPEDIA AI (schermata unica) ============== -->
  <div id="mpAI" class="mpai-root" style="display:none">
    <div class="mpai-header">
      <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" disabled>💾 Salva come progetto</button>
      </div>
    </div>


     // se non è 2xx, mostra status + body grezzo
     <div class="mpai-grid">
    if (!r.ok){
      <!-- Sinistra -->
      pending.remove();
      <aside class="mpai-col mpai-left">
      console.error('GPT proxy error', r.status, r.statusText, raw);
        <h4>📂 Progetti</h4>
      appendMsg('error', `HTTP ${r.status} ${r.statusText}\n\n${raw || '(nessun body)'}`);
        <div id="mpai-project-list" class="mpai-projects"><em class="muted">Carico progetti…</em></div>
       return;
        <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>


    // prova a parse-are il JSON
      <!-- Centro -->
    let j;
      <main class="mpai-col mpai-center">
    try { j = JSON.parse(raw); }
        <div id="mpai-uploads" class="mpai-uploads" data-state="idle">
    catch(parseErr){
          <div class="mpai-dropzone" id="mpai-dropzone">
      pending.remove();
            <div>
      console.error('JSON parse error', parseErr, raw);
              <div class="muted" style="margin-bottom:6px;">Trascina qui file <b>sanificati</b> oppure</div>
      appendMsg('error', `Risposta non in JSON:\n\n${raw.slice(0,1500)}`);
              <label class="mpai-file-label">
      return;
                <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>


    pending.remove();
        <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>


    if (j.status === 'ok' && j.result){
        <div class="mpai-composer">
      // aggiorna titolo bozza alla prima risposta
          <textarea id="mpai-input" rows="3" placeholder="Scrivi qui la tua domanda..."></textarea>
      if (!window.currentProject && window.sessionMeta && window.sessionMeta.title === 'Nuova conversazione'){
          <button id="mpai-send" class="mpai-btn primary">Invia</button>
        window.sessionMeta.title = (content.slice(0,48) || 'Nuova conversazione');
        </div>
        saveSession && saveSession();
      </main>
      }
      history.push({role:'assistant', content: j.result});
      appendMsg('assistant', j.result);
      saveLocal && saveLocal();
    } else {
      console.error('API said error', j);
      const err = j.error || 'Errore sconosciuto';
      appendMsg('error', `API error: ${err}\n\nRAW:\n${(j.raw||'').slice(0,1500)}`);
    }
  } catch(e){
    pending.remove();
    console.error('Network/JS error', e);
    appendMsg('error', `Errore di rete/JS: ${e.message}`);
  }
}
</script>


      <!-- Destra -->
      <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) ========== -->
   <!-- ========== 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;">
   <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>
     <strong>Connessione API (protetta dal server)</strong><br><br>
Riga 451: Riga 417:
   </div>
   </div>


  <!-- 📊 Stato Progetti (demo) -->
   <div id="project-status" style="display:none; padding:1rem; border:1px solid #ccc; border-radius:8px; background:#f9f9f9;">
   <div id="project-status" style="display:none; padding:1rem; border:1px solid #ccc; border-radius:8px; background:#f9f9f9;">
     <strong>📊 Stato Progetti</strong><br><br>
     <strong>📊 Stato Progetti</strong><br><br>
     <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 style="margin-top: 1rem;">
      <em style="opacity:.7">Qui apparirà la risposta generata da GPT sull’analisi del progetto…</em>
      <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);">
        <em style="opacity:.7">Qui apparirà la risposta generata da GPT sull’analisi del progetto…</em>
      </div>
     </div>
     </div>
   </div>
   </div>


  <!-- 📘 Registro Attività -->
   <div id="activity-log" style="display:none; padding:1rem; border:1px solid #ccc; border-radius:8px; background:#fdfdfd;">
   <div id="activity-log" style="display:none; padding:1rem; border:1px solid #ccc; border-radius:8px; background:#fdfdfd;">
     <strong>📘 Registro attività:</strong>
     <strong>📘 Registro attività:</strong>

Versione delle 06:59, 28 set 2025

🔧 Dashboard Operativa – Masticationpedia

Centro di comando per progetti, API, file e backup

🧾 Apri Log Dashboard