// ── State ───────────────────────────────────────────────────────── const state = { files: {}, connections: [], chatHistory: [] }; // ── Navigation ──────────────────────────────────────────────────── function showPage(name) { document.querySelectorAll('.page').forEach(p => p.classList.remove('active')); document.querySelectorAll('.nav-item[data-page]').forEach(n => n.classList.remove('active')); document.getElementById('page-' + name).classList.add('active'); document.querySelector(`.nav-item[data-page="${name}"]`)?.classList.add('active'); window.scrollTo({ top: 0, behavior: 'smooth' }); } document.querySelectorAll('.nav-item[data-page]').forEach(item => { item.addEventListener('click', () => showPage(item.dataset.page)); }); // ── API key ─────────────────────────────────────────────────────── function getKey() { return document.getElementById('apiKeyInput').value.trim(); } // ── Upload ──────────────────────────────────────────────────────── const uploadZone = document.getElementById('uploadZone'); const fileInput = document.getElementById('fileInput'); const uploadStatus = document.getElementById('uploadStatus'); uploadZone.addEventListener('click', () => fileInput.click()); uploadZone.addEventListener('dragover', e => { e.preventDefault(); uploadZone.classList.add('dragover'); }); uploadZone.addEventListener('dragleave', () => uploadZone.classList.remove('dragover')); uploadZone.addEventListener('drop', e => { e.preventDefault(); uploadZone.classList.remove('dragover'); if (e.dataTransfer.files[0]) handleUpload(e.dataTransfer.files[0]); }); fileInput.addEventListener('change', () => { if (fileInput.files[0]) handleUpload(fileInput.files[0]); }); function showStatus(msg, show = true) { uploadStatus.style.display = show ? 'block' : 'none'; uploadStatus.textContent = msg; } async function handleUpload(file) { const key = getKey(); if (!key) { alert('Please enter your Claude API key in the sidebar first.'); return; } showStatus(`⏳ Processing ${file.name} — this may take 20-60 seconds...`); const fd = new FormData(); fd.append('file', file); fd.append('api_key', key); try { const res = await fetch('/api/upload', { method: 'POST', body: fd }); const data = await res.json(); if (!res.ok) { showStatus(`Error: ${data.detail || 'Upload failed'}`); return; } state.files[data.filename] = data; if (data.connections) state.connections = data.connections; showStatus(`✓ ${data.filename} indexed — ${data.chunk_count} chunks, summary generated`); updateAll(); document.getElementById('askBtn').disabled = false; } catch (e) { showStatus('Upload failed: ' + e.message); } finally { fileInput.value = ''; } } // ── Update UI ───────────────────────────────────────────────────── function updateAll() { updateStats(); updateSidebarFiles(); updateFilesGrid(); updateSummaries(); updateConnections(); } function updateStats() { const files = Object.values(state.files); document.getElementById('stat-docs').textContent = files.length; const totalChunks = files.reduce((s, f) => s + (f.chunk_count || 0), 0); document.getElementById('stat-chunks').textContent = totalChunks; const types = [...new Set(files.map(f => f.type?.toUpperCase()))]; document.getElementById('stat-types').textContent = types.length ? types.join(', ') : '—'; } function fileIcon(type) { const icons = { pdf: 'ti-file-type-pdf', image: 'ti-photo', video: 'ti-video', text: 'ti-file-text' }; return icons[type] || 'ti-file'; } function updateSidebarFiles() { const container = document.getElementById('sidebar-files'); const files = Object.entries(state.files); if (!files.length) { container.innerHTML = ''; return; } container.innerHTML = files.map(([name, f]) => ` `).join(''); } function updateFilesGrid() { const grid = document.getElementById('filesGrid'); const files = Object.entries(state.files); if (!files.length) { grid.innerHTML = `

No files yet — upload something to get started

`; return; } grid.innerHTML = files.map(([name, f]) => `
${f.thumb_b64 ? `${name}` : ``}
${name}
${f.type || 'file'}
${f.chunk_count || 0} chunks · click to explore
`).join(''); } function updateSummaries() { const list = document.getElementById('summaryList'); const files = Object.entries(state.files); if (!files.length) { list.innerHTML = `

Upload files to see auto-generated summaries and entities

`; return; } list.innerHTML = files.map(([name, f]) => `
${name}
${f.summary || 'No summary available.'}
${f.entities?.length ? `
${f.entities.map(e => `${e}`).join('')}
` : ''} ${f.topics?.length ? `
${f.topics.map(t => `${t}`).join('')}
` : ''}
`).join(''); } function updateConnections() { const list = document.getElementById('connList'); if (!state.connections.length) { list.innerHTML = `

Upload at least two files to discover cross-document connections

`; return; } list.innerHTML = state.connections.map(c => `
${c.doc1} ↔ ${c.doc2}
${c.connection}
`).join(''); } // ── Ask ─────────────────────────────────────────────────────────── const askBtn = document.getElementById('askBtn'); const queryInput = document.getElementById('queryInput'); const chatHistory = document.getElementById('chatHistory'); askBtn.addEventListener('click', runAsk); queryInput.addEventListener('keydown', e => { if (e.key === 'Enter' && !askBtn.disabled) runAsk(); }); async function runAsk() { const query = queryInput.value.trim(); if (!query) return; const key = getKey(); if (!key) { alert('Please enter your Claude API key first.'); return; } askBtn.disabled = true; askBtn.innerHTML = ' Thinking...'; chatHistory.style.display = 'flex'; document.getElementById('suggestionsCard').style.display = 'none'; chatHistory.innerHTML += `
${query}
`; chatHistory.scrollTop = chatHistory.scrollHeight; const fd = new FormData(); fd.append('query', query); fd.append('api_key', key); try { const res = await fetch('/api/ask', { method: 'POST', body: fd }); const data = await res.json(); const confClass = { high: 'conf-high', medium: 'conf-medium', low: 'conf-low' }[data.confidence] || 'conf-medium'; const sources = (data.sources || []).map(s => `${s.file}${s.page ? ` p.${s.page}` : ''}`).join(''); chatHistory.innerHTML += `
${data.confidence || 'medium'}
${data.answer || 'No answer found.'}
${sources ? `
${sources}
` : ''}
`; chatHistory.scrollTop = chatHistory.scrollHeight; queryInput.value = ''; } catch (e) { chatHistory.innerHTML += `
Error: ${e.message}
`; } finally { askBtn.disabled = false; askBtn.innerHTML = 'Ask'; } } function setQuery(el) { queryInput.value = el.textContent; queryInput.focus(); } // ── Delete file ─────────────────────────────────────────────────── async function deleteFile(name) { await fetch(`/api/file/${encodeURIComponent(name)}`, { method: 'DELETE' }); delete state.files[name]; updateAll(); if (!Object.keys(state.files).length) document.getElementById('askBtn').disabled = true; } // ── File detail modal ───────────────────────────────────────────── let currentModalFile = null; function openModal(name) { const f = state.files[name]; if (!f) return; currentModalFile = name; document.getElementById('modal-filename').textContent = name; document.getElementById('modal-type').textContent = (f.type || 'file').toUpperCase() + ' · ' + (f.file_type_detected || ''); document.getElementById('modal-summary').textContent = f.summary || 'No summary available.'; // thumb const thumbWrap = document.getElementById('modal-thumb-wrap'); const thumbImg = document.getElementById('modal-thumb'); if (f.thumb_b64) { thumbImg.src = `data:image/jpeg;base64,${f.thumb_b64}`; thumbWrap.style.display = 'block'; } else { thumbWrap.style.display = 'none'; } // facts const facts = f.important_facts || []; const factsWrap = document.getElementById('modal-facts-wrap'); const factsEl = document.getElementById('modal-facts'); if (facts.length) { factsEl.innerHTML = facts.map(fact => `
${fact}
`).join(''); factsWrap.style.display = 'block'; } else { factsWrap.style.display = 'none'; } // sections const sections = f.key_sections || []; const sectionsWrap = document.getElementById('modal-sections-wrap'); const sectionsEl = document.getElementById('modal-sections'); if (sections.length) { sectionsEl.innerHTML = sections.map(s => `${s}`).join(''); sectionsWrap.style.display = 'block'; } else { sectionsWrap.style.display = 'none'; } // entities document.getElementById('modal-entities').innerHTML = (f.entities || []).map(e => `${e}`).join('') || 'None extracted'; // topics document.getElementById('modal-topics').innerHTML = (f.topics || []).map(t => `${t}`).join('') || 'None extracted'; // preview document.getElementById('modal-preview').textContent = f.preview || 'No content preview available.'; document.getElementById('fileModal').style.display = 'block'; document.body.style.overflow = 'hidden'; } function closeModal() { document.getElementById('fileModal').style.display = 'none'; document.body.style.overflow = ''; currentModalFile = null; } function askAboutFile(name) { closeModal(); showPage('ask'); document.getElementById('queryInput').value = `Tell me everything important about ${name}`; document.getElementById('queryInput').focus(); } // close modal on backdrop click document.getElementById('fileModal')?.addEventListener('click', function(e) { if (e.target === this) closeModal(); }); async function loadStatus() { try { const res = await fetch('/api/status'); const data = await res.json(); for (const [name, info] of Object.entries(data.files || {})) { if (!state.files[name]) state.files[name] = info; } updateAll(); if (Object.keys(state.files).length) document.getElementById('askBtn').disabled = false; } catch (e) {} } loadStatus();