// ── 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]) => `
${name}
`).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}
${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();