Spaces:
Sleeping
Sleeping
// Global variables | |
let categories = []; | |
let documents = []; | |
let authToken = null; | |
let currentUser = null; | |
let currentView = 'list'; | |
// Initialize app | |
document.addEventListener('DOMContentLoaded', function() { | |
checkAuth(); | |
setupSidebar(); | |
}); | |
// Sidebar functionality | |
function setupSidebar() { | |
const sidebarToggle = document.getElementById('sidebarToggle'); | |
const sidebar = document.getElementById('sidebar'); | |
const mainContent = document.querySelector('.main-content'); | |
if (sidebarToggle) { | |
sidebarToggle.addEventListener('click', function() { | |
sidebar.classList.toggle('collapsed'); | |
mainContent.classList.toggle('sidebar-collapsed'); | |
}); | |
} | |
// Auto-collapse on mobile | |
if (window.innerWidth <= 768) { | |
sidebar.classList.add('collapsed'); | |
mainContent.classList.add('sidebar-collapsed'); | |
} | |
} | |
// 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; | |
} | |
// Enhanced tab management with sidebar highlighting | |
function showTab(tabName) { | |
// Hide all tabs | |
document.querySelectorAll('.tab-content').forEach(tab => { | |
tab.classList.remove('active'); | |
}); | |
// Remove active class from all sidebar links | |
document.querySelectorAll('.sidebar-nav .nav-link').forEach(link => { | |
link.classList.remove('active'); | |
}); | |
// Show selected tab | |
document.getElementById(tabName).classList.add('active'); | |
// Highlight active sidebar link | |
const activeLink = document.querySelector(`[onclick="showTab('${tabName}')"]`); | |
if (activeLink) { | |
activeLink.classList.add('active'); | |
} | |
// Load data for specific tabs | |
if (tabName === 'browse') { | |
loadCategories(); | |
loadAllDocuments(); | |
} else if (tabName === 'dashboard') { | |
loadStats(); | |
loadRecentActivity(); | |
createCategoryChart(); | |
} | |
} | |
// 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; | |
} | |
}); | |
}); | |
} | |
// Enhanced stats loading with sidebar display | |
async function loadStats() { | |
try { | |
const response = await authenticatedFetch('/api/stats'); | |
const stats = await response.json(); | |
// Dashboard stats | |
const dashboardStatsHtml = ` | |
<div class="col-md-4"> | |
<div class="card stat-card"> | |
<div class="card-body"> | |
<h3>${stats.total_categories}</h3> | |
<p><i class="fas fa-tags me-2"></i>Total Categories</p> | |
</div> | |
</div> | |
</div> | |
<div class="col-md-4"> | |
<div class="card stat-card"> | |
<div class="card-body"> | |
<h3>${stats.total_documents}</h3> | |
<p><i class="fas fa-file me-2"></i>Documents Archived</p> | |
</div> | |
</div> | |
</div> | |
<div class="col-md-4"> | |
<div class="card stat-card"> | |
<div class="card-body"> | |
<h3>35%</h3> | |
<p><i class="fas fa-percentage me-2"></i>Min Confidence</p> | |
</div> | |
</div> | |
</div> | |
`; | |
// Sidebar stats | |
const sidebarStatsHtml = ` | |
<div class="sidebar-stat"> | |
<h6>${stats.total_categories}</h6> | |
<small>Categories</small> | |
</div> | |
<div class="sidebar-stat"> | |
<h6>${stats.total_documents}</h6> | |
<small>Documents</small> | |
</div> | |
`; | |
document.getElementById('dashboardStats').innerHTML = dashboardStatsHtml; | |
document.getElementById('sidebarStats').innerHTML = sidebarStatsHtml; | |
} catch (error) { | |
console.error('Error loading stats:', error); | |
} | |
} | |
// Enhanced categories loading with sidebar display | |
async function loadCategories() { | |
try { | |
const response = await authenticatedFetch('/api/categories'); | |
const data = await response.json(); | |
categories = data.categories; | |
// Main category buttons | |
const buttonsHtml = ` | |
<button class="category-btn active" onclick="filterDocuments('all')"> | |
<i class="fas fa-th-large me-2"></i>All Documents | |
</button> | |
${categories.map(cat => ` | |
<button class="category-btn" onclick="filterDocuments('${cat}')"> | |
<i class="fas fa-folder me-2"></i>${cat} (${data.counts[cat] || 0}) | |
</button> | |
`).join('')} | |
`; | |
// Sidebar categories | |
const sidebarCategoriesHtml = categories.map(cat => ` | |
<div class="sidebar-category" onclick="filterDocuments('${cat}')"> | |
<div class="sidebar-category-icon"></div> | |
<span>${cat} (${data.counts[cat] || 0})</span> | |
</div> | |
`).join(''); | |
document.getElementById('categoryButtons').innerHTML = buttonsHtml; | |
document.getElementById('sidebarCategories').innerHTML = sidebarCategoriesHtml; | |
} 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); | |
} | |
} | |
// Enhanced document display with image preview | |
function displayDocuments(docs) { | |
const container = document.getElementById('documentsContainer'); | |
if (docs.length === 0) { | |
container.innerHTML = ` | |
<div class="text-center py-5"> | |
<i class="fas fa-folder-open fa-4x text-muted mb-3"></i> | |
<h4 class="text-muted">No documents found</h4> | |
<p class="text-muted">Upload some documents to get started</p> | |
</div> | |
`; | |
return; | |
} | |
const docsHtml = docs.map(doc => { | |
const similarityClass = doc.similarity >= 0.7 ? 'similarity-high' : | |
doc.similarity >= 0.5 ? 'similarity-medium' : 'similarity-low'; | |
const confidenceText = doc.similarity >= 0.7 ? 'High' : | |
doc.similarity >= 0.5 ? 'Medium' : 'Low'; | |
return ` | |
<div class="document-card"> | |
<img src="/api/document-preview/${doc.id}" | |
class="document-preview" | |
alt="${doc.original_filename}" | |
onclick="showDocumentPreview('${doc.id}', '${doc.original_filename}')" | |
onerror="this.src='data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2Y4ZjlmYSIvPgo8dGV4dCB4PSI1MCUiIHk9IjUwJSIgZm9udC1mYW1pbHk9IkFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBmaWxsPSIjNmM3NTdkIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSI+RG9jdW1lbnQ8L3RleHQ+Cjwvc3ZnPg=='"> | |
<div class="document-title">${doc.original_filename}</div> | |
<div class="document-meta"> | |
<i class="fas fa-tag me-1"></i> | |
<strong>${doc.category}</strong> | |
</div> | |
<div class="document-meta"> | |
<i class="fas fa-chart-line me-1"></i> | |
Confidence: <span class="similarity-badge ${similarityClass}"> | |
${(doc.similarity * 100).toFixed(1)}% ${confidenceText} | |
</span> | |
</div> | |
<div class="document-meta"> | |
<i class="fas fa-calendar me-1"></i> | |
${new Date(doc.upload_date).toLocaleDateString()} | |
</div> | |
<div class="mt-3"> | |
<button class="btn btn-primary btn-sm me-2" onclick="showDocumentPreview('${doc.id}', '${doc.original_filename}')"> | |
<i class="fas fa-eye me-1"></i>Preview | |
</button> | |
<button class="btn btn-danger btn-sm" onclick="deleteDocument('${doc.id}', '${doc.original_filename}')"> | |
<i class="fas fa-trash me-1"></i>Delete | |
</button> | |
</div> | |
</div> | |
`; | |
}).join(''); | |
container.innerHTML = `<div class="document-grid">${docsHtml}</div>`; | |
} | |
// Document preview functionality | |
function showDocumentPreview(documentId, filename) { | |
const modal = new bootstrap.Modal(document.getElementById('documentModal')); | |
const preview = document.getElementById('documentPreview'); | |
const details = document.getElementById('documentDetails'); | |
preview.src = `/api/document-preview/${documentId}`; | |
details.innerHTML = `<h6>${filename}</h6>`; | |
modal.show(); | |
} | |
// View toggle functionality | |
function toggleView(viewType) { | |
currentView = viewType; | |
// Update button states | |
document.querySelectorAll('.btn-group .btn').forEach(btn => { | |
btn.classList.remove('active'); | |
}); | |
event.target.classList.add('active'); | |
// Reload documents with new view | |
loadAllDocuments(); | |
} | |
// Create category distribution chart | |
function createCategoryChart() { | |
const ctx = document.getElementById('categoryChart'); | |
if (!ctx) return; | |
// Sample data - you can modify this to use real data | |
const data = { | |
labels: categories.slice(0, 5), // Show top 5 categories | |
datasets: [{ | |
label: 'Documents', | |
data: categories.slice(0, 5).map(() => Math.floor(Math.random() * 20) + 1), | |
backgroundColor: [ | |
'rgba(13, 110, 253, 0.8)', | |
'rgba(25, 135, 84, 0.8)', | |
'rgba(255, 193, 7, 0.8)', | |
'rgba(220, 53, 69, 0.8)', | |
'rgba(13, 202, 240, 0.8)' | |
], | |
borderColor: [ | |
'rgb(13, 110, 253)', | |
'rgb(25, 135, 84)', | |
'rgb(255, 193, 7)', | |
'rgb(220, 53, 69)', | |
'rgb(13, 202, 240)' | |
], | |
borderWidth: 2 | |
}] | |
}; | |
new Chart(ctx, { | |
type: 'doughnut', | |
data: data, | |
options: { | |
responsive: true, | |
plugins: { | |
legend: { | |
position: 'bottom', | |
} | |
} | |
} | |
}); | |
} | |
// Load recent activity | |
function loadRecentActivity() { | |
const recentActivity = document.getElementById('recentActivity'); | |
// Sample recent activity - you can modify this to use real data | |
const activities = [ | |
{ action: 'Document classified', item: 'passport.jpg', time: '2 min ago' }, | |
{ action: 'Category added', item: 'driver_license', time: '1 hour ago' }, | |
{ action: 'Document uploaded', item: 'certificate.pdf', time: '3 hours ago' } | |
]; | |
const activityHtml = activities.map(activity => ` | |
<div class="d-flex justify-content-between align-items-center mb-2 p-2 bg-light rounded"> | |
<div> | |
<small class="fw-bold">${activity.action}</small><br> | |
<small class="text-muted">${activity.item}</small> | |
</div> | |
<small class="text-muted">${activity.time}</small> | |
</div> | |
`).join(''); | |
recentActivity.innerHTML = activityHtml; | |
} | |
// 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(); | |
loadCategories(); | |
} else { | |
// Handle different error types | |
let errorMessage = result.error || result.detail || 'Classification failed'; | |
if (errorMessage.includes('No categories')) { | |
errorMessage = 'β οΈ Please add some document categories first before classifying documents.'; | |
} else if (errorMessage.includes('Failed to process')) { | |
errorMessage = 'β Could not process the uploaded file. Please ensure it\'s a valid image or PDF.'; | |
} else if (errorMessage.includes('JSON serializable')) { | |
errorMessage = 'π§ Processing error occurred. Please try again.'; | |
} | |
showResult(resultDiv, errorMessage, 'error'); | |
} | |
} catch (error) { | |
console.error('Classification error:', error); | |
showResult(resultDiv, 'β Network error: Please check your connection and try again.', '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 with advanced OCR...', 'info'); | |
try { | |
const response = await authenticatedFetch('/api/ocr', { | |
method: 'POST', | |
body: formData | |
}); | |
const result = await response.json(); | |
if (response.ok) { | |
const ocrInfo = result.enhanced_features ? | |
`π€ Processed with ${result.ocr_method} (Enhanced Features: Tables, LaTeX, Watermarks)\n\n` : | |
`π Processed with ${result.ocr_method}\n\n`; | |
showResult(resultDiv, ocrInfo + result.text, 'success'); | |
} else { | |
showResult(resultDiv, result.error || 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>`; | |
} | |