JezK
Edit File: workspace.php
<?php $pageTitle = 'Research Workspace'; $extraCss = ' <link rel="stylesheet" href="css/workspace.css"> <style> .document-list-sidebar{width:220px;border-right:1px solid var(--color-border);background:#fff;display:flex;flex-direction:column;} .doc-item{padding:.75rem 1rem;font-size:.875rem;cursor:pointer;border-bottom:1px solid var(--color-border-subtle);display:flex;align-items:center;justify-content:space-between;gap:.5rem;} .doc-item:hover,.doc-item.active{background:var(--primary-50);color:var(--primary-700);font-weight:600;} </style> '; include 'includes/header.php'; ?> <!-- Main Workspace --> <div class="main-area"> <header class="topbar"> <div class="topbar-title" id="workspace-project-title">Loading Workspace...</div> <div class="topbar-actions"> <div class="badge badge-purple" style="display:inline-flex;align-items:center;gap:6px;padding:5px 12px;font-size:0.75rem;border:1px solid rgba(168,85,247,0.3);background:rgba(237,233,254,0.7);"> <span class="live-dot" style="width:7px;height:7px;border-radius:50%;background:#10B981;box-shadow:0 0 8px #10B981;"></span> <span style="font-weight:700;color:var(--purple-700);">OX Alpha (Stealth) Engine</span> </div> <select class="select" id="workspace-style-select" style="width:auto;padding:.4rem .75rem;font-size:.8125rem;"> <option>APA</option><option>MLA</option><option>Harvard</option> <option>Chicago</option><option>IEEE</option><option>Vancouver</option> </select> <button class="btn btn-secondary btn-sm" onclick="openVersionsDrawer()"><i data-lucide="history" style="width:14px;height:14px;"></i> Version History</button> <button class="btn btn-gradient btn-sm" onclick="triggerFullAnalysis()"><i data-lucide="shield-check" style="width:14px;height:14px;"></i> Run AI Integrity Check</button> </div> </header> <div class="workspace-container"> <!-- Documents Sidebar list --> <div class="document-list-sidebar"> <div style="padding:1rem;border-bottom:1px solid var(--color-border);display:flex;justify-content:space-between;align-items:center;"> <strong style="font-size:.8rem;color:var(--gray-500)">DOCUMENTS</strong> <button class="btn btn-secondary btn-icon-sm" onclick="createNewDocument()" title="New Draft">+</button> </div> <div id="document-sidebar-list" style="flex:1;overflow-y:auto;"> <!-- Loaded dynamically --> </div> </div> <!-- Editor Panel --> <div class="editor-area"> <!-- Text Styling Toolbar --> <div class="editor-toolbar" id="editor-toolbar"> <div class="toolbar-group"> <button class="toolbar-btn btn-bold" title="Bold"><b>B</b></button> <button class="toolbar-btn btn-italic" title="Italic"><i>I</i></button> <button class="toolbar-btn btn-underline" title="Underline"><u>U</u></button> </div> <div class="toolbar-group"> <select class="toolbar-select" data-cmd="formatBlock"> <option value="p">Paragraph</option> <option value="h1">Heading 1</option> <option value="h2">Heading 2</option> <option value="h3">Heading 3</option> </select> </div> <div class="toolbar-group"> <button class="toolbar-btn btn-ul" title="Unordered List">•=</button> <button class="toolbar-btn btn-ol" title="Ordered List">1=</button> </div> <div class="toolbar-group"> <button class="toolbar-btn btn-align-left" title="Align Left">←</button> <button class="toolbar-btn btn-align-center" title="Align Center">↔</button> <button class="toolbar-btn btn-align-right" title="Align Right">→</button> </div> <div class="toolbar-group"> <button class="toolbar-btn btn-quote" title="Quote">”</button> <button class="toolbar-btn" data-cmd="createLink" title="Insert Link" style="display:flex;align-items:center;justify-content:center;"><i data-lucide="link" style="width:14px;height:14px;"></i></button> </div> <div class="toolbar-group"> <button class="toolbar-btn" data-cmd="undo" title="Undo">↶</button> <button class="toolbar-btn" data-cmd="redo" title="Redo">↷</button> </div> </div> <div class="editor-info-bar"> <div class="save-indicator saved" id="save-indicator"> <span class="live-dot" id="live-analysis-dot"></span> <span id="live-analysis-label">Writing protected by AI</span> </div> <div style="margin-left:auto;display:flex;gap:1rem;"> <span class="word-count">0 words</span> <span class="char-count">0 characters</span> </div> </div> <!-- Scrollable A4 Sheet page --> <div class="editor-scroll"> <div class="editor-page"> <div class="editor-content" id="editor-content" placeholder="Start writing your research paper here..."> <h1>CRISPR Mechanisms in Neurodegeneration - Draft 1</h1> <p>Alzheimer's disease (AD) represents one of the most devastating neurodegenerative disorders, affecting over 55 million individuals worldwide. The amyloid cascade hypothesis posits that the accumulation of beta-amyloid plaques initiates a pathological cascade leading to neuronal death.</p> <p>Recent advances in CRISPR-Cas9 technology have opened new therapeutic avenues for genetic diseases previously considered untreatable. The precision of guide RNA targeting allows researchers to edit specific genomic loci with unprecedented accuracy.</p> <h2>Methods</h2> <p>We employed a murine model expressing humanized APP and PSEN1 mutations. CRISPR editing was performed using AAV9 delivery vectors injected intracerebroventricularly.</p> </div> </div> </div> </div> <!-- Intelligence Sidebar Panel --> <div class="intel-panel"> <div class="intel-panel-header"> <div class="intel-panel-title"><i data-lucide="shield" style="width:16px;height:16px;color:var(--primary-600);"></i> Citation Intelligence</div> <div class="intel-tabs"> <button class="intel-tab active" data-tab="alerts" onclick="switchIntelTab('alerts')">Alerts <span class="count" id="alerts-count">0</span></button> <button class="intel-tab" data-tab="suggestions" onclick="switchIntelTab('suggestions')">Sources</button> <button class="intel-tab" data-tab="similarity" onclick="switchIntelTab('similarity')">Similarity</button> <button class="intel-tab" data-tab="ai-verify" onclick="switchIntelTab('ai-verify')">AI Verify</button> <button class="intel-tab" data-tab="dna" onclick="switchIntelTab('dna')">DNA Profile</button> </div> </div> <div class="intel-body"> <!-- Citation Alerts Pane --> <div class="intel-pane active" id="pane-alerts"> <div id="alerts-list"> <div class="empty-state"> <div class="empty-icon"><i data-lucide="check" style="width:24px;height:24px;color:var(--success-500);"></i></div> <div class="empty-title">All Statements Cited</div> <div class="empty-desc">No missing citation warnings detected in the current text scope.</div> </div> </div> </div> <!-- Sources Recommendations Pane --> <div class="intel-pane" id="pane-suggestions"> <div style="display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem;"> <div style="display:flex;gap:.25rem;"> <select class="select input-sm" id="source-search-index" style="width:105px;padding:.25rem;font-size:10px;background:#fff;border:1px solid var(--color-border);" onchange="updateGoogleScholarLink()"> <option value="local">Local Library</option> <option value="crossref">CrossRef</option> <option value="semantic">Semantic Scholar</option> <option value="pubmed">PubMed</option> <option value="arxiv">arXiv</option> </select> <input type="text" class="input input-sm" id="source-search-input" placeholder="Search..." onkeydown="if(event.key==='Enter') searchSources(this.value)" style="flex:1;min-width:0;"> <button class="btn btn-primary btn-sm" onclick="searchSources(document.getElementById('source-search-input').value)" style="padding:.25rem .5rem;display:flex;align-items:center;justify-content:center;"><i data-lucide="search" style="width:12px;height:12px;"></i></button> </div> <div id="google-scholar-link-container" style="display:none;text-align:right;"> <a href="#" id="google-scholar-link" target="_blank" class="text-xs text-primary font-semibold" style="display:inline-flex;align-items:center;gap:4px;"> <i data-lucide="globe" style="width:12px;height:12px;"></i> Search Google Scholar </a> </div> </div> <div id="suggestions-list" style="max-height:calc(100vh - 280px);overflow-y:auto;padding-right:4px;"> <p class="text-muted text-sm text-center">Suggestions will appear here based on selected statements.</p> </div> </div> <!-- Similarity / Paraphrasing Pane --> <div class="intel-pane" id="pane-similarity"> <div style="display:flex;justify-content:space-between;margin-bottom:1rem;align-items:center;"> <span class="text-sm font-semibold">Semantic Plagiarism Risk</span> <span class="badge risk-low" id="similarity-overall-badge">Low Risk</span> </div> <div style="margin-bottom:1.5rem;text-align:center;"> <div style="font-size:2.5rem;font-weight:900;" id="similarity-score-text">0%</div> <div class="text-xs text-muted">Embedding Similarity Match</div> </div> <button class="btn btn-secondary btn-sm" style="width:100%;margin-bottom:1rem;" onclick="scanEntireDocSimilarity()">Scan Document for Plagiarism</button> <div id="similarity-list"> <p class="text-muted text-sm text-center">Highlight sentences or click the button above to run plagiarism checks.</p> </div> </div> <!-- AI Content Verification Pane --> <div class="intel-pane" id="pane-ai-verify"> <div class="card" style="padding:1rem;margin-bottom:1rem;"> <h4 style="margin-bottom:.5rem;">AI Text Detector</h4> <p class="text-xs text-muted" style="margin-bottom:1rem;">Evaluate potential AI-generated claims and verify citations presence.</p> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:.5rem;"> <span class="text-sm">Verification Score</span> <span class="badge badge-green" id="ai-verify-score">96/100</span> </div> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:.5rem;"> <span class="text-sm">AI Content Match</span> <span class="badge badge-blue" id="ai-content-score">4%</span> </div> <button class="btn btn-secondary btn-sm" style="width:100%;margin-top:.5rem;" onclick="checkAIContent()">Scan Entire Document</button> </div> <div id="ai-verify-details"></div> </div> <!-- Source DNA Lineage Pane --> <div class="intel-pane" id="pane-dna"> <div class="card" style="padding:1rem;margin-bottom:1rem;background:linear-gradient(135deg,#EFF6FF,#EDE9FE)"> <h4 style="margin-bottom:.25rem;">Paragraph DNA Profile</h4> <p class="text-xs text-muted">Visual tracking of sources and origin integrity for every block of text.</p> <div style="font-size:2rem;font-weight:900;color:var(--primary-700);margin:1rem 0;" id="dna-overall-score">95%</div> <div class="text-xs font-semibold" id="dna-profile-type">INTEGRAL SOURCE ALIGNMENT</div> </div> <div id="dna-lineage-list"> <!-- Loaded on paragraph select --> </div> </div> </div> <!-- Statusbar --> <div class="workspace-statusbar"> <div class="statusbar-item good">Score: <span id="status-coverage-score">84%</span></div> <div class="statusbar-item warning">Risk: <span id="status-risk-score">12%</span></div> <div class="statusbar-item">Style: <span id="status-style">APA</span></div> </div> </div> </div> </div> <!-- Version History Drawer overlay --> <div class="modal-overlay" id="versions-drawer" style="justify-content:flex-end;padding:0;"> <div class="modal" style="height:100vh;max-width:320px;border-radius:0;transform:translateX(100%);transition:transform var(--transition-base);"> <div class="modal-header"> <h2 class="modal-title">Document History</h2> <button class="btn btn-ghost btn-icon" onclick="closeModal('versions-drawer')">✕</button> </div> <div class="modal-body" id="versions-list-body" style="padding:0;"> <!-- Version list items --> </div> </div> </div> <!-- AI Chat Assistant Bubble --> <div class="ai-chat-bubble"> <button class="ai-chat-btn" onclick="toggleAIChat()" style="display:flex;align-items:center;justify-content:center;"><i data-lucide="bot" style="width:24px;height:24px;"></i></button> <div class="ai-chat-window" id="ai-chat-window"> <div class="ai-chat-header"> <span><i data-lucide="bot" style="width:16px;height:16px;display:inline-block;vertical-align:middle;margin-right:6px;"></i>CitationGuard AI Assistant</span> <button class="btn btn-ghost btn-icon-sm" style="color:#fff;margin-left:auto;" onclick="toggleAIChat()">✕</button> </div> <div class="ai-chat-messages" id="ai-chat-messages"> <div class="ai-msg assistant">Hello! I am your Academic Integrity advisor. How can I help verify your citations or research integrity today?</div> </div> <div class="ai-chat-input"> <input type="text" id="ai-chat-input-field" placeholder="Ask a citation question..." onkeydown="if(event.key==='Enter') sendChatMessage()"> <button class="btn btn-primary btn-sm" onclick="sendChatMessage()">Send</button> </div> </div> </div> <!-- Modal for adding citations manually --> <div class="modal-overlay" id="add-citation-modal"> <div class="modal modal-lg"> <div class="modal-header"> <h2 class="modal-title">Insert Reference Citation</h2> <button class="btn btn-ghost btn-icon" onclick="closeModal('add-citation-modal')">✕</button> </div> <div class="modal-body"> <div class="form-group"> <label class="form-label" for="cit-title">Source Title *</label> <input type="text" id="cit-title" name="cit-title" class="input" placeholder="e.g. DNA double-strand breaks induced by CRISPR-Cas9" autocomplete="off"> </div> <div class="grid-2" style="gap:1rem;"> <div class="form-group" style="margin:0;"> <label class="form-label" for="cit-authors">Authors *</label> <input type="text" id="cit-authors" name="cit-authors" class="input" placeholder="e.g. Chen, S., Robertson, J." autocomplete="off"> </div> <div class="form-group" style="margin:0;"> <label class="form-label" for="cit-year">Year *</label> <input type="text" id="cit-year" name="cit-year" class="input" placeholder="e.g. 2024" autocomplete="off"> </div> </div> <div class="grid-2" style="gap:1rem;margin-top:1rem;"> <div class="form-group" style="margin:0;"> <label class="form-label" for="cit-journal">Journal / Book Title</label> <input type="text" id="cit-journal" name="cit-journal" class="input" placeholder="e.g. Nature Biotech" autocomplete="off"> </div> <div class="form-group" style="margin:0;"> <label class="form-label" for="cit-doi">DOI</label> <input type="text" id="cit-doi" name="cit-doi" class="input" placeholder="e.g. 10.1038/nbt.3123" autocomplete="off"> </div> </div> <div class="grid-2" style="gap:1rem;margin-top:1rem;"> <div class="form-group" style="margin:0;"> <label class="form-label" for="cit-pages">Page Numbers</label> <input type="text" id="cit-pages" name="cit-pages" class="input" placeholder="e.g. 45-52" autocomplete="off"> </div> <div class="form-group" style="margin:0;"> <label class="form-label" for="cit-type">Source Type</label> <select id="cit-type" name="cit-type" class="select input"> <option value="journal">Journal Paper</option> <option value="book">Book</option> <option value="conference">Conference Proceeding</option> <option value="website">Website</option> </select> </div> </div> </div> <div class="modal-footer"> <button class="btn btn-secondary" onclick="closeModal('add-citation-modal')">Cancel</button> <button class="btn btn-primary" onclick="submitCitation()">Insert Citation</button> </div> </div> </div> <script src="js/storage.js"></script> <script src="js/ai.js"></script> <script src="js/editor.js"></script> <script src="js/citation-engine.js"></script> <script> let currentProjectId = null; let currentDocumentId = null; let currentCitations = []; let academicEditor = null; let citationEngine = null; let selectedSentenceText = ''; // Initialize workspace async function initWorkspace() { if (!await authGuard()) return; const params = new URLSearchParams(location.search); currentProjectId = parseInt(params.get('project')); if (!currentProjectId) { showToast('Project ID required. Redirecting to Dashboard...', 'error'); setTimeout(() => window.location.href = 'dashboard.php', 1500); return; } // Load project details const projRes = await api.getProject(currentProjectId).catch(() => null); if (projRes?.status === 'success') { const proj = projRes.data.project; document.getElementById('workspace-project-title').textContent = proj.title; document.getElementById('workspace-style-select').value = proj.citation_style || 'APA'; document.getElementById('status-style').textContent = proj.citation_style || 'APA'; // Fetch and store library papers in window for local similarity check reference const libRes = await api.getLibraryPapers({ project_id: currentProjectId }).catch(() => null); if (libRes?.status === 'success') { window.libraryPapers = libRes.data.papers || []; } // Load documents const docRes = await api.getDocuments(currentProjectId).catch(() => null); if (docRes?.status === 'success') { renderDocumentsSidebar(docRes.data.documents); if (docRes.data.documents.length > 0) { loadDocument(docRes.data.documents[0].id); } else { createNewDocument(); } } } } function renderDocumentsSidebar(documents) { const list = document.getElementById('document-sidebar-list'); list.innerHTML = documents.map(d => ` <div class="doc-item ${d.id === currentDocumentId ? 'active' : ''}" onclick="loadDocument(${d.id})"> <i data-lucide="file-text" style="width:14px;height:14px;color:var(--gray-500);flex-shrink:0;"></i> <span class="truncate" style="flex:1;">${d.title}</span> </div> `).join(''); if (typeof lucide !== 'undefined') { lucide.createIcons(); } } async function loadDocument(docId) { currentDocumentId = docId; // Re-highlight active side menu document.querySelectorAll('.doc-item').forEach(el => el.classList.remove('active')); const docRes = await api.getDocument(docId).catch(() => null); if (docRes?.status === 'success') { const doc = docRes.data.document; currentCitations = doc.citations || []; // Initialize editor if not done yet if (!academicEditor) { academicEditor = new AcademicEditor('editor-content', 'editor-toolbar', 'editor-info-bar', { onInput: (html) => handleEditorInput(html), onSelection: (text) => handleEditorSelection(text) }); citationEngine = new CitationEngine(academicEditor, { onAnalysisCompleted: (stats) => handleAnalysisFinished(stats) }); } academicEditor.setContent(doc.content || '<p></p>'); // Update word/char count immediately after content is set setTimeout(updateWordCount, 50); document.getElementById('status-coverage-score').textContent = parseFloat(doc.citation_coverage_score || 0).toFixed(0) + '%'; const savedPlag = parseFloat(doc.plagiarism_risk_score || 0).toFixed(0); document.getElementById('status-risk-score').textContent = savedPlag + '%'; document.getElementById('similarity-score-text').textContent = savedPlag + '%'; const plagRisk = savedPlag >= 70 ? 'critical' : (savedPlag >= 40 ? 'high' : (savedPlag >= 20 ? 'moderate' : 'low')); document.getElementById('similarity-overall-badge').className = `badge risk-${plagRisk}`; document.getElementById('similarity-overall-badge').textContent = plagRisk.toUpperCase(); // Fetch alerts loadAlerts(); loadDNAProfile(); } } async function createNewDocument() { const res = await api.createDocument({ project_id: currentProjectId, title: 'Untitled Draft', content: '<h1>New Section Title</h1><p>Start your scientific research paragraphs here...</p>' }).catch(() => null); if (res?.status === 'success') { showToast('New draft document created!', 'success'); const docRes = await api.getDocuments(currentProjectId).catch(() => null); if (docRes?.status === 'success') { renderDocumentsSidebar(docRes.data.documents); } loadDocument(res.data.document.id); } } function updateWordCount() { const editorEl = document.getElementById('editor-content'); const rawText = editorEl ? (editorEl.innerText || '') : ''; const words = rawText.trim() === '' ? 0 : rawText.trim().split(/\s+/).length; const chars = rawText.replace(/\s/g, '').length; document.querySelector('.word-count').textContent = words.toLocaleString() + ' words'; document.querySelector('.char-count').textContent = chars.toLocaleString() + ' characters'; } function handleEditorInput(html) { clearTimeout(this.autoSaveTimer); document.getElementById('save-indicator').className = 'save-indicator saving'; document.getElementById('live-analysis-label').textContent = 'Saving draft...'; // Live word/char count on every keystroke updateWordCount(); this.autoSaveTimer = setTimeout(async () => { await api.saveDocument(currentDocumentId, { content: html, citation_count: currentCitations.length, citation_coverage_score: parseFloat(document.getElementById('status-coverage-score').textContent), plagiarism_risk_score: parseFloat(document.getElementById('status-risk-score').textContent) }).catch(() => null); document.getElementById('save-indicator').className = 'save-indicator saved'; document.getElementById('live-analysis-label').textContent = 'Writing protected by AI'; citationEngine.analyzeDocument(currentDocumentId); }, 2000); } function handleEditorSelection(text) { selectedSentenceText = text; if (text.length > 15) { searchSources(text); checkSimilarity(text); } } async function triggerFullAnalysis() { if (!citationEngine) return; showToast('Running full document integrity scan...', 'info'); await citationEngine.analyzeDocument(currentDocumentId); await scanEntireDocSimilarity(); loadAlerts(); showToast('Integrity check completed successfully', 'success'); } async function loadAlerts() { const res = await api.getAnalysis(currentDocumentId).catch(() => null); const list = document.getElementById('alerts-list'); const countEl = document.getElementById('alerts-count'); if (res?.status === 'success' && res.data.analysis.length > 0) { const listHtml = res.data.analysis.map(a => ` <div class="alert-card warning" id="alert-card-${a.id}"> <div class="alert-card-header"> <span class="badge risk-${a.risk_level}">${a.risk_level.toUpperCase()} ALERT</span> <span class="text-xs text-muted">${(a.confidence * 100).toFixed(0)}% confidence</span> </div> <div class="alert-card-body"> <div class="alert-card-excerpt">${a.sentence_text}</div> <div class="alert-card-reason">${a.ai_response || 'Uncited claims detected. Supporting research references required.'}</div> <div class="alert-card-actions"> <button class="btn btn-primary btn-sm" style="flex:1" onclick="openCitationModal('${a.sentence_text.replace(/'/g, "\\'")}')">Add Citation</button> <button class="btn btn-ghost btn-sm" onclick="dismissAlert(${a.id}, this)">Ignore</button> </div> </div> </div> `).join(''); list.innerHTML = listHtml; countEl.textContent = res.data.analysis.length; countEl.style.display = 'inline-block'; } else { list.innerHTML = ` <div class="empty-state"> <div class="empty-icon"><i data-lucide="check" style="width:24px;height:24px;color:var(--success-500);"></i></div> <div class="empty-title">All Statements Cited</div> <div class="empty-desc">No missing citation warnings detected in the current text scope.</div> </div>`; countEl.style.display = 'none'; if (typeof lucide !== 'undefined') { lucide.createIcons(); } } } async function dismissAlert(alertId, btnEl) { if (btnEl) { btnEl.disabled = true; btnEl.textContent = 'Ignoring...'; } const res = await api.request('analysis.php', { action: 'dismiss', id: alertId }).catch(() => null); const card = document.getElementById(`alert-card-${alertId}`); if (card) { card.style.opacity = '0'; card.style.transition = 'opacity 0.3s'; setTimeout(() => { card.remove(); // Update count badge const remaining = document.querySelectorAll('.alert-card').length; const countEl = document.getElementById('alerts-count'); if (remaining === 0) { document.getElementById('alerts-list').innerHTML = ` <div class="empty-state"> <div class="empty-icon"><i data-lucide="check" style="width:24px;height:24px;color:var(--success-500);"></i></div> <div class="empty-title">All Statements Cited</div> <div class="empty-desc">No missing citation warnings detected.</div> </div>`; countEl.style.display = 'none'; if (typeof lucide !== 'undefined') { lucide.createIcons(); } } else { countEl.textContent = remaining; } }, 300); } showToast('Alert dismissed', 'info'); } async function loadDNAProfile() { if (!academicEditor || !citationEngine) return; const sentences = citationEngine.getSentences ? citationEngine.getSentences() : []; if (sentences.length === 0) return; // Build a simple local DNA profile without AI call const totalSents = sentences.length; const citedSents = sentences.filter(s => s.hasCitation).length; const coverage = totalSents > 0 ? Math.round((citedSents / totalSents) * 100) : 95; document.getElementById('dna-overall-score').textContent = `${coverage}%`; document.getElementById('dna-overall-score').style.color = coverage >= 80 ? 'var(--success-600)' : (coverage >= 50 ? 'var(--warning-600)' : 'var(--danger-600)'); document.getElementById('dna-profile-type').textContent = coverage >= 80 ? 'INTEGRAL SOURCE ALIGNMENT' : (coverage >= 50 ? 'PARTIAL SOURCE ALIGNMENT' : 'WEAK SOURCE ALIGNMENT'); const list = document.getElementById('dna-lineage-list'); if (currentCitations.length > 0) { list.innerHTML = currentCitations.slice(0, 6).map(c => ` <div class="dna-source-item"> <span class="dna-source-dot" style="background:var(--primary-600)"></span> <span class="text-xs font-semibold" style="flex:1;">${c.source_title || c.title || 'Unnamed Source'}</span> <span class="badge badge-green">CITED</span> </div> `).join(''); } else { list.innerHTML = ` <div class="dna-source-item"> <span class="dna-source-dot" style="background:var(--danger-500)"></span> <span class="text-xs" style="flex:1;">Document contains uncited primary concepts</span> <span class="badge badge-red">DEFICIENT</span> </div>`; } } let currentSearchResults = []; function updateGoogleScholarLink() { const q = document.getElementById('source-search-input').value.trim(); const index = document.getElementById('source-search-index').value; const container = document.getElementById('google-scholar-link-container'); const link = document.getElementById('google-scholar-link'); if (q && index !== 'local') { link.href = `https://scholar.google.com/scholar?q=${encodeURIComponent(q)}`; container.style.display = 'block'; } else { container.style.display = 'none'; } } async function searchSources(q) { if (!q || !q.trim()) return; updateGoogleScholarLink(); const resList = document.getElementById('suggestions-list'); resList.innerHTML = `<div class="text-center py-4"><span class="spinner"></span></div>`; const index = document.getElementById('source-search-index').value; let papers = []; try { if (index === 'local') { papers = await aiService.suggestCitations(q); } else if (index === 'crossref') { papers = await fetchCrossRef(q); } else if (index === 'semantic') { papers = await fetchSemanticScholar(q); } else if (index === 'pubmed') { papers = await fetchPubMed(q); } else if (index === 'arxiv') { papers = await fetchArXiv(q); } } catch (err) { console.error('API Error: ', err); resList.innerHTML = `<p class="text-danger text-sm text-center">Failed to fetch results from ${index.toUpperCase()}.</p>`; return; } currentSearchResults = papers; if (papers.length > 0) { resList.innerHTML = papers.map((p, i) => { return ` <div class="cite-suggestion" style="margin-bottom:0.75rem;padding:0.75rem;border:1px solid var(--color-border);border-radius:8px;background:#fff;text-align:left;"> <div class="cite-title" style="font-weight:700;font-size:12px;margin-bottom:4px;color:var(--gray-900);line-height:1.4;">${p.title}</div> <div class="cite-authors" style="font-size:11px;color:var(--gray-600);">${p.authors} (${p.year})</div> <div class="cite-meta" style="font-size:10px;color:var(--gray-500);margin-top:2px;"> <span>Journal: ${p.journal || 'Research Proceedings'}</span> </div> ${p.doi ? `<div class="cite-doi" style="font-size:10px;color:var(--primary-600);margin-top:2px;font-family:monospace;word-break:break-all;">DOI: ${p.doi}</div>` : ''} <div style="display:flex;gap:0.25rem;margin-top:0.5rem;"> <button class="btn btn-primary btn-sm" style="font-size:10px;padding:2px 6px;flex:1;" onclick="insertSuggestedCitationFromIndex(${i})">Insert [Cit]</button> ${index !== 'local' ? `<button class="btn btn-secondary btn-sm" id="import-btn-${i}" style="font-size:10px;padding:2px 6px;" onclick="importToLibraryFromIndex(${i}, this)">Import</button>` : ''} </div> </div> `; }).join(''); } else { resList.innerHTML = `<p class="text-muted text-sm text-center">No reference citations found for the query.</p>`; } } function insertSuggestedCitationFromIndex(idx) { const p = currentSearchResults[idx]; if (!p) return; insertSuggestedCitation(p.authors, p.year, p.title, p.journal || '', p.doi || ''); } async function importToLibraryFromIndex(idx, btnEl) { const p = currentSearchResults[idx]; if (!p) return; btnEl.disabled = true; btnEl.textContent = 'Importing...'; const res = await api.importPaperMetadata({ project_id: currentProjectId, title: p.title, authors: p.authors, year: p.year, journal: p.journal || 'Research Proceedings', doi: p.doi || '', abstract: p.abstract || '' }).catch(() => null); if (res?.status === 'success') { btnEl.textContent = 'Imported'; btnEl.className = 'btn btn-success btn-sm'; btnEl.disabled = true; showToast('Reference imported into your project library!', 'success'); // Refresh library papers loaded in workspace state const libRes = await api.getLibraryPapers({ project_id: currentProjectId }).catch(() => null); if (libRes?.status === 'success') { window.libraryPapers = libRes.data.papers || []; } } else { btnEl.disabled = false; btnEl.textContent = 'Import'; showToast(res?.message || 'Failed to import reference', 'error'); } } async function fetchCrossRef(q) { const url = `https://api.crossref.org/works?query=${encodeURIComponent(q)}&rows=10`; const res = await fetch(url).then(r => r.json()); const items = res?.message?.items || []; return items.map(item => { let authors = 'Unknown Authors'; if (item.author) { authors = item.author.map(a => { if (a.family && a.given) return `${a.family}, ${a.given}`; return a.family || a.name || 'Unknown'; }).join(', '); } let year = 'n.d.'; if (item.issued && item.issued['date-parts'] && item.issued['date-parts'][0]) { year = item.issued['date-parts'][0][0]; } else if (item.created && item.created['date-parts'] && item.created['date-parts'][0]) { year = item.created['date-parts'][0][0]; } return { title: item.title?.[0] || 'Untitled', authors: authors, year: String(year), journal: item['container-title']?.[0] || 'CrossRef Index', doi: item.DOI || '', abstract: '' }; }); } async function fetchSemanticScholar(q) { const url = `https://api.semanticscholar.org/graph/v1/paper/search?query=${encodeURIComponent(q)}&limit=10&fields=title,authors,year,venue,externalIds,abstract`; const res = await fetch(url).then(r => r.json()); const items = res?.data || []; return items.map(item => { const authors = item.authors ? item.authors.map(a => a.name).join(', ') : 'Unknown Authors'; return { title: item.title || 'Untitled', authors: authors, year: String(item.year || 'n.d.'), journal: item.venue || 'Semantic Scholar', doi: item.externalIds?.DOI || '', abstract: item.abstract || '' }; }); } async function fetchPubMed(q) { const searchUrl = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=${encodeURIComponent(q)}&retmode=json&retmax=10`; const searchRes = await fetch(searchUrl).then(r => r.json()); const ids = searchRes?.esearchresult?.idlist || []; if (ids.length === 0) return []; const summaryUrl = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&id=${ids.join(',')}&retmode=json`; const summaryRes = await fetch(summaryUrl).then(r => r.json()); const results = summaryRes?.result || {}; return ids.map(id => { const item = results[id] || {}; const authors = item.authors ? item.authors.map(a => a.name).join(', ') : 'Unknown Authors'; const year = item.pubdate ? item.pubdate.substring(0, 4) : 'n.d.'; return { title: item.title || 'Untitled', authors: authors, year: year, journal: item.source || 'PubMed Central', doi: item.articleids?.find(aid => aid.idtype === 'doi')?.value || '', abstract: '' }; }); } async function fetchArXiv(q) { const url = `https://export.arxiv.org/api/query?search_query=all:${encodeURIComponent(q)}&max_results=10`; const text = await fetch(url).then(r => r.text()); const parser = new DOMParser(); const xmlDoc = parser.parseFromString(text, 'text/xml'); const entries = xmlDoc.getElementsByTagName('entry'); const results = []; for (let i = 0; i < entries.length; i++) { const entry = entries[i]; const title = entry.getElementsByTagName('title')[0]?.textContent?.replace(/\n/g, ' ').trim() || 'Untitled'; const authorsList = []; const authorNodes = entry.getElementsByTagName('author'); for (let j = 0; j < authorNodes.length; j++) { authorsList.push(authorNodes[j].getElementsByTagName('name')[0]?.textContent || ''); } const published = entry.getElementsByTagName('published')[0]?.textContent || 'n.d.'; const year = published.substring(0, 4); const arxivId = entry.getElementsByTagName('id')[0]?.textContent || ''; const summary = entry.getElementsByTagName('summary')[0]?.textContent?.trim() || ''; results.push({ title: title, authors: authorsList.join(', '), year: year, journal: 'arXiv Preprint', doi: arxivId.split('/abs/')[1] || arxivId, abstract: summary }); } return results; } async function checkSimilarity(text) { const list = document.getElementById('similarity-list'); list.innerHTML = `<div class="text-center py-4"><span class="spinner"></span><p class="text-xs text-muted" style="margin-top:.5rem;">Checking CrossRef · Semantic Scholar · arXiv...</p></div>`; const res = await api.checkPlagiarism(text).catch(() => null); if (!res || res.status !== 'success') { list.innerHTML = `<p class="text-muted text-sm text-center">Plagiarism check unavailable. Please try again.</p>`; return; } const d = res.data; document.getElementById('similarity-score-text').textContent = `${d.similarityScore}%`; document.getElementById('similarity-overall-badge').className = `badge risk-${d.riskLevel}`; document.getElementById('similarity-overall-badge').textContent = d.riskLevel.toUpperCase(); const sourceBadge = s => ({ CrossRef:'badge-blue', 'Semantic Scholar':'badge-purple', PubMed:'badge-green', arXiv:'badge-secondary' }[s] || 'badge-secondary'); const matchCards = (d.matches || []).map(m => ` <div style="border:1px solid var(--color-border);border-radius:8px;padding:.6rem .75rem;margin-bottom:.5rem;background:#fff;"> <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:.5rem;"> <div style="flex:1;font-size:11px;font-weight:600;line-height:1.4;color:var(--gray-900);">${m.title}</div> <span class="badge ${sourceBadge(m.source)}" style="font-size:9px;white-space:nowrap;flex-shrink:0;">${m.source}</span> </div> <div style="font-size:10px;color:var(--gray-500);margin-top:2px;">${m.authors} · ${m.year}${m.journal ? ' · ' + m.journal : ''}</div> ${m.url ? `<a href="${m.url}" target="_blank" style="font-size:10px;color:var(--primary-600);">View Source →</a>` : ''} <div style="font-size:10px;font-weight:700;color:var(--danger-600);margin-top:3px;"> Similarity: ${Math.round((m.score||0)*100)}% <span style="font-weight:400;color:var(--gray-500);margin-left:6px;">· ${m.detectionMode === 'title-match' ? '🔴 Title Match' : m.detectionMode === 'title-similarity' ? '🟡 Title Similarity' : '🔵 Abstract Match'}</span> </div> </div> `).join(''); if (d.similarityScore >= 15) { list.innerHTML = ` <div class="alert alert-danger" style="border-left:4px solid var(--danger-600);background:#FEF2F2;color:#991B1B;padding:.75rem;border-radius:6px;font-size:12px;line-height:1.4;margin-bottom:.75rem;"> <div class="font-semibold" style="font-weight:700;margin-bottom:4px;">Paraphrasing Risk Detected</div> <div>${d.recommendation}</div> </div> <button class="btn btn-gradient btn-sm" style="width:100%;margin-bottom:.75rem;font-size:11px;display:inline-flex;align-items:center;justify-content:center;gap:6px;" onclick="openRephraseModal(selectedSentenceText || '${text.replace(/'/g,"\\'").substring(0,300)}')"><i data-lucide="sparkles" style="width:12px;height:12px;"></i> Rephrase with AI → Natural Language</button> ${matchCards || '<p class="text-muted text-xs text-center">No specific paper matches returned.</p>'} <p class="text-xs text-muted" style="margin-top:.5rem;text-align:right;">Checked: ${(d.sourcesChecked||[]).join(' · ')}</p>`; } else { list.innerHTML = `<p class="text-success text-sm text-center" style="display:flex;align-items:center;justify-content:center;gap:6px;"><i data-lucide="check" style="width:14px;height:14px;"></i> Low plagiarism risk across all academic databases.</p> <p class="text-xs text-muted" style="text-align:center;margin-top:.25rem;">Checked: ${(d.sourcesChecked||[]).join(' · ')}</p>`; } if (typeof lucide !== 'undefined') { lucide.createIcons(); } } async function scanEntireDocSimilarity() { const list = document.getElementById('similarity-list'); list.innerHTML = `<div class="text-center py-4"><span class="spinner"></span><p class="text-xs text-muted" style="margin-top:.5rem;">Scanning CrossRef · Semantic Scholar · arXiv...</p></div>`; const plainText = academicEditor ? (academicEditor.editor.innerText || '') : ''; if (plainText.trim().length < 15) { showToast('Document text is too short to run plagiarism scan', 'warning'); list.innerHTML = `<p class="text-muted text-sm text-center">Write more text to run a scan.</p>`; return; } const wordCount = plainText.trim().split(/\s+/).length; const chunkNote = wordCount > 5000 ? ` · ${wordCount.toLocaleString()} words` : ''; showToast(`Scanning across CrossRef, Semantic Scholar, arXiv${chunkNote}...`, 'info'); list.innerHTML = `<div class="text-center py-4"><span class="spinner"></span><p class="text-xs text-muted" style="margin-top:.5rem;">Scanning ${wordCount.toLocaleString()} words across CrossRef · Semantic Scholar · arXiv...</p></div>`; const res = await api.checkPlagiarism(plainText).catch(() => null); if (!res || res.status !== 'success') { list.innerHTML = `<p class="text-muted text-sm text-center">Plagiarism check unavailable. Please try again.</p>`; return; } const d = res.data; document.getElementById('similarity-score-text').textContent = `${d.similarityScore}%`; document.getElementById('similarity-overall-badge').className = `badge risk-${d.riskLevel}`; document.getElementById('similarity-overall-badge').textContent = d.riskLevel.toUpperCase(); document.getElementById('status-risk-score').textContent = `${d.similarityScore}%`; await api.saveDocument(currentDocumentId, { content: academicEditor.getContent(), citation_count: currentCitations.length, citation_coverage_score: parseFloat(document.getElementById('status-coverage-score').textContent), plagiarism_risk_score: parseFloat(d.similarityScore) }).catch(() => null); const sourceBadge = s => ({ CrossRef:'badge-blue', 'Semantic Scholar':'badge-purple', PubMed:'badge-green', arXiv:'badge-secondary' }[s] || 'badge-secondary'); const matchCards = (d.matches || []).map(m => ` <div style="border:1px solid var(--color-border);border-radius:8px;padding:.6rem .75rem;margin-bottom:.5rem;background:#fff;"> <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:.5rem;"> <div style="flex:1;font-size:11px;font-weight:600;line-height:1.4;color:var(--gray-900);">${m.title}</div> <span class="badge ${sourceBadge(m.source)}" style="font-size:9px;white-space:nowrap;flex-shrink:0;">${m.source}</span> </div> <div style="font-size:10px;color:var(--gray-500);margin-top:2px;">${m.authors} · ${m.year}${m.journal ? ' · ' + m.journal : ''}</div> ${m.url ? `<a href="${m.url}" target="_blank" style="font-size:10px;color:var(--primary-600);">View Source →</a>` : ''} <div style="font-size:10px;font-weight:700;color:var(--danger-600);margin-top:3px;">Similarity: ${Math.round((m.score||0)*100)}%</div> </div> `).join(''); if (d.similarityScore >= 15) { list.innerHTML = ` <div class="alert alert-danger" style="border-left:4px solid var(--danger-600);background:#FEF2F2;color:#991B1B;padding:.75rem;border-radius:6px;font-size:12px;line-height:1.4;text-align:left;margin-bottom:.75rem;"> <div style="font-weight:700;margin-bottom:4px;font-size:13px;">High Plagiarism Match Detected!</div> <div style="margin-bottom:6px;">${d.recommendation}</div> ${d.patterns.length > 0 ? `<div style="font-size:10px;color:var(--danger-700);">Pattern: ${d.patterns.join(', ')}</div>` : ''} </div> <button class="btn btn-gradient btn-sm" style="width:100%;margin-bottom:.75rem;font-size:11px;display:inline-flex;align-items:center;justify-content:center;gap:6px;" onclick="openRephraseModal(selectedSentenceText || (academicEditor ? academicEditor.editor.innerText.substring(0,800) : ''))"><i data-lucide="sparkles" style="width:12px;height:12px;"></i> Rephrase Flagged Text with AI → Natural Language</button> ${matchCards} <p class="text-xs text-muted" style="margin-top:.5rem;text-align:right;">Checked: ${(d.sourcesChecked||[]).join(' · ')}</p>`; showToast('Plagiarism scan complete — matches found!', 'warning'); } else { list.innerHTML = ` <p class="text-success text-sm text-center" style="padding:1rem;display:flex;align-items:center;justify-content:center;gap:6px;"><i data-lucide="check" style="width:14px;height:14px;"></i> Low plagiarism risk. No significant matches found.</p> <p class="text-xs text-muted" style="text-align:center;">Checked: ${(d.sourcesChecked||[]).join(' · ')}</p>`; showToast('Plagiarism scan complete — low risk', 'success'); } if (typeof lucide !== 'undefined') { lucide.createIcons(); } } async function checkAIContent() { const details = document.getElementById('ai-verify-details'); details.innerHTML = `<div class="text-center py-4"><span class="spinner"></span><p class="text-xs text-muted" style="margin-top:.5rem;">Analyzing with AI detector...</p></div>`; if (!academicEditor) { details.innerHTML = `<p class="text-muted text-sm text-center">Editor not ready. Please wait.</p>`; return; } const text = academicEditor.editor ? (academicEditor.editor.innerText || '') : academicEditor.getContent(); if (text.trim().length < 20) { details.innerHTML = `<p class="text-muted text-sm text-center">Write at least a paragraph to run AI detection.</p>`; return; } // Heuristic AI detection (no external API needed) const sentences = text.match(/[^.!?]+[.!?]+/g) || []; const avgLen = sentences.reduce((s, x) => s + x.trim().split(/\s+/).length, 0) / (sentences.length || 1); const passive = (text.match(/\b(is|are|was|were|been|being)\s+\w+ed\b/gi) || []).length; const hedges = (text.match(/\b(however|furthermore|nevertheless|moreover|additionally|consequently|therefore)\b/gi) || []).length; const aiScore = Math.min(95, Math.round(((avgLen > 22 ? 30 : 0) + (passive > 5 ? 20 : 0) + (hedges > 4 ? 25 : 0) + Math.random() * 15))); const verified = 100 - aiScore; document.getElementById('ai-verify-score').textContent = `${verified}/100`; document.getElementById('ai-content-score').textContent = `${aiScore}%`; const label = aiScore >= 60 ? 'High AI probability' : (aiScore >= 30 ? 'Moderate AI indicators' : 'Likely human-written'); const color = aiScore >= 60 ? 'var(--danger-600)' : (aiScore >= 30 ? 'var(--warning-600)' : 'var(--success-600)'); const indicators = []; if (avgLen > 22) indicators.push(`Average sentence length ${avgLen.toFixed(0)} words (AI tends toward longer, uniform sentences)`); if (passive > 5) indicators.push(`${passive} passive-voice constructions detected (common in AI text)`); if (hedges > 4) indicators.push(`${hedges} academic hedge words found (may indicate AI-generated academic style)`); if (!indicators.length) indicators.push('No strong AI indicators found — text appears naturally written.'); details.innerHTML = ` <div style="margin-top:.75rem;padding:.75rem;border-radius:8px;border:1px solid var(--color-border);"> <div style="font-weight:700;font-size:12px;color:${color};margin-bottom:.5rem;">${label}</div> ${indicators.map(i => `<div class="text-xs text-muted" style="margin-bottom:.35rem;">⚠ ${i}</div>`).join('')} <p class="text-xs text-muted" style="margin-top:.5rem;border-top:1px solid var(--color-border-subtle);padding-top:.5rem;">AI detection is probabilistic. Results are indicative, not definitive.</p> </div>`; } function handleAnalysisFinished(stats) { document.getElementById('status-coverage-score').textContent = stats.coverageScore.toFixed(0) + '%'; document.getElementById('status-risk-score').textContent = stats.plagiarismRisk.toFixed(0) + '%'; } function openCitationModal(sentenceText) { selectedSentenceText = sentenceText; openModal('add-citation-modal'); } async function submitCitation() { const title = document.getElementById('cit-title').value.trim(); const authors = document.getElementById('cit-authors').value.trim(); const year = document.getElementById('cit-year').value.trim(); if (!title || !authors || !year) { showToast('Please fill all required fields', 'error'); return; } const citationKey = `cit_${Date.now()}`; const res = await api.addCitation({ document_id: currentDocumentId, source_title: title, authors: authors, year: year, journal: document.getElementById('cit-journal').value, doi: document.getElementById('cit-doi').value, page_numbers: document.getElementById('cit-pages').value, source_type: document.getElementById('cit-type').value, citation_key: citationKey, citation_style: document.getElementById('workspace-style-select').value }).catch(() => null); if (res?.status === 'success') { closeModal('add-citation-modal'); showToast('Citation added successfully!', 'success'); const index = currentCitations.length + 1; academicEditor.insertCitation(`[${index}]`, res.data.citation.id); currentCitations.push(res.data.citation); loadAlerts(); } } function insertSuggestedCitation(authors, year, title, journal, doi) { document.getElementById('cit-title').value = title; document.getElementById('cit-authors').value = authors; document.getElementById('cit-year').value = year; document.getElementById('cit-journal').value = journal; document.getElementById('cit-doi').value = doi; openModal('add-citation-modal'); } // ── AI Chat ────────────────────────────────────────────────── function toggleAIChat() { document.getElementById('ai-chat-window').classList.toggle('open'); } async function sendChatMessage() { const input = document.getElementById('ai-chat-input-field'); const msgText = input.value.trim(); if (!msgText) return; input.value = ''; const chatMsgs = document.getElementById('ai-chat-messages'); const userDiv = document.createElement('div'); userDiv.className = 'ai-msg user animate-fade-in-up'; userDiv.textContent = msgText; chatMsgs.appendChild(userDiv); chatMsgs.scrollTop = chatMsgs.scrollHeight; const assistantDiv = document.createElement('div'); assistantDiv.className = 'ai-msg assistant animate-fade-in-up'; assistantDiv.innerHTML = `<span class="thinking-dots"><span></span><span></span><span></span></span>`; chatMsgs.appendChild(assistantDiv); chatMsgs.scrollTop = chatMsgs.scrollHeight; const context = academicEditor ? academicEditor.getContent() : ''; const response = await aiService.chat(msgText, context); assistantDiv.innerHTML = response; chatMsgs.scrollTop = chatMsgs.scrollHeight; } // ── Version history drawer ──────────────────────────────────── async function openVersionsDrawer() { openModal('versions-drawer'); const res = await api.getVersions(currentDocumentId).catch(() => null); const listBody = document.getElementById('versions-list-body'); if (res?.status === 'success' && res.data.versions.length > 0) { listBody.innerHTML = res.data.versions.map(v => ` <div class="version-item" onclick="restoreVersion(${v.id})"> <div class="version-number">Version ${v.version_number}</div> <div class="version-date">${formatDate(v.created_at)} · ${v.word_count} words</div> <div class="version-summary">${v.change_summary || 'Auto-save version'}</div> </div> `).join(''); } else { listBody.innerHTML = `<p class="text-muted text-sm text-center py-8">No past versions found.</p>`; } } async function restoreVersion(versionId) { if (!confirm("Are you sure you want to restore this version? Your current unsaved content will be overwritten.")) return; const res = await api.restoreVersion(currentDocumentId, versionId).catch(() => null); if (res?.status === 'success') { academicEditor.setContent(res.data.content); closeModal('versions-drawer'); showToast('Version restored successfully', 'success'); } } function switchIntelTab(tab) { document.querySelectorAll('.intel-tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('.intel-pane').forEach(p => p.classList.remove('active')); document.querySelector(`.intel-tab[data-tab="${tab}"]`).classList.add('active'); document.getElementById(`pane-${tab}`).classList.add('active'); } // ── AI Rephrasing ───────────────────────────────────────────── let _rephraseOriginal = ''; let _savedSelection = null; function openRephraseModal(text) { if (!text || !text.trim()) { showToast('Highlight the plagiarised text in the editor first, then click Rephrase.', 'warning'); return; } _rephraseOriginal = text.trim(); // Save current editor selection so we can restore it for Apply const sel = window.getSelection(); _savedSelection = (sel && sel.rangeCount > 0) ? sel.getRangeAt(0).cloneRange() : null; document.getElementById('rephrase-original-text').textContent = _rephraseOriginal; document.getElementById('rephrase-output').innerHTML = ''; document.getElementById('rephrase-apply-btn').style.display = 'none'; document.getElementById('rephrase-copy-btn').style.display = 'none'; openModal('rephrase-modal'); rephraseWithAI(_rephraseOriginal); } async function rephraseWithAI(text) { const out = document.getElementById('rephrase-output'); const applyBtn = document.getElementById('rephrase-apply-btn'); const copyBtn = document.getElementById('rephrase-copy-btn'); out.innerHTML = `<div class="text-center py-3"><span class="spinner"></span><p class="text-xs text-muted" style="margin-top:.5rem;">Rephrasing in natural human language...</p></div>`; const prompt = `You are an expert academic writer. Rephrase the following text so it sounds naturally written by a human researcher — conversational yet scholarly, avoiding overly formal or robotic phrasing. Preserve all factual content and technical accuracy. Return ONLY the rephrased text with no explanations, quotes, or preamble.\n\nText to rephrase:\n${text}`; const res = await fetch('api/ai_proxy.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt }) }).then(r => r.json()).catch(() => null); const rephrased = res?.content?.trim() || res?.choices?.[0]?.message?.content?.trim() || ''; if (!rephrased) { out.innerHTML = `<p class="text-danger text-sm text-center">AI rephrasing failed. Please try again.</p>`; return; } out.innerHTML = ` <div style="background:linear-gradient(135deg,#F0FDF4,#ECFDF5);border:1px solid var(--success-300,#6ee7b7);border-radius:8px;padding:.9rem;font-size:13px;line-height:1.7;color:var(--gray-800);white-space:pre-wrap;">${rephrased.replace(/</g,'<').replace(/>/g,'>')}</div>`; applyBtn.dataset.rephrased = rephrased; applyBtn.style.display = 'inline-flex'; copyBtn.dataset.rephrased = rephrased; copyBtn.style.display = 'inline-flex'; } function applyRephrasedText(btn) { const rephrased = btn.dataset.rephrased; if (!rephrased || !academicEditor) return; const editor = academicEditor.editor; if (!editor) return; editor.focus(); // Helper to format plain text / linebreaks into HTML paragraphs const formatAsHTML = (txt) => { const lines = txt.split(/\n\s*\n/).map(s => s.trim()).filter(Boolean); if (lines.length > 1) { return lines.map(l => `<p>${l}</p>`).join(''); } return txt.replace(/\n/g, '<br>'); }; // Strategy 1: If _savedSelection was recorded and is valid if (_savedSelection) { try { const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(_savedSelection); if (!_savedSelection.collapsed) { const formatted = formatAsHTML(rephrased); if (!document.execCommand('insertHTML', false, formatted)) { const range = sel.getRangeAt(0); range.deleteContents(); const temp = document.createElement('div'); temp.innerHTML = formatted; const frag = document.createDocumentFragment(); while (temp.firstChild) frag.appendChild(temp.firstChild); range.insertNode(frag); } _savedSelection = null; closeModal('rephrase-modal'); showToast('Rephrased text applied to editor!', 'success'); academicEditor.handleInput(); return; } } catch(e) {} } // Strategy 2: Block & DOM Node Matching Strategy const orig = (_rephraseOriginal || '').trim(); const cleanOrig = orig.replace(/\s+/g, ' '); if (cleanOrig && editor) { const blocks = Array.from(editor.querySelectorAll('p, h1, h2, h3, h4, h5, h6, blockquote, div')); // Find block elements whose text matches cleanOrig or cleanOrig contains block text const matchingBlocks = blocks.filter(b => { const bt = b.textContent.trim().replace(/\s+/g, ' '); return bt && (cleanOrig.includes(bt) || bt.includes(cleanOrig) || (cleanOrig.length > 20 && bt.length > 20 && (cleanOrig.startsWith(bt.substring(0, 20)) || bt.startsWith(cleanOrig.substring(0, 20))))); }); if (matchingBlocks.length > 0) { const formatted = formatAsHTML(rephrased); const replacementHTML = formatted.startsWith('<p>') || formatted.startsWith('<h') ? formatted : `<p>${formatted}</p>`; matchingBlocks[0].outerHTML = replacementHTML; // Remove any subsequent matching blocks if original text spanned across multiple blocks for (let i = 1; i < matchingBlocks.length; i++) { if (matchingBlocks[i].parentNode) { matchingBlocks[i].parentNode.removeChild(matchingBlocks[i]); } } closeModal('rephrase-modal'); showToast('Rephrased text applied to editor!', 'success'); academicEditor.handleInput(); return; } // Substring word match fallback across paragraphs const words = cleanOrig.split(' ').filter(w => w.length > 3); if (words.length > 0) { let bestBlock = null; let maxScore = 0; blocks.forEach(b => { const bt = b.textContent.toLowerCase(); let score = 0; words.forEach(w => { if (bt.includes(w.toLowerCase())) score++; }); if (score > maxScore && score >= Math.min(2, words.length)) { maxScore = score; bestBlock = b; } }); if (bestBlock) { const formatted = formatAsHTML(rephrased); const replacementHTML = formatted.startsWith('<p>') || formatted.startsWith('<h') ? formatted : `<p>${formatted}</p>`; bestBlock.outerHTML = replacementHTML; closeModal('rephrase-modal'); showToast('Rephrased text applied to editor!', 'success'); academicEditor.handleInput(); return; } } } // Strategy 3: Fallback editor content replacement if (rephrased) { const formatted = formatAsHTML(rephrased); editor.innerHTML = formatted.startsWith('<p>') || formatted.startsWith('<h') ? formatted : `<p>${formatted}</p>`; closeModal('rephrase-modal'); showToast('Rephrased text applied to editor!', 'success'); academicEditor.handleInput(); return; } } function copyRephrased(btn) { const rephrased = btn.dataset.rephrased; if (!rephrased) return; navigator.clipboard.writeText(rephrased).then(() => showToast('Rephrased text copied to clipboard!', 'success') ); } initWorkspace(); </script> <!-- ── Rephrase Modal ──────────────────────────────────────────────────── --> <div class="modal-overlay" id="rephrase-modal"> <div class="modal" style="max-width:600px;width:95%;"> <div class="modal-header" style="background:var(--primary-600);color:#fff;border-radius:12px 12px 0 0;padding:1.1rem 1.5rem;"> <h2 class="modal-title" style="color:#fff;font-size:1rem;display:flex;align-items:center;gap:6px;"><i data-lucide="sparkles" style="width:16px;height:16px;"></i> AI Rephraser — Natural Language</h2> <button class="btn btn-ghost btn-icon" style="color:#fff;" onclick="closeModal('rephrase-modal')">✕</button> </div> <div class="modal-body" style="padding:1.25rem;"> <div style="margin-bottom:1rem;"> <div style="font-size:11px;font-weight:700;color:var(--gray-500);text-transform:uppercase;letter-spacing:.06em;margin-bottom:.4rem;">Original (Flagged) Text</div> <div id="rephrase-original-text" style="background:#FEF2F2;border:1px solid #FECACA;border-radius:8px;padding:.75rem;font-size:12px;line-height:1.6;color:#991B1B;max-height:160px;overflow-y:auto;white-space:pre-wrap;"></div> </div> <div style="display:flex;align-items:center;gap:.5rem;margin:1rem 0;"> <div style="flex:1;height:1px;background:var(--color-border);"></div> <span style="font-size:11px;color:var(--gray-400);">AI rephrasing ↓</span> <div style="flex:1;height:1px;background:var(--color-border);"></div> </div> <div style="margin-bottom:1rem;"> <div style="font-size:11px;font-weight:700;color:var(--gray-500);text-transform:uppercase;letter-spacing:.06em;margin-bottom:.4rem;">Rephrased in Natural Human Language</div> <div id="rephrase-output" style="min-height:80px;"></div> </div> </div> <div class="modal-footer" style="display:flex;gap:.5rem;justify-content:flex-end;padding:1rem 1.25rem;border-top:1px solid var(--color-border);"> <button class="btn btn-ghost btn-sm" onclick="closeModal('rephrase-modal')">Cancel</button> <button id="rephrase-copy-btn" class="btn btn-secondary btn-sm" style="display:none;display:inline-flex;align-items:center;gap:4px;" onclick="copyRephrased(this)"><i data-lucide="copy" style="width:12px;height:12px;"></i> Copy</button> <button id="rephrase-apply-btn" class="btn btn-gradient btn-sm" style="display:none;display:inline-flex;align-items:center;gap:4px;" onclick="applyRephrasedText(this)"><i data-lucide="check" style="width:12px;height:12px;"></i> Apply to Document</button> </div> </div> </div> <?php include 'includes/footer.php'; ?>