// ── Inline chat for document_view.html ─────────────────────────────────────── // Uses a plain as read-only editor (CodeMirror CDN bundle is huge; // we use a lightweight syntax-highlighted approach instead). const $ = (sel) => document.querySelector(sel); const DOC_ID = window.__DOC_ID__; let selectedText = ''; // ── Load document content ───────────────────────────────────────────────────── async function loadDoc() { const res = await fetch(`/api/documents/${DOC_ID}/content`); if (!res.ok) { $('#editor-mount').textContent = 'Failed to load document.'; return; } const data = await res.json(); $('#doc-name').textContent = data.name; // Render as selectable pre const pre = document.createElement('pre'); pre.id = 'doc-content'; pre.className = 'p-6 text-sm text-copilot-text whitespace-pre-wrap leading-relaxed outline-none min-h-full'; pre.textContent = data.content; $('#editor-mount').appendChild(pre); } // ── Selection detection ─────────────────────────────────────────────────────── document.addEventListener('mouseup', (e) => { if ($('#inline-popup')?.contains(e.target)) return; const sel = window.getSelection(); const text = sel?.toString().trim(); if (!text || text.length < 5) { hidePopup(); return; } selectedText = text; showPopup(e.clientX, e.clientY); }); document.addEventListener('keyup', (e) => { if (e.key === 'Escape') hidePopup(); }); function showPopup(x, y) { const popup = $('#inline-popup'); const preview = $('#inline-selection-preview'); const question = $('#inline-question'); const answer = $('#inline-answer'); preview.textContent = selectedText.length > 300 ? selectedText.slice(0, 300) + '…' : selectedText; question.value = ''; answer.textContent = ''; answer.classList.add('hidden'); $('#inline-loading').classList.add('hidden'); // Position popup near the cursor, stay within viewport const wrapper = $('#editor-wrapper').getBoundingClientRect(); const popupW = 320; const popupH = 260; let left = x - wrapper.left + 12; let top = y - wrapper.top + 12; if (left + popupW > wrapper.width) left = wrapper.width - popupW - 8; if (top + popupH > wrapper.height) top = Math.max(0, y - wrapper.top - popupH - 12); popup.style.left = `${left}px`; popup.style.top = `${top}px`; popup.classList.remove('hidden'); setTimeout(() => question.focus(), 50); } function hidePopup() { $('#inline-popup').classList.add('hidden'); selectedText = ''; } // ── Submit inline question ──────────────────────────────────────────────────── $('#inline-submit').addEventListener('click', askInline); $('#inline-question').addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); askInline(); } }); $('#inline-close').addEventListener('click', hidePopup); async function askInline() { const question = $('#inline-question').value.trim(); if (!question || !selectedText) return; const loading = $('#inline-loading'); const answer = $('#inline-answer'); const btn = $('#inline-submit'); btn.disabled = true; loading.classList.remove('hidden'); answer.classList.add('hidden'); try { const res = await fetch('/api/chat/inline', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ selected_text: selectedText, question }), }); const data = await res.json(); answer.textContent = res.ok ? data.reply : `Error: ${data.error}`; answer.classList.remove('hidden'); } catch (err) { answer.textContent = `Network error: ${err.message}`; answer.classList.remove('hidden'); } finally { loading.classList.add('hidden'); btn.disabled = false; } } loadDoc();
approach instead). const $ = (sel) => document.querySelector(sel); const DOC_ID = window.__DOC_ID__; let selectedText = ''; // ── Load document content ───────────────────────────────────────────────────── async function loadDoc() { const res = await fetch(`/api/documents/${DOC_ID}/content`); if (!res.ok) { $('#editor-mount').textContent = 'Failed to load document.'; return; } const data = await res.json(); $('#doc-name').textContent = data.name; // Render as selectable pre const pre = document.createElement('pre'); pre.id = 'doc-content'; pre.className = 'p-6 text-sm text-copilot-text whitespace-pre-wrap leading-relaxed outline-none min-h-full'; pre.textContent = data.content; $('#editor-mount').appendChild(pre); } // ── Selection detection ─────────────────────────────────────────────────────── document.addEventListener('mouseup', (e) => { if ($('#inline-popup')?.contains(e.target)) return; const sel = window.getSelection(); const text = sel?.toString().trim(); if (!text || text.length < 5) { hidePopup(); return; } selectedText = text; showPopup(e.clientX, e.clientY); }); document.addEventListener('keyup', (e) => { if (e.key === 'Escape') hidePopup(); }); function showPopup(x, y) { const popup = $('#inline-popup'); const preview = $('#inline-selection-preview'); const question = $('#inline-question'); const answer = $('#inline-answer'); preview.textContent = selectedText.length > 300 ? selectedText.slice(0, 300) + '…' : selectedText; question.value = ''; answer.textContent = ''; answer.classList.add('hidden'); $('#inline-loading').classList.add('hidden'); // Position popup near the cursor, stay within viewport const wrapper = $('#editor-wrapper').getBoundingClientRect(); const popupW = 320; const popupH = 260; let left = x - wrapper.left + 12; let top = y - wrapper.top + 12; if (left + popupW > wrapper.width) left = wrapper.width - popupW - 8; if (top + popupH > wrapper.height) top = Math.max(0, y - wrapper.top - popupH - 12); popup.style.left = `${left}px`; popup.style.top = `${top}px`; popup.classList.remove('hidden'); setTimeout(() => question.focus(), 50); } function hidePopup() { $('#inline-popup').classList.add('hidden'); selectedText = ''; } // ── Submit inline question ──────────────────────────────────────────────────── $('#inline-submit').addEventListener('click', askInline); $('#inline-question').addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); askInline(); } }); $('#inline-close').addEventListener('click', hidePopup); async function askInline() { const question = $('#inline-question').value.trim(); if (!question || !selectedText) return; const loading = $('#inline-loading'); const answer = $('#inline-answer'); const btn = $('#inline-submit'); btn.disabled = true; loading.classList.remove('hidden'); answer.classList.add('hidden'); try { const res = await fetch('/api/chat/inline', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ selected_text: selectedText, question }), }); const data = await res.json(); answer.textContent = res.ok ? data.reply : `Error: ${data.error}`; answer.classList.remove('hidden'); } catch (err) { answer.textContent = `Network error: ${err.message}`; answer.classList.remove('hidden'); } finally { loading.classList.add('hidden'); btn.disabled = false; } } loadDoc();