Nessun oggetto della modifica
Nessun oggetto della modifica
Riga 18: Riga 18:


</div>
</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>
    </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 -->
      <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, allega eventuali file <b>sanificati</b> e fai la tua domanda.
          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>
<!-- ============ STILI (CSS) ============ -->
<style>
  .mpai-root{--bg:#f7f8fa;--card:#fff;--muted:#6b7280;--line:#e5e7eb;--text:#111827;
    --primary:#0a7cff;--danger:#e74c3c;--accent:#eef2ff;--green:#1f9d55;
    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-title{font-size:20px;font-weight:700}
  .mpai-actions{display:flex;gap:8px}
  .mpai-btn{border:1px solid var(--line);background:var(--card);padding:.45rem .8rem;border-radius:8px;cursor:pointer}
  .mpai-btn:hover{background:#f3f4f6}
  .mpai-btn.primary{background:var(--primary);border-color:var(--primary);color:#fff}
  .mpai-btn.danger{background:var(--danger);border-color:var(--danger);color:#fff}
  .mpai-btn.ghost{background:transparent}
  .muted{color:var(--muted)}
  .mpai-grid{display:grid;grid-template-columns:260px minmax(0,1fr) 260px;gap:12px}
  .mpai-col{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:12px}
  .mpai-left,.mpai-right{max-height:75vh;overflow:auto}
  .mpai-projects{display:flex;flex-direction:column;gap:6px;margin:8px 0}
  .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}
  .mpai-projects .row .title{font-family:monospace;font-size:12px}
  .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>
<!-- ============ JS (minimo) ============ -->
<script>
/* --- mostra la schermata AI e nasconde gli altri box --- */
function showMpAI(){
  // Nascondi i vecchi box se esistono
  ['api-settings','project-status','chatgpt-plus','test-tools','activity-log']
    .forEach(id => { var el = document.getElementById(id); if (el) el.style.display='none'; });
  // Mostra la nuova UI AI
  var ai = document.getElementById('mpAI');
  if (ai) ai.style.display = 'block';
}
(function(){
  // ======== Stato =========
  let currentProject = null;
  let attachments = [];  // [{file, name}]
  let history = [];      // [{role:'user'|'assistant'|'error', content:string}]
  // ======== Util ========
  const $ = s => document.querySelector(s);
  const chatEl = $('#mpai-chat');
  const fileListEl = $('#mpai-file-list');
  function appendMsg(role, content){
    const div = document.createElement('div');
    div.className = 'mpai-msg ' + (role === 'user' ? 'user' : (role === 'assistant' ? 'assistant' : 'error'));
    div.innerHTML = `<div class="role">${role === 'user' ? 'Tu' : role === 'assistant' ? 'GPT' : 'Errore'}</div>
                    <div class="content" style="white-space:pre-wrap">${content}</div>`;
    chatEl.appendChild(div);
    chatEl.scrollTop = chatEl.scrollHeight;
  }
  function saveLocal(){
    if (!currentProject) return;
    const key = 'mpai.hist.' + currentProject;
    localStorage.setItem(key, JSON.stringify(history.slice(-200)));
  }
  function loadLocal(project){
    const key = 'mpai.hist.' + project;
    const raw = localStorage.getItem(key);
    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 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(){
    fileListEl.innerHTML = '';
    attachments.forEach((a,i)=> fileListEl.appendChild(pill(a.name, i)));
  }
  // ======== 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 && arr.length){
        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 (solo elenco visivo; back-end upload dopo) ========
  const dz = $('#mpai-dropzone');
  const fi = $('#mpai-file-input');
  dz.addEventListener('dragover', e => { e.preventDefault(); dz.style.opacity = .85; });
  dz.addEventListener('dragleave', e => { dz.style.opacity = 1; });
  dz.addEventListener('drop', e => {
    e.preventDefault(); dz.style.opacity = 1;
    const files = Array.from(e.dataTransfer.files || []);
    files.forEach(f => attachments.push({file:f, name:f.name}));
    renderFiles();
  });
  fi.addEventListener('change', e => {
    const files = Array.from(fi.files || []);
    files.forEach(f => attachments.push({file:f, name:f.name}));
    fi.value = '';
    renderFiles();
  });
  // ======== Invio prompt a GPT via proxy sicuro ========
  async function sendPrompt(){
    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();
    if (!content){ ta.focus(); return; }
    if (!currentProject){ alert('Seleziona o crea un progetto (colonna sinistra).'); return; }
    history.push({role:'user', content});
    appendMsg('user', content);
    ta.value='';
    const fileNames = attachments.map(a => a.name).join(', ');
    const preface = `Contesto progetto: ${currentProject}
File allegati${sanitizedOnly?' (sanificati)':''}: ${fileNames || 'nessuno'}
Istruzioni: rispondi in ITALIANO, struttura chiara (titoli, punti elenco), sii operativo. Temperatura: ${temp}.
---
Utente:
${content}`;
    const pending = document.createElement('div');
    pending.className = 'mpai-msg';
    pending.innerHTML = '<em class="muted">Elaboro…</em>';
    chatEl.appendChild(pending);
    chatEl.scrollTop = chatEl.scrollHeight;
    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){
        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.getElementById('mpai-send').addEventListener('click', sendPrompt);
  document.getElementById('mpai-input').addEventListener('keydown', e=>{
    if (e.key==='Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); sendPrompt(); }
  });
  document.getElementById('mpai-project-create').addEventListener('click', createProject);
  document.getElementById('mpai-new-chat').addEventListener('click', ()=>{
    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 ========
  loadProjects();
})();
</script>
<!-- ============== /MASTICATIONPEDIA AI ============== -->


<!-- 📦 Connessione API -->
<!-- 📦 Connessione API -->

Versione delle 14:14, 27 set 2025

🔧 Dashboard Operativa – Masticationpedia

Centro di comando per progetti, API, file e backup

🧾 Apri Log Dashboard