Spaces:
Sleeping
Sleeping
// Global variables | |
let categories = []; | |
let documents = []; | |
let authToken = null; | |
let currentUser = null; | |
// Initialize app | |
document.addEventListener('DOMContentLoaded', function() { | |
checkAuth(); | |
}); | |
// Authentication functions | |
function checkAuth() { | |
authToken = localStorage.getItem('authToken'); | |
currentUser = localStorage.getItem('currentUser'); | |
if (authToken && currentUser) { | |
showMainApp(); | |
document.getElementById('welcomeUser').textContent = `Welcome, ${currentUser}`; | |
loadStats(); | |
loadCategories(); | |
setupFileUploads(); | |
} else { | |
showLoginModal(); | |
} | |
} | |
function showLoginModal() { | |
document.getElementById('loginModal').style.display = 'flex'; | |
document.getElementById('mainApp').style.display = 'none'; | |
} | |
function showMainApp() { | |
document.getElementById('loginModal').style.display = 'none'; | |
document.getElementById('mainApp').style.display = 'block'; | |
} | |
function logout() { | |
localStorage.removeItem('authToken'); | |
localStorage.removeItem('currentUser'); | |
authToken = null; | |
currentUser = null; | |
showLoginModal(); | |
} | |
// Login form handler | |
document.getElementById('loginForm').addEventListener('submit', async (e) => { | |
e.preventDefault(); | |
const username = document.getElementById('username').value; | |
const password = document.getElementById('password').value; | |
const resultDiv = document.getElementById('loginResult'); | |
const formData = new FormData(); | |
formData.append('username', username); | |
formData.append('password', password); | |
try { | |
const response = await fetch('/api/login', { | |
method: 'POST', | |
body: formData | |
}); | |
const result = await response.json(); | |
if (response.ok) { | |
authToken = result.access_token; | |
currentUser = result.username; | |
localStorage.setItem('authToken', authToken); | |
localStorage.setItem('currentUser', currentUser); | |
showMainApp(); | |
document.getElementById('welcomeUser').textContent = `Welcome, ${currentUser}`; | |
loadStats(); | |
loadCategories(); | |
setupFileUploads(); | |
} else { | |
showResult(resultDiv, result.detail, 'error'); | |
} | |
} catch (error) { | |
showResult(resultDiv, 'Login failed: ' + error.message, 'error'); | |
} | |
}); | |
// API request with authentication | |
async function authenticatedFetch(url, options = {}) { | |
if (!authToken) { | |
throw new Error('No authentication token'); | |
} | |
const defaultOptions = { | |
headers: { | |
'Authorization': `Bearer ${authToken}`, | |
...options.headers | |
} | |
}; | |
const response = await fetch(url, { ...options, ...defaultOptions }); | |
if (response.status === 401) { | |
logout(); | |
throw new Error('Authentication failed'); | |
} | |
return response; | |
} | |
// Tab management | |
function showTab(tabName) { | |
// Hide all tabs | |
document.querySelectorAll('.tab-content').forEach(tab => { | |
tab.classList.remove('active'); | |
}); | |
document.querySelectorAll('.tab-button').forEach(btn => { | |
btn.classList.remove('active'); | |
}); | |
// Show selected tab | |
document.getElementById(tabName).classList.add('active'); | |
event.target.classList.add('active'); | |
// Load data for specific tabs | |
if (tabName === 'browse') { | |
loadCategories(); | |
loadAllDocuments(); | |
} | |
} | |
// Setup file upload drag & drop | |
function setupFileUploads() { | |
const uploads = [ | |
{ div: 'categoryUpload', input: 'categoryFile' }, | |
{ div: 'classifyUpload', input: 'classifyFile' }, | |
{ div: 'ocrUpload', input: 'ocrFile' } | |
]; | |
uploads.forEach(upload => { | |
const uploadDiv = document.getElementById(upload.div); | |
const fileInput = document.getElementById(upload.input); | |
uploadDiv.addEventListener('click', () => fileInput.click()); | |
uploadDiv.addEventListener('dragover', (e) => { | |
e.preventDefault(); | |
uploadDiv.classList.add('dragover'); | |
}); | |
uploadDiv.addEventListener('dragleave', () => { | |
uploadDiv.classList.remove('dragover'); | |
}); | |
uploadDiv.addEventListener('drop', (e) => { | |
e.preventDefault(); | |
uploadDiv.classList.remove('dragover'); | |
const files = e.dataTransfer.files; | |
if (files.length > 0) { | |
fileInput.files = files; | |
uploadDiv.querySelector('p').textContent = files[0].name; | |
} | |
}); | |
fileInput.addEventListener('change', () => { | |
if (fileInput.files.length > 0) { | |
uploadDiv.querySelector('p').textContent = fileInput.files[0].name; | |
} | |
}); | |
}); | |
} | |
// Load dashboard stats | |
async function loadStats() { | |
try { | |
const response = await authenticatedFetch('/api/stats'); | |
const stats = await response.json(); | |
const statsHtml = ` | |
<div class="stat-card"> | |
<h3>${stats.total_categories}</h3> | |
<p><i class="fas fa-tags"></i> Total Categories</p> | |
</div> | |
<div class="stat-card"> | |
<h3>${stats.total_documents}</h3> | |
<p><i class="fas fa-file"></i> Documents Archived</p> | |
</div> | |
<div class="stat-card"> | |
<h3>35%</h3> | |
<p><i class="fas fa-percentage"></i> Min Confidence</p> | |
</div> | |
`; | |
document.getElementById('stats').innerHTML = statsHtml; | |
} catch (error) { | |
console.error('Error loading stats:', error); | |
} | |
} | |
// Load categories | |
async function loadCategories() { | |
try { | |
const response = await authenticatedFetch('/api/categories'); | |
const data = await response.json(); | |
categories = data.categories; | |
const buttonsHtml = ` | |
<button class="category-btn active" onclick="filterDocuments('all')"> | |
All Documents | |
</button> | |
${categories.map(cat => ` | |
<button class="category-btn" onclick="filterDocuments('${cat}')"> | |
${cat} (${data.counts[cat] || 0}) | |
</button> | |
`).join('')} | |
`; | |
document.getElementById('categoryButtons').innerHTML = buttonsHtml; | |
} catch (error) { | |
console.error('Error loading categories:', error); | |
} | |
} | |
// Load all documents | |
async function loadAllDocuments() { | |
try { | |
const response = await authenticatedFetch('/api/documents'); | |
const data = await response.json(); | |
documents = data.documents; | |
displayDocuments(documents); | |
} catch (error) { | |
console.error('Error loading documents:', error); | |
} | |
} | |
// Filter documents by category | |
async function filterDocuments(category) { | |
// Update active button | |
document.querySelectorAll('.category-btn').forEach(btn => { | |
btn.classList.remove('active'); | |
}); | |
event.target.classList.add('active'); | |
try { | |
let filteredDocs; | |
if (category === 'all') { | |
const response = await authenticatedFetch('/api/documents'); | |
const data = await response.json(); | |
filteredDocs = data.documents; | |
} else { | |
const response = await authenticatedFetch(`/api/documents/${category}`); | |
const data = await response.json(); | |
filteredDocs = data.documents; | |
} | |
displayDocuments(filteredDocs); | |
} catch (error) { | |
console.error('Error filtering documents:', error); | |
} | |
} | |
// Delete document | |
async function deleteDocument(documentId, filename) { | |
if (!confirm(`Are you sure you want to delete "${filename}"? This action cannot be undone.`)) { | |
return; | |
} | |
try { | |
const response = await authenticatedFetch(`/api/documents/${documentId}`, { | |
method: 'DELETE' | |
}); | |
const result = await response.json(); | |
if (response.ok) { | |
// Refresh the current view | |
loadAllDocuments(); | |
loadStats(); | |
loadCategories(); | |
alert('Document deleted successfully'); | |
} else { | |
alert('Failed to delete document: ' + result.detail); | |
} | |
} catch (error) { | |
alert('Error deleting document: ' + error.message); | |
} | |
} | |
// Display documents | |
function displayDocuments(docs) { | |
const container = document.getElementById('documentsContainer'); | |
if (docs.length === 0) { | |
container.innerHTML = '<p>No documents found for this category.</p>'; | |
return; | |
} | |
const docsHtml = docs.map(doc => { | |
const similarityClass = doc.similarity >= 0.7 ? 'similarity-high' : | |
doc.similarity >= 0.5 ? 'similarity-medium' : 'similarity-low'; | |
return ` | |
<div class="document-card"> | |
<h4><i class="fas fa-file"></i> ${doc.original_filename}</h4> | |
<p><strong>Category:</strong> ${doc.category}</p> | |
<p><strong>Confidence:</strong> | |
<span class="similarity-badge ${similarityClass}"> | |
${(doc.similarity * 100).toFixed(1)}% | |
</span> | |
</p> | |
<p><strong>Upload Date:</strong> ${new Date(doc.upload_date).toLocaleDateString()}</p> | |
<p><strong>OCR Preview:</strong></p> | |
<div style="max-height: 100px; overflow-y: auto; background: #f8f9fa; padding: 0.5rem; border-radius: 4px; font-size: 0.8rem;"> | |
${doc.ocr_text.substring(0, 200)}${doc.ocr_text.length > 200 ? '...' : ''} | |
</div> | |
<div class="document-actions"> | |
<button class="btn btn-danger" onclick="deleteDocument('${doc.id}', '${doc.original_filename}')"> | |
<i class="fas fa-trash"></i> Delete | |
</button> | |
</div> | |
</div> | |
`; | |
}).join(''); | |
container.innerHTML = `<div class="document-grid">${docsHtml}</div>`; | |
} | |
// Form submissions | |
document.getElementById('uploadForm').addEventListener('submit', async (e) => { | |
e.preventDefault(); | |
const fileInput = document.getElementById('categoryFile'); | |
const labelInput = document.getElementById('categoryLabel'); | |
const resultDiv = document.getElementById('uploadResult'); | |
if (!fileInput.files[0] || !labelInput.value.trim()) { | |
showResult(resultDiv, 'Please select a file and enter a label.', 'error'); | |
return; | |
} | |
const formData = new FormData(); | |
formData.append('file', fileInput.files[0]); | |
formData.append('label', labelInput.value.trim()); | |
showResult(resultDiv, '<div class="loading"></div> Uploading...', 'info'); | |
try { | |
const response = await authenticatedFetch('/api/upload-category', { | |
method: 'POST', | |
body: formData | |
}); | |
const result = await response.json(); | |
if (response.ok) { | |
showResult(resultDiv, result.message, 'success'); | |
labelInput.value = ''; | |
fileInput.value = ''; | |
document.querySelector('#categoryUpload p').textContent = 'Click to select or drag & drop files here'; | |
loadStats(); | |
loadCategories(); | |
} else { | |
showResult(resultDiv, result.detail, 'error'); | |
} | |
} catch (error) { | |
showResult(resultDiv, 'Upload failed: ' + error.message, 'error'); | |
} | |
}); | |
document.getElementById('classifyForm').addEventListener('submit', async (e) => { | |
e.preventDefault(); | |
const fileInput = document.getElementById('classifyFile'); | |
const resultDiv = document.getElementById('classifyResult'); | |
if (!fileInput.files[0]) { | |
showResult(resultDiv, 'Please select a file to classify.', 'error'); | |
return; | |
} | |
const formData = new FormData(); | |
formData.append('file', fileInput.files[0]); | |
showResult(resultDiv, '<div class="loading"></div> Classifying...', 'info'); | |
try { | |
const response = await authenticatedFetch('/api/classify-document', { | |
method: 'POST', | |
body: formData | |
}); | |
const result = await response.json(); | |
if (response.ok) { | |
const confidenceText = result.confidence === 'high' ? 'β High Confidence' : 'β οΈ Low Confidence'; | |
const savedText = result.document_saved ? '\nπ Document saved to archive' : ''; | |
let matchesText = '\n\nTop matches:\n'; | |
result.matches.forEach(match => { | |
matchesText += `β’ ${match.category}: ${(match.similarity * 100).toFixed(1)}%\n`; | |
}); | |
showResult(resultDiv, | |
`π― Classification: ${result.category}\n` + | |
`${confidenceText} (${(result.similarity * 100).toFixed(1)}%)${savedText}${matchesText}`, | |
result.confidence === 'high' ? 'success' : 'warning' | |
); | |
fileInput.value = ''; | |
document.querySelector('#classifyUpload p').textContent = 'Click to select or drag & drop files here'; | |
loadStats(); | |
} else { | |
showResult(resultDiv, result.detail, 'error'); | |
} | |
} catch (error) { | |
showResult(resultDiv, 'Classification failed: ' + error.message, 'error'); | |
} | |
}); | |
document.getElementById('ocrForm').addEventListener('submit', async (e) => { | |
e.preventDefault(); | |
const fileInput = document.getElementById('ocrFile'); | |
const resultDiv = document.getElementById('ocrResult'); | |
if (!fileInput.files[0]) { | |
showResult(resultDiv, 'Please select a file for OCR.', 'error'); | |
return; | |
} | |
const formData = new FormData(); | |
formData.append('file', fileInput.files[0]); | |
showResult(resultDiv, '<div class="loading"></div> Extracting text...', 'info'); | |
try { | |
const response = await authenticatedFetch('/api/ocr', { | |
method: 'POST', | |
body: formData | |
}); | |
const result = await response.json(); | |
if (response.ok) { | |
showResult(resultDiv, result.text, 'success'); | |
} else { | |
showResult(resultDiv, result.detail, 'error'); | |
} | |
} catch (error) { | |
showResult(resultDiv, 'OCR failed: ' + error.message, 'error'); | |
} | |
}); | |
// Utility function to show results | |
function showResult(element, message, type) { | |
const className = type === 'success' ? 'result-success' : | |
type === 'error' ? 'result-error' : | |
type === 'warning' ? 'result-warning' : ''; | |
element.innerHTML = `<div class="result-box ${className}">${message}</div>`; | |
} | |