106 lines
3.4 KiB
JavaScript
106 lines
3.4 KiB
JavaScript
// Document upload and list management
|
|
const DOC_UI = {
|
|
async init() {
|
|
$("#doc-upload").addEventListener("change", (e) => this.handleUpload(e));
|
|
|
|
// Periodically refresh document list
|
|
setInterval(() => this.refreshList(), 30000);
|
|
},
|
|
|
|
async handleUpload(e) {
|
|
const file = e.target.files[0];
|
|
if (!file) return;
|
|
|
|
const output = $("#research-output");
|
|
if (!output.querySelector(".empty-state")) output.innerHTML = "";
|
|
|
|
output.innerHTML = '<div class="spinner"></div> <span class="typing">Reading file...</span>';
|
|
|
|
const formData = new FormData();
|
|
formData.append("file", file);
|
|
|
|
const res = await fetch("/api/documents/upload", { method: "POST", body: formData });
|
|
const data = await res.json();
|
|
|
|
if (res.ok) {
|
|
showToast(`Document indexed: ${data.chunks || "unknown"} chunks`, "info");
|
|
STORE.documents = [];
|
|
await this.refreshList();
|
|
|
|
// Select the new document
|
|
if (data.doc_id) {
|
|
STORE.currentDocId = data.doc_id;
|
|
this.renderDocList();
|
|
$("#viewer-doc-name").textContent = file.name;
|
|
}
|
|
} else {
|
|
output.innerHTML = `<p style="color:var(--accent-red)">Upload failed: ${data.detail || data.error || "unknown"}</p>`;
|
|
}
|
|
|
|
e.target.value = "";
|
|
},
|
|
|
|
async refreshList() {
|
|
try {
|
|
const res = await API.get("/api/documents");
|
|
STORE.documents = res.documents || [];
|
|
this.renderDocList();
|
|
|
|
// Update stats
|
|
const stats = $("#sidebar-stats");
|
|
const total = STORE.documents.length;
|
|
const indexed = STORE.documents.filter(d => d.status === "indexed").length;
|
|
stats.textContent = `${indexed}/${total} documents indexed`;
|
|
} catch (e) {
|
|
console.warn("Doc list refresh failed:", e);
|
|
}
|
|
},
|
|
|
|
async selectDoc(docId) {
|
|
STORE.currentDocId = docId;
|
|
this.renderDocList();
|
|
|
|
// Load into viewer
|
|
if (STORE.viewerDoc) {
|
|
DOC_VIEWER.loadDocument(docId);
|
|
}
|
|
|
|
// Switch to viewer tab
|
|
$$(".tab").forEach(t => t.classList.remove("active"));
|
|
$$(".tab-pane").forEach(p => p.classList.remove("active"));
|
|
document.querySelector('[data-tab="viewer"]').classList.add("active");
|
|
$("#tab-viewer").classList.add("active");
|
|
},
|
|
|
|
renderDocList() {
|
|
const list = $("#doc-list");
|
|
if (!list) return;
|
|
|
|
if (!STORE.documents.length) {
|
|
list.innerHTML = '<div style="font-size:12px;color:var(--text-muted);padding:8px">No documents</div>';
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = STORE.documents.map(doc => {
|
|
const ext = doc.filename.split(".").pop().toLowerCase();
|
|
const icons = { pdf: "📄", txt: "📝", md: "📋", doc: "📑" };
|
|
const icon = icons[ext] || "📄";
|
|
const isActive = doc.id === STORE.currentDocId;
|
|
return `
|
|
<div class="doc-item ${isActive ? 'active' : ''}" data-doc-id="${doc.id}">
|
|
<span class="file-icon">${icon}</span>
|
|
<span class="file-name" title="${doc.filename}">${doc.filename.length > 25 ? doc.filename.substring(0, 25) + '...' : doc.filename}</span>
|
|
<span class="file-status ${doc.status}">${doc.status.charAt(0).toUpperCase() + doc.status.slice(1)}</span>
|
|
</div>`;
|
|
}).join('');
|
|
|
|
// Click handlers
|
|
list.querySelectorAll(".doc-item").forEach(item => {
|
|
item.addEventListener("click", (e) => {
|
|
if (e.target.closest(".file-remove")) return;
|
|
this.selectDoc(item.dataset.docId);
|
|
});
|
|
});
|
|
},
|
|
};
|