Nessun oggetto della modifica
Nessun oggetto della modifica
Riga 6: Riga 6:
<button onclick="clearServerLog()">🧹 Svuota Log</button>
<button onclick="clearServerLog()">🧹 Svuota Log</button>


<!-- 🔘 Pulsanti di navigazione -->
<!-- 🔘 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>
Riga 14: Riga 14:
   <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="uploadToOpenAI()">📄 Carica in OpenAI</button>
   <button class="dashboard-toggle" onclick="uploadToOpenAI()">📄 Carica in OpenAI</button>
<button class="dashboard-toggle" onclick="showMpAI()">🤖 Masticationpedia AI</button>
  <button class="dashboard-toggle" onclick="showMpAI()">🤖 Masticationpedia AI</button>
 
 
</div>
</div>


Riga 48: Riga 46:
     <!-- Chat centrale -->
     <!-- Chat centrale -->
     <main class="mpai-col mpai-center">
     <main class="mpai-col mpai-center">
       <!-- Allegati -->
       <!-- Allegati (solo elenco locale; invio server lo aggiungiamo in step 2) -->
       <div id="mpai-uploads" class="mpai-uploads" data-state="idle">
       <div id="mpai-uploads" class="mpai-uploads" data-state="idle">
         <div class="mpai-dropzone" id="mpai-dropzone">
         <div class="mpai-dropzone" id="mpai-dropzone">
Riga 103: Riga 101:
<style>
<style>
   .mpai-root{--bg:#f7f8fa;--card:#fff;--muted:#6b7280;--line:#e5e7eb;--text:#111827;
   .mpai-root{--bg:#f7f8fa;--card:#fff;--muted:#6b7280;--line:#e5e7eb;--text:#111827;
     --primary:#0a7cff;--danger:#e74c3c;--accent:#eef2ff;--green:#1f9d55;
     --primary:#0a7cff;--danger:#e74c3c;--accent:#eef2ff;
     font-family: system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif; color:var(--text);}
     font-family: system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif; color:var(--text);}
   .mpai-header{display:flex;align-items:center;justify-content:space-between;margin:8px 0 12px}
   .mpai-header{display:flex;align-items:center;justify-content:space-between;margin:8px 0 12px}
Riga 146: Riga 144:
</style>
</style>


<!-- ============ JS (minimo) ============ -->
<!-- ============ JS (tutto qui) ============ -->
<script>
<script>
/* --- mostra la schermata AI e nasconde gli altri box --- */
/* -------- Fallback utili (non rompersi se mancano) -------- */
function toggleDashboardBox(id){
  var el = document.getElementById(id);
  if (!el) return;
  // nascondo AI se apro altro box
  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(){
  try{
    const r = await fetch('/dashboard/api/log_clear.php',{method:'POST',credentials:'include'});
    const j = await r.json().catch(()=>({ok:false}));
    alert(j && j.ok ? 'Log svuotato.' : 'Impossibile svuotare il log.');
  }catch(e){ alert('Errore rete: '+e.message); }
}
function uploadToOpenAI(){ alert('Prossimo step: upload verso OpenAI.'); }
 
/* --- mostra la nuova UI AI e nasconde gli altri box --- */
function showMpAI(){
function showMpAI(){
  // Nascondi i vecchi box se esistono
   ['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'; });
  // Mostra la nuova UI AI
   var ai = document.getElementById('mpAI');
   var ai = document.getElementById('mpAI');
   if (ai) ai.style.display = 'block';
   if (ai) ai.style.display = 'block';
}
}


/* ========= MPAI APP ========= */
(function(){
(function(){
   // ======== Stato =========
   // Stato
   let currentProject = null;
   let currentProject = null;
   let attachments = [];  // [{file, name}]
   let attachments = [];  // [{file, name}]
   let history = [];      // [{role:'user'|'assistant'|'error', content:string}]
   let history = [];      // [{role:'user'|'assistant'|'error', content:string}]


   // ======== Util ========
   // Shortcuts
   const $ = s => document.querySelector(s);
   const $ = s => document.querySelector(s);
   const chatEl = $('#mpai-chat');
   const chatEl = $('#mpai-chat');
   const fileListEl = $('#mpai-file-list');
   const fileListEl = $('#mpai-file-list');


  // UI helpers
   function appendMsg(role, content){
   function appendMsg(role, content){
     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">${role==='user'?'Tu':role==='assistant'?'GPT':'Errore'}</div>
                     <div class="content" style="white-space:pre-wrap">${content}</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;
   }
   }
   function saveLocal(){
   function saveLocal(){
     if (!currentProject) return;
     if (!currentProject) return;
     const key = 'mpai.hist.' + currentProject;
     localStorage.setItem('mpai.hist.'+currentProject, JSON.stringify(history.slice(-200)));
    localStorage.setItem(key, JSON.stringify(history.slice(-200)));
   }
   }
   function loadLocal(project){
   function loadLocal(project){
     const key = 'mpai.hist.' + project;
     const raw = localStorage.getItem('mpai.hist.'+project);
    const raw = localStorage.getItem(key);
     history = raw ? JSON.parse(raw) : [];
     history = raw ? JSON.parse(raw) : [];
     chatEl.innerHTML = '';
     chatEl.innerHTML = '';
     if (!history.length){
     if (!history.length){
       appendMsg('assistant', 'Pronto! Dimmi cosa vuoi fare su "' + project + '". Allegami anche file sanificati se servono.');
       appendMsg('assistant', 'Pronto! Dimmi cosa vuoi fare su "'+project+'". Allegami anche file sanificati se servono.');
     } else {
     } else {
       history.forEach(m => appendMsg(m.role, m.content));
       history.forEach(m => appendMsg(m.role, m.content));
     }
     }
  }
  function pill(name, idx){
    const wrap = document.createElement('span');
    wrap.className = 'mpai-filepill';
    wrap.innerHTML = `<span title="Allegato">${name}</span><button title="Rimuovi">✕</button>`;
    wrap.querySelector('button').onclick = () => { attachments.splice(idx,1); renderFiles(); };
    return wrap;
   }
   }
   function renderFiles(){
   function renderFiles(){
     fileListEl.innerHTML = '';
     fileListEl.innerHTML = '';
     attachments.forEach((a,i)=> fileListEl.appendChild(pill(a.name, i)));
     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);
    });
   }
   }


   // ======== Progetti (sinistra) ========
   // Progetti
   async function loadProjects(){
   async function loadProjects(){
     const box = $('#mpai-project-list');
     const box = $('#mpai-project-list');
     box.innerHTML = '<em class="muted">Carico progetti…</em>';
     box.innerHTML = '<em class="muted">Carico progetti…</em>';
     try {
     try{
       const r = await fetch('/dashboard/api/project_list.php', {cache:'no-store', credentials:'include'});
       const r = await fetch('/dashboard/api/project_list.php', {cache:'no-store', credentials:'include'});
       const j = await r.json();
       const j = await r.json();
       const arr = Array.isArray(j) ? j : (Array.isArray(j.projects) ? j.projects : []);
       const arr = Array.isArray(j) ? j : (Array.isArray(j.projects) ? j.projects : []);
       if (!arr.length){ box.innerHTML = '<em class="muted">Nessun progetto</em>'; return; }
       if (!arr.length){ box.innerHTML='<em class="muted">Nessun progetto</em>'; return; }
       box.innerHTML = '';
       box.innerHTML='';
       arr.forEach(item=>{
       arr.forEach(item=>{
         const name = (typeof item === 'string') ? item : (item.name || item);
         const name = (typeof item === 'string') ? item : (item.name || item);
Riga 223: Riga 233:
         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 = ()=> {
         row.querySelector('[data-open]').onclick = ()=>{ currentProject=name; loadLocal(name); };
          currentProject = name;
          loadLocal(name);
        };
         box.appendChild(row);
         box.appendChild(row);
       });
       });
       if (!currentProject && arr.length){
       if (!currentProject){ currentProject = (typeof arr[0]==='string')?arr[0]:(arr[0].name||arr[0]); loadLocal(currentProject); }
        const first = (typeof arr[0]==='string')?arr[0]:(arr[0].name||arr[0]);
     }catch(e){
        currentProject = first;
        loadLocal(first);
      }
     } catch(e){
       box.innerHTML = '<span class="muted">Errore caricamento: '+e.message+'</span>';
       box.innerHTML = '<span class="muted">Errore caricamento: '+e.message+'</span>';
     }
     }
Riga 244: Riga 247:
     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' },
         headers:{'Content-Type':'application/json'},
         credentials: 'include',
         credentials:'include',
         body: JSON.stringify({name})
         body: JSON.stringify({name})
       });
       });
       const txt = await r.text();
       const txt = await r.text(); let j; try{ j=JSON.parse(txt) }catch{ j={ok:false,raw:txt}; }
      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();
     }catch(e){ alert('Errore rete: '+e.message); }
        currentProject = name;
        history = [];
        loadLocal(name);
        inp.value = '';
       } else {
        alert('Errore creazione: ' + (j.error||''));
      }
     } catch(e){
      alert('Errore rete: '+e.message);
    }
   }
   }


   // ======== Upload (solo elenco visivo; back-end upload dopo) ========
   // Upload locale
   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; });
   dz.addEventListener('dragover', e => { e.preventDefault(); dz.style.opacity = .85; });
   dz.addEventListener('dragleave', e => { dz.style.opacity = 1; });
   dz.addEventListener('dragleave', () => { dz.style.opacity = 1; });
   dz.addEventListener('drop', e => {
   dz.addEventListener('drop', e => {
     e.preventDefault(); dz.style.opacity = 1;
     e.preventDefault(); dz.style.opacity = 1;
     const files = Array.from(e.dataTransfer.files || []);
     Array.from(e.dataTransfer.files||[]).forEach(f=> attachments.push({file:f,name:f.name}));
    files.forEach(f => attachments.push({file:f, name:f.name}));
     renderFiles();
     renderFiles();
   });
   });
   fi.addEventListener('change', e => {
   fi.addEventListener('change', () => {
     const files = Array.from(fi.files || []);
     Array.from(fi.files||[]).forEach(f=> attachments.push({file:f,name:f.name}));
    files.forEach(f => attachments.push({file:f, name:f.name}));
     fi.value=''; renderFiles();
     fi.value = '';
    renderFiles();
   });
   });


   // ======== Invio prompt a GPT via proxy sicuro ========
   // Invio prompt
   async function sendPrompt(){
   async function sendPrompt(){
     const ta = $('#mpai-input');
     const ta = $('#mpai-input');
Riga 289: Riga 279:
     const temp  = parseFloat($('#mpai-temp')?.value || '0.7') || 0.7;
     const temp  = parseFloat($('#mpai-temp')?.value || '0.7') || 0.7;
     const sanitizedOnly = $('#mpai-sanitized-only')?.checked;
     const sanitizedOnly = $('#mpai-sanitized-only')?.checked;
     const content = (ta.value||'').trim();
     const content = (ta.value||'').trim();
     if (!content){ ta.focus(); return; }
     if (!content){ ta.focus(); return; }
Riga 298: Riga 287:
     ta.value='';
     ta.value='';


     const fileNames = attachments.map(a => a.name).join(', ');
     const fileNames = attachments.map(a=>a.name).join(', ');
     const preface = `Contesto progetto: ${currentProject}
     const preface = `Contesto progetto: ${currentProject}
File allegati${sanitizedOnly?' (sanificati)':''}: ${fileNames || 'nessuno'}
File allegati${sanitizedOnly?' (sanificati)':''}: ${fileNames || 'nessuno'}
Riga 308: Riga 297:


     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;
     chatEl.appendChild(pending);
    chatEl.scrollTop = chatEl.scrollHeight;


     try{
     try{
Riga 320: Riga 307:
         body: JSON.stringify({ model, prompt: preface })
         body: JSON.stringify({ model, prompt: preface })
       });
       });
       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){
         history.push({role:'assistant', content:j.result});
         history.push({role:'assistant', content:j.result});
         appendMsg('assistant', j.result);
         appendMsg('assistant', j.result); saveLocal();
        saveLocal();
       }else{
       } else {
         const err = j.error || 'Errore sconosciuto';
         const err = j.error || 'Errore sconosciuto';
         history.push({role:'error', content:err});
         history.push({role:'error', content:err});
         appendMsg('error', err + (j.raw ? '\n\nRAW:\n'+j.raw : ''));
         appendMsg('error', err + (j.raw?'\n\nRAW:\n'+j.raw:''));
         saveLocal();
         saveLocal();
       }
       }
     } catch(e){
     }catch(e){
       pending.remove();
       pending.remove(); history.push({role:'error', content:e.message}); appendMsg('error', e.message); saveLocal();
      history.push({role:'error', content:e.message});
      appendMsg('error', e.message);
      saveLocal();
     }
     }
   }
   }


   // ======== Bind ========
   // Bind
   document.getElementById('mpai-send').addEventListener('click', sendPrompt);
   document.addEventListener('click', (e)=>{
  document.getElementById('mpai-input').addEventListener('keydown', e=>{
    if (e.target && e.target.id==='mpai-send') sendPrompt();
     if (e.key==='Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); sendPrompt(); }
    if (e.target && e.target.id==='mpai-project-create') createProject();
    if (e.target && e.target.id==='mpai-new-chat'){
      if (!currentProject){ alert('Crea o seleziona un progetto'); return; }
      history=[]; attachments=[]; renderFiles(); chatEl.innerHTML=''; appendMsg('assistant','Nuova chat per "'+currentProject+'".'); saveLocal();
    }
     if (e.target && e.target.id==='mpai-clear'){
      if (!currentProject){ alert('Crea o seleziona un progetto'); return; }
      if (!confirm('Svuotare la conversazione locale?')) return;
      history=[]; attachments=[]; renderFiles(); chatEl.innerHTML=''; appendMsg('assistant','Conversazione pulita.'); saveLocal();
    }
   });
   });
   document.getElementById('mpai-project-create').addEventListener('click', createProject);
   document.addEventListener('keydown', (e)=>{
  document.getElementById('mpai-new-chat').addEventListener('click', ()=>{
     if ((e.metaKey||e.ctrlKey) && e.key==='Enter'){ const ta=$('#mpai-input'); if (ta && ta===document.activeElement){ e.preventDefault(); sendPrompt(); } }
     if (!currentProject){ alert('Crea o seleziona un progetto'); return; }
    history = [];
    attachments = [];
    renderFiles();
    chatEl.innerHTML = '';
    appendMsg('assistant','Nuova chat per "'+currentProject+'".');
    saveLocal();
  });
  document.getElementById('mpai-clear').addEventListener('click', ()=>{
    if (!currentProject){ alert('Crea o seleziona un progetto'); return; }
    if (!confirm('Svuotare la conversazione locale?')) return;
    history = [];
    attachments = [];
    renderFiles();
    chatEl.innerHTML = '';
    appendMsg('assistant','Conversazione pulita.');
    saveLocal();
   });
   });


   // ======== Boot ========
   // Boot
   loadProjects();
   loadProjects();
})();
})();
Riga 376: Riga 348:




 
<!-- ========== SEZIONI LEGACY (restano, ma sono nascoste di default) ========== -->


<!-- 📦 Connessione API -->
<!-- 📦 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>
   <label>Modello</label>
   <label>Modello</label>
   <select id="model-select" style="width:100%; margin-bottom:0.5rem;">
   <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-4o-2024-05-13" selected>gpt-4o-2024-05-13</option>
    <option value="gpt-4o-mini">gpt-4o-mini</option>
     <option value="gpt-4-turbo-2024-04-09">gpt-4-turbo-2024-04-09</option>
     <option value="gpt-4-turbo-2024-04-09">gpt-4-turbo-2024-04-09</option>
   </select>
   </select>
   <label>Prompt di test</label>
   <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>
   <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>
   <button onclick="testAPIConnection()">▶️ Esegui</button>
   <pre id="api-result" style="background:#f0f0f0; padding:1rem; border:1px solid #ccc; margin-top:1rem; white-space:pre-wrap;"></pre>
   <pre id="api-result" style="background:#f0f0f0; padding:1rem; border:1px solid #ccc; margin-top:1rem; white-space:pre-wrap;"></pre>
</div>
</div>
Riga 399: Riga 367:
<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>
  <!-- 🧠 Risposta GPT – Analisi progetto -->
   <div style="margin-top: 1rem;">
   <div style="margin-top: 1rem;">
     <label><strong>🧠 Risposta GPT – Analisi progetto:</strong></label>
     <label><strong>🧠 Risposta GPT – Analisi progetto:</strong></label>
     <div id="gptResponse" class="gpt-card" style="
     <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);">
        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>
    <!-- Parametri -->
     <div id="gptParams" style="margin-top:14px;padding:10px;border:1px dashed #ccc;border-radius:8px;background:#fcfcfc;">
     <div id="gptParams" style="margin-top:14px;padding:10px;border:1px dashed #ccc;border-radius:8px;background:#fcfcfc;">
       <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
       <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
Riga 419: Riga 380:
       </div>
       </div>
       <div style="margin-top:10px;">
       <div style="margin-top:10px;">
         <button id="btnAnalyze" style="background:#0a7cff;color:#fff;border:none;padding:.5rem .9rem;border-radius:8px;font-weight:600;">
         <button id="btnAnalyze" style="background:#0a7cff;color:#fff;border:none;padding:.5rem .9rem;border-radius:8px;font-weight:600;"
           Analizza con GPT
           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
        </button>
Obiettivo: ${c.goal||'—'}
Pubblico: ${c.audience||'—'}
 
## Step
- Pianificazione
- Esecuzione
- Revisione
 
## Output atteso
${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>


  <!-- 📂 Gestione Progetti -->
   <div class="projects-controls" style="margin-top:1.5rem; padding:1rem; border:1px solid #ddd; border-radius:6px; background:#fff;">
   <div class="projects-controls" style="margin-top:1.5rem; padding:1rem; border:1px solid #ddd; border-radius:6px; background:#fff;">
     <h4>📂 Gestione Progetti</h4>
     <h4>📂 Gestione Progetti</h4>
     <input id="newProjectName" type="text" placeholder="Nome progetto (A-Z 0-9 _ -)" style="margin-right:6px; padding:4px;">
     <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;">Crea progetto</button>
     <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>
     <ul id="projectsList" class="projects-list" style="margin-top:1rem; list-style:none; padding:0;"></ul>
   </div>
   </div>
Riga 438: Riga 411:
<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>
   <button onclick="clearActivityLog()" style="float:right; background:#e74c3c; color:white; border:none; padding:0.4rem 1rem; border-radius:6px; font-weight:bold;">🧹 Svuota</button>
   <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;">
   <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>
     <em><span style="color:#888;">Registro avviato...</span></em>
Riga 444: Riga 417:
</div>
</div>


<!-- ✅ JS -->
<script>
/* === Renderer stile ChatGPT === */
function renderGpt(text) {
  const box = document.getElementById('gptResponse');
  if (!box) return;
  if (!text) { box.innerHTML = '<em style="opacity:.7">Nessuna risposta</em>'; return; }
  let html = (text || '').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;
}
/* === Costruzione prompt === */
function buildProjectContext() {
  return {
    title: (document.getElementById('newProjectTitle')?.value || '').trim(),
    notes: (document.getElementById('newProjectNotes')?.value || '').trim(),
    goal: (document.getElementById('p_goal')?.value || '').trim(),
    audience: (document.getElementById('p_audience')?.value || '').trim(),
    deliverable: (document.getElementById('p_deliverable')?.value || '').trim(),
    constraints: (document.getElementById('p_constraints')?.value || '').trim()
  };
}
function buildPromptForGPT() {
  const c = buildProjectContext();
  const out = [];
  out.push(`Tu sei un project manager ed editor senior. Rispondi sempre in **italiano**. Fornisci un piano chiaro, sintetico e leggibile.`);
  if (c.title)      out.push(`\n# Titolo\n${c.title}`);
  if (c.notes)      out.push(`\n# Note\n${c.notes}`);
  if (c.goal)        out.push(`\n# Obiettivo\n${c.goal}`);
  if (c.audience)    out.push(`\n# Pubblico\n${c.audience}`);
  if (c.deliverable) out.push(`\n# Output atteso\n${c.deliverable}`);
  if (c.constraints) out.push(`\n# Vincoli\n${c.constraints}`);
  out.push(`\n# Formato output richiesto
- Sommario introduttivo
- Punti di forza e rischi
- Piano step-by-step (milestones)
- Risorse e budget
- Metriche di successo`);
  return out.join('\n');
}
/* === Analisi progetto === */
async function runProjectAnalysis() {
  const btn = document.getElementById('btnAnalyze');
  if (!btn) return;
  btn.disabled = true; btn.textContent = 'Analizzo…';
  try {
    const prompt = buildPromptForGPT();
    // SIMULAZIONE (puoi sostituire con fetch reale)
    const fake = `# Sommario introduttivo
Analisi del progetto: ${ (document.getElementById('newProjectTitle')?.value || 'titolo non specificato') }.
Obiettivo: ${ document.getElementById('p_goal')?.value || 'non specificato' }.
Pubblico: ${ document.getElementById('p_audience')?.value || 'non specificato' }.
## Punti di forza e rischi
- Punti di forza: contenuti specialistici.
- Rischi: vincoli non chiariti (${ document.getElementById('p_constraints')?.value || '—' }).
## Piano step-by-step
1. Definizione indice capitoli
2. Sviluppo contenuti
3. Revisione clinica e bibliografia
4. Pubblicazione
## Risorse e budget
- Autori, revisori, strumenti AI, hosting
## Metriche di successo
- Capitoli completati
- Feedback medici
- Engagement degli studenti`;
    renderGpt(fake);
  } catch (e) {
    renderGpt('❌ Errore: ' + e.message);
  } finally {
    btn.disabled = false; btn.textContent = 'Analizza con GPT';
  }
}
document.addEventListener('DOMContentLoaded', () => {
  const b = document.getElementById('btnAnalyze');
  if (b) b.addEventListener('click', runProjectAnalysis);
});
</script>
</html>
</html>

Versione delle 14:29, 27 set 2025

🔧 Dashboard Operativa – Masticationpedia

Centro di comando per progetti, API, file e backup

🧾 Apri Log Dashboard