// Tab switching document.querySelectorAll('.tab').forEach(tab => { tab.addEventListener('click', () => { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); tab.classList.add('active'); document.getElementById('tab-' + tab.dataset.tab).classList.add('active'); }); }); // ── Text file upload zone ── const uploadZone = document.getElementById('uploadZone'); const fileInput = document.getElementById('fileInput'); let selectedFile = null; 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.length) { selectedFile = e.dataTransfer.files[0]; uploadZone.innerHTML = `
${selectedFile.name}
${(selectedFile.size / 1024).toFixed(1)} KB
`; } }); fileInput.addEventListener('change', () => { if (fileInput.files.length) { selectedFile = fileInput.files[0]; uploadZone.innerHTML = `${selectedFile.name}
${(selectedFile.size / 1024).toFixed(1)} KB
`; } }); // ── CSV upload zone ── const csvUploadZone = document.getElementById('csvUploadZone'); const csvFileInput = document.getElementById('csvFileInput'); let csvFile = null; let csvHeaders = []; csvUploadZone.addEventListener('click', () => csvFileInput.click()); csvUploadZone.addEventListener('dragover', e => { e.preventDefault(); csvUploadZone.classList.add('dragover'); }); csvUploadZone.addEventListener('dragleave', () => csvUploadZone.classList.remove('dragover')); csvUploadZone.addEventListener('drop', e => { e.preventDefault(); csvUploadZone.classList.remove('dragover'); if (e.dataTransfer.files.length) handleCSVFile(e.dataTransfer.files[0]); }); csvFileInput.addEventListener('change', () => { if (csvFileInput.files.length) handleCSVFile(csvFileInput.files[0]); }); async function handleCSVFile(file) { csvFile = file; csvUploadZone.innerHTML = `${file.name}
${(file.size / 1024).toFixed(1)} KB -- previewing...
`; const form = new FormData(); form.append('file', file); try { const res = await fetch('/api/csv/preview', { method: 'POST', body: form }); const data = await res.json(); if (data.error) { csvUploadZone.innerHTML = `${data.error}
`; return; } csvHeaders = data.headers; document.getElementById('csvFileName').textContent = data.filename; document.getElementById('csvMeta').textContent = `${data.total_rows} rows | ${data.columns} columns`; // Render table document.getElementById('csvThead').innerHTML = 'Error: ${err.message}
`; } } async function ingestCSV() { if (!csvFile) return; const checkboxes = document.querySelectorAll('#columnPicker input:checked'); const cols = Array.from(checkboxes).map(c => c.value).join(','); const form = new FormData(); form.append('file', csvFile); form.append('columns', cols); const el = document.getElementById('csvResult'); el.classList.remove('hidden'); el.innerHTML = ''; try { const res = await fetch('/api/csv/ingest', { method: 'POST', body: form }); const data = await res.json(); if (data.error) { el.innerHTML = ``; return; } el.innerHTML = `Ingested "${escapeHtml(data.title)}"
Rows indexed: ${data.rows_indexed} | Chunks: ${data.chunks} | Columns: ${data.columns_used.join(', ')}
Total in store: ${data.total_docs}
`; } catch (err) { el.innerHTML = ``; } } // ── PDF upload zone ── const pdfUploadZone = document.getElementById('pdfUploadZone'); const pdfFileInput = document.getElementById('pdfFileInput'); let pdfFile = null; pdfUploadZone.addEventListener('click', () => pdfFileInput.click()); pdfUploadZone.addEventListener('dragover', e => { e.preventDefault(); pdfUploadZone.classList.add('dragover'); }); pdfUploadZone.addEventListener('dragleave', () => pdfUploadZone.classList.remove('dragover')); pdfUploadZone.addEventListener('drop', e => { e.preventDefault(); pdfUploadZone.classList.remove('dragover'); if (e.dataTransfer.files.length) handlePDFFile(e.dataTransfer.files[0]); }); pdfFileInput.addEventListener('change', () => { if (pdfFileInput.files.length) handlePDFFile(pdfFileInput.files[0]); }); async function handlePDFFile(file) { pdfFile = file; pdfUploadZone.innerHTML = `${file.name}
${(file.size / 1024).toFixed(1)} KB -- extracting pages...
`; const form = new FormData(); form.append('file', file); try { const res = await fetch('/api/pdf/preview', { method: 'POST', body: form }); const data = await res.json(); if (data.error) { pdfUploadZone.innerHTML = `${data.error}
`; return; } document.getElementById('pdfFileName').textContent = data.filename; document.getElementById('pdfMeta').textContent = `${data.total_pages} pages | ${data.total_chars.toLocaleString()} characters`; // Set page range defaults document.getElementById('pdfPageStart').value = 1; document.getElementById('pdfPageStart').max = data.total_pages; document.getElementById('pdfPageEnd').value = data.total_pages; document.getElementById('pdfPageEnd').max = data.total_pages; // Render page previews const pagesEl = document.getElementById('pdfPages'); pagesEl.innerHTML = data.pages.map(p => `${file.name}
${data.total_pages} pages extracted
`; } catch (err) { pdfUploadZone.innerHTML = `Error: ${err.message}
`; } } async function ingestPDF() { if (!pdfFile) return; const pageStart = parseInt(document.getElementById('pdfPageStart').value) || 1; const pageEnd = parseInt(document.getElementById('pdfPageEnd').value) || 0; const form = new FormData(); form.append('file', pdfFile); form.append('page_start', pageStart); form.append('page_end', pageEnd); const el = document.getElementById('pdfResult'); el.classList.remove('hidden'); el.innerHTML = ''; try { const res = await fetch('/api/pdf/ingest', { method: 'POST', body: form }); const data = await res.json(); if (data.error) { el.innerHTML = ``; return; } el.innerHTML = `Ingested "${escapeHtml(data.title)}"
Page range: ${data.page_range} of ${data.total_pages} | Total in store: ${data.total_docs}
`; } catch (err) { el.innerHTML = ``; } } // ── Ask (chatbot) ── let chatHistory = []; async function askQuestion() { const input = document.getElementById('askInput'); const question = input.value.trim(); if (!question) return; const btn = document.getElementById('askBtn'); btn.disabled = true; input.value = ''; const messages = document.getElementById('chatMessages'); // Remove welcome message if present const welcome = messages.querySelector('.chat-welcome'); if (welcome) welcome.remove(); // Add user bubble const userBubble = document.createElement('div'); userBubble.className = 'chat-bubble user'; userBubble.textContent = question; messages.appendChild(userBubble); // Add thinking bubble const thinkBubble = document.createElement('div'); thinkBubble.className = 'chat-bubble thinking'; thinkBubble.innerHTML = 'Reading your documents'; messages.appendChild(thinkBubble); messages.scrollTop = messages.scrollHeight; try { const res = await fetch('/api/ask', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question, top_k: 3, max_tokens: 300 }), }); const data = await res.json(); // Remove thinking bubble thinkBubble.remove(); if (data.error) { const errBubble = document.createElement('div'); errBubble.className = 'chat-bubble bot'; errBubble.textContent = data.error; messages.appendChild(errBubble); } else { // Add bot answer bubble const botBubble = document.createElement('div'); botBubble.className = 'chat-bubble bot'; let sourcesHtml = ''; if (data.sources && data.sources.length > 0) { sourcesHtml = 'Ingested "${escapeHtml(data.title)}"
Chunks: ${data.chunks} | Characters: ${data.characters.toLocaleString()} | Total in store: ${data.total_docs}
`; } catch (err) { el.innerHTML = ``; } } async function ingestFile() { if (!selectedFile) return; const el = document.getElementById('ingestResult'); el.classList.remove('hidden'); el.innerHTML = ''; try { const form = new FormData(); form.append('file', selectedFile); const res = await fetch('/api/upload', { method: 'POST', body: form }); const data = await res.json(); if (data.error) { el.innerHTML = ``; return; } el.innerHTML = `Ingested "${escapeHtml(data.title)}"
Chunks: ${data.chunks} | Characters: ${data.characters.toLocaleString()} | Total in store: ${data.total_docs}
`; } catch (err) { el.innerHTML = ``; } } // ── Query with similarity bars ── async function queryDocs() { const question = document.getElementById('queryInput').value.trim(); const topK = parseInt(document.getElementById('topK').value) || 5; if (!question) return; const el = document.getElementById('queryResult'); el.classList.remove('hidden'); el.innerHTML = ''; try { const res = await fetch('/api/query', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question, top_k: topK }), }); const data = await res.json(); if (data.error) { el.innerHTML = ``; return; } let html = ``; for (const m of data.results) { const simPct = (m.similarity * 100).toFixed(1); const barColor = m.similarity > 0.7 ? '#34d399' : m.similarity > 0.4 ? '#fbbf24' : '#f87171'; html += `