|
|
| Riga 5: |
Riga 5: |
| <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 di navigazione --> | | <!-- 🔘 Pulsanti di navigazione --> |
| Riga 18: |
Riga 16: |
| </div> | | </div> |
|
| |
|
| <!-- 📦 Connessione API (via server, nessuna key lato client) --> | | <!-- 📦 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 50: |
Riga 48: |
| </div> | | </div> |
|
| |
|
| <!-- Parametri per dare contesto a GPT --> | | <!-- 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 71: |
Riga 69: |
| <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;">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> |
| </div>
| |
|
| |
|
| |
| <!-- 🤖 ChatGPT plus (unico contenitore) -->
| |
| <div id="chatgpt-plus" style="display:none; padding:1rem; border:1px solid #ccc; border-radius:8px; background:#fcfcfc;">
| |
| <strong>ChatGPT plus – Generazione capitoli</strong><br><br>
| |
|
| |
| <!-- Barra superiore -->
| |
| <div style="display:flex; gap:12px; flex-wrap:wrap; margin-bottom:10px;">
| |
| <label>📁 Progetto:
| |
| <select id="mpChatProject">
| |
| <option value="Generazione_capitoli" selected>Generazione capitoli</option>
| |
| </select>
| |
| </label>
| |
| <label>⚙️ Modalità:
| |
| <select id="mpChatMode">
| |
| <option value="analysis">Analisi</option>
| |
| <option value="rewrite">Riscrittura</option>
| |
| <option value="generate">Genera capitolo</option>
| |
| <option value="biblio">Bibliografia</option>
| |
| </select>
| |
| </label>
| |
| </div>
| |
|
| |
| <!-- Allegati -->
| |
| <div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:8px;">
| |
| <input id="mpChatFile" type="file" multiple />
| |
| <span id="mpChatDrop" style="border:1px dashed #bbb; padding:6px 10px; border-radius:8px; cursor:pointer; user-select:none;">
| |
| Trascina qui file (solo elenco, non inviati)
| |
| </span>
| |
| </div>
| |
| <div id="mpChatFiles" style="display:flex; gap:8px; flex-wrap:wrap; margin-bottom:8px;"></div>
| |
|
| |
| <!-- Input domanda -->
| |
| <label><b>Domanda</b> (breve, come in ChatGPT):</label>
| |
| <div style="display:flex; gap:8px; align-items:flex-start; margin-bottom:8px;">
| |
| <textarea id="mpChatPrompt" rows="3" style="flex:1 1 auto; width:100%;"></textarea>
| |
| <button id="mpChatSend">Invia</button>
| |
| </div>
| |
|
| |
| <!-- Output risposta -->
| |
| <label><b>Risposta</b> (box grande):</label>
| |
| <div id="mpChatAnswer" style="min-height:220px; border:1px solid #ddd; background:#fff; border-radius:8px; padding:10px; white-space:pre-wrap;"></div>
| |
|
| |
| <!-- Explorer semplice dei file salvati -->
| |
| <div style="margin-top:10px; border-top:1px dashed #ddd; padding-top:10px;">
| |
| <div style="display:flex; align-items:center; gap:8px; margin-bottom:6px;">
| |
| <strong>🗂️ File salvati (output/)</strong>
| |
| <button id="mpChatRefresh"
| |
| title="Aggiorna elenco"
| |
| onclick="window.__forceRefreshList && window.__forceRefreshList()">Aggiorna</button>
| |
| </div>
| |
| <div id="mpSavedList" style="max-height:180px; overflow:auto; border:1px solid #eee; background:#fff; border-radius:8px; padding:6px; font-family:monospace; font-size:12px;">
| |
| <em style="color:#777;">Nessun file elencato. Clicca “Aggiorna”.</em>
| |
| </div>
| |
| </div>
| |
|
| |
| <!-- Azioni -->
| |
| <div style="margin-top:8px; display:flex; gap:8px; justify-content:flex-end;">
| |
| <button id="mpChatClear">Pulisci</button>
| |
| <button id="mpChatCopy">Copia risposta</button>
| |
| <button id="mpChatSave">📥 Salva risposta</button>
| |
| </div>
| |
| </div>
| |
|
| |
| <!-- 📁 Importa file dal server (semplificata) -->
| |
| <div id="serverFileSimpleImportContainer">
| |
| <div class="dashboard-box" style="margin-top:1rem;">
| |
| <h4>📁 Importa file dal server (semplificata)</h4>
| |
| <label for="sourcePathInput"><b>📂 Percorso Assoluto del file sorgente:</b></label><br>
| |
| <input type="text" id="sourcePathInput" placeholder="/percorso/assoluto/file.php" style="width:100%; margin-bottom:0.5rem;"><br>
| |
| <label for="projectNameInput"><b>📁 Cartella di destinazione nel progetto:</b></label><br>
| |
| <input type="text" id="projectNameInput" placeholder="nome_progetto/">
| |
| </div>
| |
| </div>
| |
|
| |
| <!-- 📁 Caricamento File GPT -->
| |
| <div style="margin-top:2rem;">
| |
| <h3>📁 Carica File GPT</h3>
| |
| <label>📁 Project: <input type="text" id="gpt-load-project" value="SSO_LinkedIn" /></label>
| |
| <label>📂 Subfolder: <input type="text" id="gpt-load-subfolder" value="php" /></label>
| |
| <label>📄 Filename: <input type="text" id="gpt-load-filename" placeholder="es: testGPT.php" /></label>
| |
| <button onclick="caricaFileGPT()">📁 Carica file</button>
| |
| </div>
| |
|
| |
| <!-- 🧪 Console JS -->
| |
| <div id="test-tools" style="display:none; padding:1rem; border:1px solid #ccc; border-radius:8px; background:#f9f9f9;">
| |
| <strong>Console JS:</strong><br>
| |
| <textarea id="codeArea" placeholder="Scrivi codice JS da testare..." style="width:100%; height:100px;"></textarea><br>
| |
| <button onclick="checkCodeAI()">🔍 Controlla con AI</button>
| |
| <pre id="consoleOutput" style="background:#000; color:#0f0; padding:1rem; border-radius:8px; font-family:monospace;"></pre>
| |
| </div> | | </div> |
|
| |
|
| Riga 176: |
Riga 82: |
| </div> | | </div> |
|
| |
|
| <!-- ✅ Carica JS esterno (se lo usi) --> | | <!-- ✅ JS --> |
| <script src="/dashboard/api/dashboard.js?v=20250907d"></script>
| |
| | |
| | |
| <!-- ✅ Mini-script che POPOLA l’elenco e gestisce “Apri” -->
| |
| <script>
| |
| document.addEventListener('DOMContentLoaded', () => {
| |
| const refreshBtn = document.getElementById('mpChatRefresh');
| |
| const listBox = document.getElementById('mpSavedList');
| |
| const answerBox = document.getElementById('mpChatAnswer');
| |
| const projectSel = document.getElementById('mpChatProject');
| |
| | |
| function currentProject(){
| |
| return (projectSel?.value || 'Generazione_capitoli').trim();
| |
| }
| |
| | |
| async function refreshList(){
| |
| if (!listBox) return;
| |
| listBox.innerHTML = '<em style="color:#777;">Aggiorno…</em>';
| |
| const prj = currentProject();
| |
| try {
| |
| const r = await fetch('/dashboard/api/list_saved_output.php?project=' + encodeURIComponent(prj) + '&sub=output', { cache:'no-store' });
| |
| const txt = await r.text();
| |
| let j;
| |
| try { j = JSON.parse(txt); }
| |
| catch(e){
| |
| listBox.innerHTML = '<span style="color:#c00;">Risposta non-JSON</span><pre style="white-space:pre-wrap">'+txt.substring(0,400)+'</pre>';
| |
| console.error('list_saved_output NON JSON:', txt);
| |
| return;
| |
| }
| |
| | |
| if (!j.ok) { listBox.textContent = 'Errore: ' + (j.error || ''); return; }
| |
| if (!j.files || !j.files.length) { listBox.innerHTML = '<em style="color:#777;">Nessun file salvato.</em>'; return; }
| |
| | |
| listBox.innerHTML = j.files.map(f => `
| |
| <div style="display:flex;justify-content:space-between;gap:8px;border-bottom:1px dashed #eee;padding:4px 0;">
| |
| <span>${f.name} <span style="color:#888;">(${f.size} B)</span></span>
| |
| <button class="mpOpen" data-name="${f.name}">Apri</button>
| |
| </div>
| |
| `).join('');
| |
| | |
| listBox.querySelectorAll('.mpOpen').forEach(btn => {
| |
| btn.addEventListener('click', async () => {
| |
| const name = btn.getAttribute('data-name');
| |
| try {
| |
| const rr = await fetch('/dashboard/api/read_saved_output.php', {
| |
| method:'POST',
| |
| headers:{'Content-Type':'application/json'},
| |
| body: JSON.stringify({ project: prj, file: name })
| |
| });
| |
| const jj = await rr.json();
| |
| if (!jj.ok) { alert('Errore lettura: ' + (jj.error||'')); return; }
| |
| if (answerBox) answerBox.textContent = jj.content || '';
| |
| } catch (e) {
| |
| alert('Errore apertura file: ' + e.message);
| |
| }
| |
| });
| |
| });
| |
| } catch (e) {
| |
| listBox.textContent = 'Errore JS: ' + e.message;
| |
| console.error(e);
| |
| }
| |
| }
| |
| | |
| refreshBtn && refreshBtn.addEventListener('click', refreshList);
| |
| // carica subito l'elenco alla prima apertura
| |
| refreshList();
| |
| });
| |
| </script>
| |
| <script>
| |
| // ✅ Correttore AI – funzione GLOBALE chiamata dal bottone "🔍 Controlla con AI"
| |
| window.checkCodeAI = async function () {
| |
| const codeEl = document.getElementById('codeArea');
| |
| const outEl = document.getElementById('consoleOutput');
| |
| if (!codeEl || !outEl) { alert('Manca #codeArea o #consoleOutput'); return; }
| |
| | |
| const code = codeEl.value;
| |
| const language = 'php'; // 🔁 cambia a "js", "python", "html", ecc. quando serve
| |
| | |
| if (!code.trim()) { alert('Incolla del codice da analizzare.'); return; }
| |
| | |
| outEl.textContent = '⏳ Invio al correttore AI…';
| |
| | |
| try {
| |
| const r = await fetch('/dashboard/api/ai_check.php', {
| |
| method: 'POST',
| |
| headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
| |
| body: new URLSearchParams({ language, code })
| |
| });
| |
| const j = await r.json();
| |
| | |
| if (j.ok) {
| |
| let txt = '';
| |
| if (j.lint && j.lint.trim()) {
| |
| txt += '=== LINT ' + language.toUpperCase() + " ===\n" + j.lint + "\n\n";
| |
| }
| |
| txt += (j.analysis || 'Nessun output.');
| |
| outEl.textContent = txt;
| |
| } else {
| |
| outEl.textContent = '❌ Errore: ' + (j.error || 'sconosciuto');
| |
| if (j.raw) outEl.textContent += '\n\nRAW:\n' + j.raw;
| |
| }
| |
| } catch (e) {
| |
| outEl.textContent = '❌ Errore rete: ' + e.message;
| |
| }
| |
| };
| |
| </script>
| |
| | |
| <script> | | <script> |
| /* === Renderer stile ChatGPT: trasforma testo in HTML arioso === */ | | /* === Renderer stile ChatGPT === */ |
| function renderGpt(text) { | | function renderGpt(text) { |
| const box = document.getElementById('gptResponse'); | | const box = document.getElementById('gptResponse'); |
| Riga 291: |
Riga 90: |
| if (!text) { box.innerHTML = '<em style="opacity:.7">Nessuna risposta</em>'; return; } | | if (!text) { box.innerHTML = '<em style="opacity:.7">Nessuna risposta</em>'; return; } |
|
| |
|
| // headings ###, ##, #
| |
| let html = (text || '').replace(/\r\n/g, '\n') | | let html = (text || '').replace(/\r\n/g, '\n') |
| .replace(/^\s*#{3}\s?(.*)$/gm, '<h3 style="font-size:18px;margin:14px 0 6px;">$1</h3>') | | .replace(/^### (.*)$/gm, '<h3 style="margin:14px 0 6px;font-size:17px;">$1</h3>') |
| .replace(/^\s*#{2}\s?(.*)$/gm, '<h2 style="font-size:20px;margin:16px 0 8px;">$1</h2>') | | .replace(/^## (.*)$/gm, '<h2 style="margin:16px 0 8px;font-size:19px;">$1</h2>') |
| .replace(/^\s*#\s?(.*)$/gm, '<h1 style="font-size:22px;margin:18px 0 10px;">$1</h1>') | | .replace(/^# (.*)$/gm, '<h1 style="margin:18px 0 10px;font-size:21px;">$1</h1>') |
| .replace(/^\s*-\s+(.*)$/gm, '<li>$1</li>'); | | .replace(/^\- (.*)$/gm, '<li>$1</li>'); |
|
| |
|
| // racchiudi sequenze di <li> in <ul>
| | html = html.replace(/(<li>.*<\/li>\n?)+/gs, m => `<ul style="margin:8px 0 14px 22px;">${m}</ul>`); |
| html = html.replace(/(?:<li>.*<\/li>\n?)+/gs, m => `<ul style="margin:8px 0 14px 22px;">${m}</ul>`); | |
|
| |
|
| // paragrafi (evita di wrappare headings/list)
| |
| html = html.split('\n').map(line => { | | html = html.split('\n').map(line => { |
| if (/^<h\d|^<ul|^<li|^<\/ul>/.test(line)) return line; | | if (/^<h\d|^<ul|^<li|^<\/ul>/.test(line)) return line; |
| if (line.trim()==='') return ''; | | if (line.trim()==='') return ''; |
| return `<p style="margin:8px 0;">${line}</p>`; | | return `<p style="margin:6px 0;line-height:1.5">${line}</p>`; |
| }).join(''); | | }).join(''); |
|
| |
|
| Riga 311: |
Riga 107: |
| } | | } |
|
| |
|
| /* === Costruzione contesto per GPT dai campi pagina === */ | | /* === Costruzione prompt === */ |
| function buildProjectContext() { | | function buildProjectContext() { |
| const title = (document.getElementById('newProjectTitle')?.value || '').trim(); | | return { |
| const notes = (document.getElementById('newProjectNotes')?.value || '').trim();
| | title: (document.getElementById('newProjectTitle')?.value || '').trim(), |
| const goal = (document.getElementById('p_goal')?.value || '').trim();
| | notes: (document.getElementById('newProjectNotes')?.value || '').trim(), |
| const audience = (document.getElementById('p_audience')?.value || '').trim();
| | goal: (document.getElementById('p_goal')?.value || '').trim(), |
| const deliverable = (document.getElementById('p_deliverable')?.value || '').trim();
| | audience: (document.getElementById('p_audience')?.value || '').trim(), |
| const constraints = (document.getElementById('p_constraints')?.value || '').trim();
| | deliverable: (document.getElementById('p_deliverable')?.value || '').trim(), |
| return { title, notes, goal, audience, deliverable, constraints }; | | constraints: (document.getElementById('p_constraints')?.value || '').trim() |
| | }; |
| } | | } |
|
| |
|
| Riga 325: |
Riga 122: |
| const c = buildProjectContext(); | | const c = buildProjectContext(); |
| const out = []; | | const out = []; |
| out.push(`You are a senior editor and project designer. Provide a clear, actionable plan.`); | | 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# Title\n${c.title}`); | | if (c.title) out.push(`\n# Titolo\n${c.title}`); |
| if (c.notes) out.push(`\n# Notes\n${c.notes}`); | | if (c.notes) out.push(`\n# Note\n${c.notes}`); |
| if (c.goal) out.push(`\n# Goal\n${c.goal}`); | | if (c.goal) out.push(`\n# Obiettivo\n${c.goal}`); |
| if (c.audience) out.push(`\n# Audience\n${c.audience}`); | | if (c.audience) out.push(`\n# Pubblico\n${c.audience}`); |
| if (c.deliverable) out.push(`\n# Deliverables\n${c.deliverable}`); | | if (c.deliverable) out.push(`\n# Output atteso\n${c.deliverable}`); |
| if (c.constraints) out.push(`\n# Constraints\n${c.constraints}`); | | if (c.constraints) out.push(`\n# Vincoli\n${c.constraints}`); |
| out.push(`\n# Output format | | out.push(`\n# Formato output richiesto |
| - Executive Summary | | - Sommario introduttivo |
| - Strengths & Risks | | - Punti di forza e rischi |
| - Step-by-step Plan (milestones) | | - Piano step-by-step (milestones) |
| - Resources & Budget hints | | - Risorse e budget |
| - Metrics of success`); | | - Metriche di successo`); |
| return out.join('\n'); | | return out.join('\n'); |
| } | | } |
|
| |
|
| /* === Azione: Analizza con GPT (usa la tua API già protetta) === | | /* === Analisi progetto === */ |
| * Se NON hai ancora un endpoint dedicato, per test immediato simuliamo la risposta.
| |
| * Quando vorrai la chiamata reale, sostituisci la parte "FAKE RESPONSE" con una fetch al tuo endpoint server (/dashboard/api/ai_project_analyze.php).
| |
| */
| |
| async function runProjectAnalysis() { | | async function runProjectAnalysis() { |
| const btn = document.getElementById('btnAnalyze'); | | const btn = document.getElementById('btnAnalyze'); |
| Riga 353: |
Riga 147: |
| const prompt = buildPromptForGPT(); | | const prompt = buildPromptForGPT(); |
|
| |
|
| // === FAKE RESPONSE PER TEST VISIVO (subito) === | | // SIMULAZIONE (puoi sostituire con fetch reale) |
| const fake = `# Executive Summary | | const fake = `# Sommario introduttivo |
| Progetto focalizzato su ${ (document.getElementById('newProjectTitle')?.value || 'il tema indicato') }.
| | Analisi del progetto: ${ (document.getElementById('newProjectTitle')?.value || 'titolo non specificato') }. |
| Obiettivo: ${ document.getElementById('p_goal')?.value || 'non specificato' }. | | Obiettivo: ${ document.getElementById('p_goal')?.value || 'non specificato' }. |
| Pubblico: ${ document.getElementById('p_audience')?.value || 'non specificato' }. | | Pubblico: ${ document.getElementById('p_audience')?.value || 'non specificato' }. |
|
| |
|
| ## Strengths & Risks | | ## Punti di forza e rischi |
| - Strength: contenuti specialistici. | | - Punti di forza: contenuti specialistici. |
| - Risk: mancano vincoli chiari (${ document.getElementById('p_constraints')?.value || '—' }). | | - Rischi: vincoli non chiariti (${ document.getElementById('p_constraints')?.value || '—' }). |
|
| |
|
| ## Step-by-step Plan (milestones) | | ## Piano step-by-step |
| - M1: Definizione indice capitoli.
| | 1. Definizione indice capitoli |
| - M2: Sviluppo contenuti.
| | 2. Sviluppo contenuti |
| - M3: Revisione clinica e bibliografia.
| | 3. Revisione clinica e bibliografia |
| - M4: Pubblicazione.
| | 4. Pubblicazione |
|
| |
|
| ## Resources & Budget hints | | ## Risorse e budget |
| - Autori, revisori, tool AI, hosting video. | | - Autori, revisori, strumenti AI, hosting |
|
| |
|
| ## Metrics of success | | ## Metriche di successo |
| - Capitoli completati, feedback medici, engagement studenti.`; | | - Capitoli completati |
| | - Feedback medici |
| | - Engagement degli studenti`; |
|
| |
|
| renderGpt(fake); | | renderGpt(fake); |
|
| |
| // === VERSIONE REALE (quando pronta):
| |
| // const r = await fetch('/dashboard/api/ai_project_analyze.php', {
| |
| // method: 'POST',
| |
| // headers: {'Content-Type':'application/x-www-form-urlencoded'},
| |
| // body: new URLSearchParams({ prompt })
| |
| // });
| |
| // const j = await r.json();
| |
| // if (!j.ok) throw new Error(j.error || 'AI error');
| |
| // renderGpt(j.text);
| |
|
| |
|
| } catch (e) { | | } catch (e) { |
| Riga 394: |
Riga 180: |
| } | | } |
|
| |
|
| /* bind del bottone */
| |
| document.addEventListener('DOMContentLoaded', () => { | | document.addEventListener('DOMContentLoaded', () => { |
| const b = document.getElementById('btnAnalyze'); | | const b = document.getElementById('btnAnalyze'); |
| Riga 400: |
Riga 185: |
| }); | | }); |
| </script> | | </script> |
|
| |
| </html> | | </html> |