IZERE HIRWA Roger commited on
Commit
a457084
·
1 Parent(s): 5753ed4
Files changed (4) hide show
  1. app.py +36 -0
  2. static/index.html +132 -40
  3. static/script.js +220 -34
  4. static/styles.css +286 -252
app.py CHANGED
@@ -481,5 +481,41 @@ def get_stats():
481
  "category_distribution": category_stats
482
  })
483
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  if __name__ == "__main__":
485
  app.run(host="0.0.0.0", port=7860, debug=True)
 
481
  "category_distribution": category_stats
482
  })
483
 
484
+ @app.route("/api/document-preview/<document_id>", methods=["GET"])
485
+ def get_document_preview(document_id):
486
+ # Verify token
487
+ auth_header = request.headers.get('Authorization')
488
+ if not auth_header or not auth_header.startswith('Bearer '):
489
+ # For image requests, try to get token from query params as fallback
490
+ token = request.args.get('token')
491
+ if not token:
492
+ return jsonify({"error": "Missing or invalid token"}), 401
493
+ username = verify_token(token)
494
+ else:
495
+ token = auth_header.split(' ')[1]
496
+ username = verify_token(token)
497
+
498
+ if not username:
499
+ return jsonify({"error": "Invalid token"}), 401
500
+
501
+ try:
502
+ conn = sqlite3.connect(DATABASE_PATH)
503
+ cursor = conn.cursor()
504
+ cursor.execute('SELECT file_path FROM documents WHERE id = ?', (document_id,))
505
+ result = cursor.fetchone()
506
+ conn.close()
507
+
508
+ if not result:
509
+ return jsonify({"error": "Document not found"}), 404
510
+
511
+ file_path = result[0]
512
+
513
+ if not os.path.exists(file_path):
514
+ return jsonify({"error": "File not found"}), 404
515
+
516
+ return send_from_directory(os.path.dirname(file_path), os.path.basename(file_path))
517
+ except Exception as e:
518
+ return jsonify({"error": str(e)}), 500
519
+
520
  if __name__ == "__main__":
521
  app.run(host="0.0.0.0", port=7860, debug=True)
static/index.html CHANGED
@@ -47,9 +47,12 @@
47
 
48
  <!-- Main Application -->
49
  <div id="mainApp" style="display: none;">
50
- <!-- Navigation Header -->
51
- <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
52
- <div class="container">
 
 
 
53
  <a class="navbar-brand" href="#">
54
  <i class="fas fa-archive me-2"></i>Archive Digitalization System
55
  </a>
@@ -62,45 +65,104 @@
62
  </div>
63
  </nav>
64
 
65
- <div class="container mt-4">
66
- <!-- Dashboard Stats -->
67
- <div class="row mb-4" id="stats">
 
 
 
 
 
68
  <!-- Stats will be loaded here -->
69
  </div>
70
 
71
- <!-- Navigation Tabs -->
72
- <ul class="nav nav-tabs mb-4" id="mainTabs" role="tablist">
73
- <li class="nav-item" role="presentation">
74
- <button class="nav-link active" onclick="showTab('upload')" type="button">
 
 
 
 
75
  <i class="fas fa-upload me-2"></i>Upload Categories
76
- </button>
77
  </li>
78
- <li class="nav-item" role="presentation">
79
- <button class="nav-link" onclick="showTab('classify')" type="button">
80
  <i class="fas fa-search me-2"></i>Classify Documents
81
- </button>
82
  </li>
83
- <li class="nav-item" role="presentation">
84
- <button class="nav-link" onclick="showTab('browse')" type="button">
85
  <i class="fas fa-folder-open me-2"></i>Browse Archive
86
- </button>
87
  </li>
88
- <li class="nav-item" role="presentation">
89
- <button class="nav-link" onclick="showTab('ocr')" type="button">
90
  <i class="fas fa-eye me-2"></i>OCR Text
91
- </button>
92
  </li>
93
  </ul>
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  <!-- Upload Categories Tab -->
96
- <div id="upload" class="tab-content active">
 
 
 
 
 
97
  <div class="card">
98
- <div class="card-header bg-primary text-white">
99
- <h4><i class="fas fa-tags me-2"></i>Upload Document Categories</h4>
100
- </div>
101
  <div class="card-body">
102
- <p class="text-muted">Upload sample documents for each category to train the classification system.</p>
103
-
104
  <form id="uploadForm" class="row g-3">
105
  <div class="col-md-6">
106
  <label for="categoryFile" class="form-label">Document File (Image or PDF)</label>
@@ -114,9 +176,8 @@
114
  <label for="categoryLabel" class="form-label">Category Label</label>
115
  <input type="text" id="categoryLabel" class="form-control" placeholder="e.g., birth_certificate, passport, diploma">
116
  <div class="form-text">Use descriptive names without spaces</div>
117
- </div>
118
- <div class="col-12">
119
- <button type="submit" class="btn btn-primary">
120
  <i class="fas fa-plus me-2"></i>Add Category
121
  </button>
122
  </div>
@@ -159,22 +220,35 @@
159
 
160
  <!-- Browse Archive Tab -->
161
  <div id="browse" class="tab-content">
162
- <div class="card">
163
- <div class="card-header bg-info text-white">
164
- <h4><i class="fas fa-archive me-2"></i>Browse Document Archive</h4>
165
- </div>
 
 
 
166
  <div class="card-body">
167
- <p class="text-muted">Browse and search through your classified documents by category.</p>
168
-
169
- <div class="mb-3" id="categoryButtons">
170
- <!-- Category buttons will be loaded here -->
 
 
 
 
 
 
171
  </div>
172
-
173
- <div id="documentsContainer">
174
- <!-- Documents will be loaded here -->
175
  </div>
176
  </div>
177
  </div>
 
 
 
 
 
178
  </div>
179
 
180
  <!-- OCR Text Tab -->
@@ -206,11 +280,29 @@
206
  </div>
207
  </div>
208
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  </div>
210
  </div>
211
 
212
  <!-- Bootstrap JS -->
213
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
 
 
214
  <!-- Custom JS -->
215
  <script src="/static/script.js"></script>
216
  </body>
 
47
 
48
  <!-- Main Application -->
49
  <div id="mainApp" style="display: none;">
50
+ <!-- Top Navigation -->
51
+ <nav class="navbar navbar-expand-lg navbar-dark bg-primary fixed-top">
52
+ <div class="container-fluid">
53
+ <button class="btn btn-primary me-3" type="button" id="sidebarToggle">
54
+ <i class="fas fa-bars"></i>
55
+ </button>
56
  <a class="navbar-brand" href="#">
57
  <i class="fas fa-archive me-2"></i>Archive Digitalization System
58
  </a>
 
65
  </div>
66
  </nav>
67
 
68
+ <!-- Sidebar -->
69
+ <nav id="sidebar" class="sidebar">
70
+ <div class="sidebar-header">
71
+ <h4><i class="fas fa-archive me-2"></i>Archive System</h4>
72
+ </div>
73
+
74
+ <!-- Dashboard Stats in Sidebar -->
75
+ <div class="sidebar-stats" id="sidebarStats">
76
  <!-- Stats will be loaded here -->
77
  </div>
78
 
79
+ <ul class="sidebar-nav">
80
+ <li class="nav-item">
81
+ <a href="#" class="nav-link active" onclick="showTab('dashboard')">
82
+ <i class="fas fa-chart-dashboard me-2"></i>Dashboard
83
+ </a>
84
+ </li>
85
+ <li class="nav-item">
86
+ <a href="#" class="nav-link" onclick="showTab('upload')">
87
  <i class="fas fa-upload me-2"></i>Upload Categories
88
+ </a>
89
  </li>
90
+ <li class="nav-item">
91
+ <a href="#" class="nav-link" onclick="showTab('classify')">
92
  <i class="fas fa-search me-2"></i>Classify Documents
93
+ </a>
94
  </li>
95
+ <li class="nav-item">
96
+ <a href="#" class="nav-link" onclick="showTab('browse')">
97
  <i class="fas fa-folder-open me-2"></i>Browse Archive
98
+ </a>
99
  </li>
100
+ <li class="nav-item">
101
+ <a href="#" class="nav-link" onclick="showTab('ocr')">
102
  <i class="fas fa-eye me-2"></i>OCR Text
103
+ </a>
104
  </li>
105
  </ul>
106
 
107
+ <!-- Category List in Sidebar -->
108
+ <div class="sidebar-section">
109
+ <h6 class="sidebar-heading">Categories</h6>
110
+ <div id="sidebarCategories">
111
+ <!-- Categories will be loaded here -->
112
+ </div>
113
+ </div>
114
+ </nav>
115
+
116
+ <!-- Main Content -->
117
+ <main class="main-content">
118
+ <!-- Dashboard Tab -->
119
+ <div id="dashboard" class="tab-content active">
120
+ <div class="content-header">
121
+ <h2><i class="fas fa-chart-dashboard me-2"></i>Dashboard</h2>
122
+ <p class="text-muted">Overview of your document archive system</p>
123
+ </div>
124
+
125
+ <!-- Stats Cards -->
126
+ <div class="row mb-4" id="dashboardStats">
127
+ <!-- Stats will be loaded here -->
128
+ </div>
129
+
130
+ <!-- Category Overview -->
131
+ <div class="row">
132
+ <div class="col-md-8">
133
+ <div class="card">
134
+ <div class="card-header">
135
+ <h5><i class="fas fa-chart-bar me-2"></i>Document Distribution</h5>
136
+ </div>
137
+ <div class="card-body">
138
+ <canvas id="categoryChart" width="400" height="200"></canvas>
139
+ </div>
140
+ </div>
141
+ </div>
142
+ <div class="col-md-4">
143
+ <div class="card">
144
+ <div class="card-header">
145
+ <h5><i class="fas fa-clock me-2"></i>Recent Activity</h5>
146
+ </div>
147
+ <div class="card-body">
148
+ <div id="recentActivity">
149
+ <!-- Recent activity will be loaded here -->
150
+ </div>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </div>
156
+
157
  <!-- Upload Categories Tab -->
158
+ <div id="upload" class="tab-content">
159
+ <div class="content-header">
160
+ <h2><i class="fas fa-tags me-2"></i>Upload Document Categories</h2>
161
+ <p class="text-muted">Train the system by uploading sample documents for each category</p>
162
+ </div>
163
+
164
  <div class="card">
 
 
 
165
  <div class="card-body">
 
 
166
  <form id="uploadForm" class="row g-3">
167
  <div class="col-md-6">
168
  <label for="categoryFile" class="form-label">Document File (Image or PDF)</label>
 
176
  <label for="categoryLabel" class="form-label">Category Label</label>
177
  <input type="text" id="categoryLabel" class="form-control" placeholder="e.g., birth_certificate, passport, diploma">
178
  <div class="form-text">Use descriptive names without spaces</div>
179
+
180
+ <button type="submit" class="btn btn-primary mt-3">
 
181
  <i class="fas fa-plus me-2"></i>Add Category
182
  </button>
183
  </div>
 
220
 
221
  <!-- Browse Archive Tab -->
222
  <div id="browse" class="tab-content">
223
+ <div class="content-header">
224
+ <h2><i class="fas fa-archive me-2"></i>Browse Document Archive</h2>
225
+ <p class="text-muted">Browse and search through your classified documents by category</p>
226
+ </div>
227
+
228
+ <!-- Category Filters -->
229
+ <div class="card mb-4">
230
  <div class="card-body">
231
+ <div class="d-flex justify-content-between align-items-center mb-3">
232
+ <h5 class="mb-0">Filter by Category</h5>
233
+ <div class="btn-group" role="group">
234
+ <button type="button" class="btn btn-outline-primary btn-sm" onclick="toggleView('grid')">
235
+ <i class="fas fa-th-large"></i> Grid
236
+ </button>
237
+ <button type="button" class="btn btn-outline-primary btn-sm active" onclick="toggleView('list')">
238
+ <i class="fas fa-list"></i> List
239
+ </button>
240
+ </div>
241
  </div>
242
+ <div id="categoryButtons" class="category-buttons">
243
+ <!-- Category buttons will be loaded here -->
 
244
  </div>
245
  </div>
246
  </div>
247
+
248
+ <!-- Documents Container -->
249
+ <div id="documentsContainer">
250
+ <!-- Documents will be loaded here -->
251
+ </div>
252
  </div>
253
 
254
  <!-- OCR Text Tab -->
 
280
  </div>
281
  </div>
282
  </div>
283
+ </main>
284
+ </div>
285
+
286
+ <!-- Document Preview Modal -->
287
+ <div class="modal fade" id="documentModal" tabindex="-1">
288
+ <div class="modal-dialog modal-lg">
289
+ <div class="modal-content">
290
+ <div class="modal-header">
291
+ <h5 class="modal-title">Document Preview</h5>
292
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
293
+ </div>
294
+ <div class="modal-body text-center">
295
+ <img id="documentPreview" class="img-fluid mb-3" style="max-height: 400px;">
296
+ <div id="documentDetails"></div>
297
+ </div>
298
+ </div>
299
  </div>
300
  </div>
301
 
302
  <!-- Bootstrap JS -->
303
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
304
+ <!-- Chart.js -->
305
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
306
  <!-- Custom JS -->
307
  <script src="/static/script.js"></script>
308
  </body>
static/script.js CHANGED
@@ -3,12 +3,34 @@ let categories = [];
3
  let documents = [];
4
  let authToken = null;
5
  let currentUser = null;
 
6
 
7
  // Initialize app
8
  document.addEventListener('DOMContentLoaded', function() {
9
  checkAuth();
 
10
  });
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  // Authentication functions
13
  function checkAuth() {
14
  authToken = localStorage.getItem('authToken');
@@ -105,24 +127,35 @@ async function authenticatedFetch(url, options = {}) {
105
  return response;
106
  }
107
 
108
- // Tab management
109
  function showTab(tabName) {
110
  // Hide all tabs
111
  document.querySelectorAll('.tab-content').forEach(tab => {
112
  tab.classList.remove('active');
113
  });
114
- document.querySelectorAll('.tab-button').forEach(btn => {
115
- btn.classList.remove('active');
 
 
116
  });
117
 
118
  // Show selected tab
119
  document.getElementById(tabName).classList.add('active');
120
- event.target.classList.add('active');
 
 
 
 
 
121
 
122
  // Load data for specific tabs
123
  if (tabName === 'browse') {
124
  loadCategories();
125
  loadAllDocuments();
 
 
 
 
126
  }
127
  }
128
 
@@ -167,52 +200,88 @@ function setupFileUploads() {
167
  });
168
  }
169
 
170
- // Load dashboard stats
171
  async function loadStats() {
172
  try {
173
  const response = await authenticatedFetch('/api/stats');
174
  const stats = await response.json();
175
 
176
- const statsHtml = `
177
- <div class="stat-card">
178
- <h3>${stats.total_categories}</h3>
179
- <p><i class="fas fa-tags"></i> Total Categories</p>
 
 
 
 
 
180
  </div>
181
- <div class="stat-card">
182
- <h3>${stats.total_documents}</h3>
183
- <p><i class="fas fa-file"></i> Documents Archived</p>
 
 
 
 
184
  </div>
185
- <div class="stat-card">
186
- <h3>35%</h3>
187
- <p><i class="fas fa-percentage"></i> Min Confidence</p>
 
 
 
 
188
  </div>
189
  `;
190
 
191
- document.getElementById('stats').innerHTML = statsHtml;
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  } catch (error) {
193
  console.error('Error loading stats:', error);
194
  }
195
  }
196
 
197
- // Load categories
198
  async function loadCategories() {
199
  try {
200
  const response = await authenticatedFetch('/api/categories');
201
  const data = await response.json();
202
  categories = data.categories;
203
 
 
204
  const buttonsHtml = `
205
  <button class="category-btn active" onclick="filterDocuments('all')">
206
- All Documents
207
  </button>
208
  ${categories.map(cat => `
209
  <button class="category-btn" onclick="filterDocuments('${cat}')">
210
- ${cat} (${data.counts[cat] || 0})
211
  </button>
212
  `).join('')}
213
  `;
214
 
 
 
 
 
 
 
 
 
215
  document.getElementById('categoryButtons').innerHTML = buttonsHtml;
 
216
  } catch (error) {
217
  console.error('Error loading categories:', error);
218
  }
@@ -283,12 +352,18 @@ async function deleteDocument(documentId, filename) {
283
  }
284
  }
285
 
286
- // Display documents
287
  function displayDocuments(docs) {
288
  const container = document.getElementById('documentsContainer');
289
 
290
  if (docs.length === 0) {
291
- container.innerHTML = '<p>No documents found for this category.</p>';
 
 
 
 
 
 
292
  return;
293
  }
294
 
@@ -296,23 +371,42 @@ function displayDocuments(docs) {
296
  const similarityClass = doc.similarity >= 0.7 ? 'similarity-high' :
297
  doc.similarity >= 0.5 ? 'similarity-medium' : 'similarity-low';
298
 
 
 
 
299
  return `
300
  <div class="document-card">
301
- <h4><i class="fas fa-file"></i> ${doc.original_filename}</h4>
302
- <p><strong>Category:</strong> ${doc.category}</p>
303
- <p><strong>Confidence:</strong>
304
- <span class="similarity-badge ${similarityClass}">
305
- ${(doc.similarity * 100).toFixed(1)}%
 
 
 
 
 
 
 
 
 
 
 
 
306
  </span>
307
- </p>
308
- <p><strong>Upload Date:</strong> ${new Date(doc.upload_date).toLocaleDateString()}</p>
309
- <p><strong>OCR Preview:</strong></p>
310
- <div style="max-height: 100px; overflow-y: auto; background: #f8f9fa; padding: 0.5rem; border-radius: 4px; font-size: 0.8rem;">
311
- ${doc.ocr_text.substring(0, 200)}${doc.ocr_text.length > 200 ? '...' : ''}
312
  </div>
313
- <div class="document-actions">
314
- <button class="btn btn-danger" onclick="deleteDocument('${doc.id}', '${doc.original_filename}')">
315
- <i class="fas fa-trash"></i> Delete
 
 
 
 
 
 
 
 
 
316
  </button>
317
  </div>
318
  </div>
@@ -322,6 +416,98 @@ function displayDocuments(docs) {
322
  container.innerHTML = `<div class="document-grid">${docsHtml}</div>`;
323
  }
324
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  // Form submissions
326
  document.getElementById('uploadForm').addEventListener('submit', async (e) => {
327
  e.preventDefault();
 
3
  let documents = [];
4
  let authToken = null;
5
  let currentUser = null;
6
+ let currentView = 'list';
7
 
8
  // Initialize app
9
  document.addEventListener('DOMContentLoaded', function() {
10
  checkAuth();
11
+ setupSidebar();
12
  });
13
 
14
+ // Sidebar functionality
15
+ function setupSidebar() {
16
+ const sidebarToggle = document.getElementById('sidebarToggle');
17
+ const sidebar = document.getElementById('sidebar');
18
+ const mainContent = document.querySelector('.main-content');
19
+
20
+ if (sidebarToggle) {
21
+ sidebarToggle.addEventListener('click', function() {
22
+ sidebar.classList.toggle('collapsed');
23
+ mainContent.classList.toggle('sidebar-collapsed');
24
+ });
25
+ }
26
+
27
+ // Auto-collapse on mobile
28
+ if (window.innerWidth <= 768) {
29
+ sidebar.classList.add('collapsed');
30
+ mainContent.classList.add('sidebar-collapsed');
31
+ }
32
+ }
33
+
34
  // Authentication functions
35
  function checkAuth() {
36
  authToken = localStorage.getItem('authToken');
 
127
  return response;
128
  }
129
 
130
+ // Enhanced tab management with sidebar highlighting
131
  function showTab(tabName) {
132
  // Hide all tabs
133
  document.querySelectorAll('.tab-content').forEach(tab => {
134
  tab.classList.remove('active');
135
  });
136
+
137
+ // Remove active class from all sidebar links
138
+ document.querySelectorAll('.sidebar-nav .nav-link').forEach(link => {
139
+ link.classList.remove('active');
140
  });
141
 
142
  // Show selected tab
143
  document.getElementById(tabName).classList.add('active');
144
+
145
+ // Highlight active sidebar link
146
+ const activeLink = document.querySelector(`[onclick="showTab('${tabName}')"]`);
147
+ if (activeLink) {
148
+ activeLink.classList.add('active');
149
+ }
150
 
151
  // Load data for specific tabs
152
  if (tabName === 'browse') {
153
  loadCategories();
154
  loadAllDocuments();
155
+ } else if (tabName === 'dashboard') {
156
+ loadStats();
157
+ loadRecentActivity();
158
+ createCategoryChart();
159
  }
160
  }
161
 
 
200
  });
201
  }
202
 
203
+ // Enhanced stats loading with sidebar display
204
  async function loadStats() {
205
  try {
206
  const response = await authenticatedFetch('/api/stats');
207
  const stats = await response.json();
208
 
209
+ // Dashboard stats
210
+ const dashboardStatsHtml = `
211
+ <div class="col-md-4">
212
+ <div class="card stat-card">
213
+ <div class="card-body">
214
+ <h3>${stats.total_categories}</h3>
215
+ <p><i class="fas fa-tags me-2"></i>Total Categories</p>
216
+ </div>
217
+ </div>
218
  </div>
219
+ <div class="col-md-4">
220
+ <div class="card stat-card">
221
+ <div class="card-body">
222
+ <h3>${stats.total_documents}</h3>
223
+ <p><i class="fas fa-file me-2"></i>Documents Archived</p>
224
+ </div>
225
+ </div>
226
  </div>
227
+ <div class="col-md-4">
228
+ <div class="card stat-card">
229
+ <div class="card-body">
230
+ <h3>35%</h3>
231
+ <p><i class="fas fa-percentage me-2"></i>Min Confidence</p>
232
+ </div>
233
+ </div>
234
  </div>
235
  `;
236
 
237
+ // Sidebar stats
238
+ const sidebarStatsHtml = `
239
+ <div class="sidebar-stat">
240
+ <h6>${stats.total_categories}</h6>
241
+ <small>Categories</small>
242
+ </div>
243
+ <div class="sidebar-stat">
244
+ <h6>${stats.total_documents}</h6>
245
+ <small>Documents</small>
246
+ </div>
247
+ `;
248
+
249
+ document.getElementById('dashboardStats').innerHTML = dashboardStatsHtml;
250
+ document.getElementById('sidebarStats').innerHTML = sidebarStatsHtml;
251
  } catch (error) {
252
  console.error('Error loading stats:', error);
253
  }
254
  }
255
 
256
+ // Enhanced categories loading with sidebar display
257
  async function loadCategories() {
258
  try {
259
  const response = await authenticatedFetch('/api/categories');
260
  const data = await response.json();
261
  categories = data.categories;
262
 
263
+ // Main category buttons
264
  const buttonsHtml = `
265
  <button class="category-btn active" onclick="filterDocuments('all')">
266
+ <i class="fas fa-th-large me-2"></i>All Documents
267
  </button>
268
  ${categories.map(cat => `
269
  <button class="category-btn" onclick="filterDocuments('${cat}')">
270
+ <i class="fas fa-folder me-2"></i>${cat} (${data.counts[cat] || 0})
271
  </button>
272
  `).join('')}
273
  `;
274
 
275
+ // Sidebar categories
276
+ const sidebarCategoriesHtml = categories.map(cat => `
277
+ <div class="sidebar-category" onclick="filterDocuments('${cat}')">
278
+ <div class="sidebar-category-icon"></div>
279
+ <span>${cat} (${data.counts[cat] || 0})</span>
280
+ </div>
281
+ `).join('');
282
+
283
  document.getElementById('categoryButtons').innerHTML = buttonsHtml;
284
+ document.getElementById('sidebarCategories').innerHTML = sidebarCategoriesHtml;
285
  } catch (error) {
286
  console.error('Error loading categories:', error);
287
  }
 
352
  }
353
  }
354
 
355
+ // Enhanced document display with image preview
356
  function displayDocuments(docs) {
357
  const container = document.getElementById('documentsContainer');
358
 
359
  if (docs.length === 0) {
360
+ container.innerHTML = `
361
+ <div class="text-center py-5">
362
+ <i class="fas fa-folder-open fa-4x text-muted mb-3"></i>
363
+ <h4 class="text-muted">No documents found</h4>
364
+ <p class="text-muted">Upload some documents to get started</p>
365
+ </div>
366
+ `;
367
  return;
368
  }
369
 
 
371
  const similarityClass = doc.similarity >= 0.7 ? 'similarity-high' :
372
  doc.similarity >= 0.5 ? 'similarity-medium' : 'similarity-low';
373
 
374
+ const confidenceText = doc.similarity >= 0.7 ? 'High' :
375
+ doc.similarity >= 0.5 ? 'Medium' : 'Low';
376
+
377
  return `
378
  <div class="document-card">
379
+ <img src="/api/document-preview/${doc.id}"
380
+ class="document-preview"
381
+ alt="${doc.original_filename}"
382
+ onclick="showDocumentPreview('${doc.id}', '${doc.original_filename}')"
383
+ onerror="this.src='data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2Y4ZjlmYSIvPgo8dGV4dCB4PSI1MCUiIHk9IjUwJSIgZm9udC1mYW1pbHk9IkFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBmaWxsPSIjNmM3NTdkIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSI+RG9jdW1lbnQ8L3RleHQ+Cjwvc3ZnPg=='">
384
+
385
+ <div class="document-title">${doc.original_filename}</div>
386
+
387
+ <div class="document-meta">
388
+ <i class="fas fa-tag me-1"></i>
389
+ <strong>${doc.category}</strong>
390
+ </div>
391
+
392
+ <div class="document-meta">
393
+ <i class="fas fa-chart-line me-1"></i>
394
+ Confidence: <span class="similarity-badge ${similarityClass}">
395
+ ${(doc.similarity * 100).toFixed(1)}% ${confidenceText}
396
  </span>
 
 
 
 
 
397
  </div>
398
+
399
+ <div class="document-meta">
400
+ <i class="fas fa-calendar me-1"></i>
401
+ ${new Date(doc.upload_date).toLocaleDateString()}
402
+ </div>
403
+
404
+ <div class="mt-3">
405
+ <button class="btn btn-primary btn-sm me-2" onclick="showDocumentPreview('${doc.id}', '${doc.original_filename}')">
406
+ <i class="fas fa-eye me-1"></i>Preview
407
+ </button>
408
+ <button class="btn btn-danger btn-sm" onclick="deleteDocument('${doc.id}', '${doc.original_filename}')">
409
+ <i class="fas fa-trash me-1"></i>Delete
410
  </button>
411
  </div>
412
  </div>
 
416
  container.innerHTML = `<div class="document-grid">${docsHtml}</div>`;
417
  }
418
 
419
+ // Document preview functionality
420
+ function showDocumentPreview(documentId, filename) {
421
+ const modal = new bootstrap.Modal(document.getElementById('documentModal'));
422
+ const preview = document.getElementById('documentPreview');
423
+ const details = document.getElementById('documentDetails');
424
+
425
+ preview.src = `/api/document-preview/${documentId}`;
426
+ details.innerHTML = `<h6>${filename}</h6>`;
427
+
428
+ modal.show();
429
+ }
430
+
431
+ // View toggle functionality
432
+ function toggleView(viewType) {
433
+ currentView = viewType;
434
+
435
+ // Update button states
436
+ document.querySelectorAll('.btn-group .btn').forEach(btn => {
437
+ btn.classList.remove('active');
438
+ });
439
+ event.target.classList.add('active');
440
+
441
+ // Reload documents with new view
442
+ loadAllDocuments();
443
+ }
444
+
445
+ // Create category distribution chart
446
+ function createCategoryChart() {
447
+ const ctx = document.getElementById('categoryChart');
448
+ if (!ctx) return;
449
+
450
+ // Sample data - you can modify this to use real data
451
+ const data = {
452
+ labels: categories.slice(0, 5), // Show top 5 categories
453
+ datasets: [{
454
+ label: 'Documents',
455
+ data: categories.slice(0, 5).map(() => Math.floor(Math.random() * 20) + 1),
456
+ backgroundColor: [
457
+ 'rgba(13, 110, 253, 0.8)',
458
+ 'rgba(25, 135, 84, 0.8)',
459
+ 'rgba(255, 193, 7, 0.8)',
460
+ 'rgba(220, 53, 69, 0.8)',
461
+ 'rgba(13, 202, 240, 0.8)'
462
+ ],
463
+ borderColor: [
464
+ 'rgb(13, 110, 253)',
465
+ 'rgb(25, 135, 84)',
466
+ 'rgb(255, 193, 7)',
467
+ 'rgb(220, 53, 69)',
468
+ 'rgb(13, 202, 240)'
469
+ ],
470
+ borderWidth: 2
471
+ }]
472
+ };
473
+
474
+ new Chart(ctx, {
475
+ type: 'doughnut',
476
+ data: data,
477
+ options: {
478
+ responsive: true,
479
+ plugins: {
480
+ legend: {
481
+ position: 'bottom',
482
+ }
483
+ }
484
+ }
485
+ });
486
+ }
487
+
488
+ // Load recent activity
489
+ function loadRecentActivity() {
490
+ const recentActivity = document.getElementById('recentActivity');
491
+ // Sample recent activity - you can modify this to use real data
492
+ const activities = [
493
+ { action: 'Document classified', item: 'passport.jpg', time: '2 min ago' },
494
+ { action: 'Category added', item: 'driver_license', time: '1 hour ago' },
495
+ { action: 'Document uploaded', item: 'certificate.pdf', time: '3 hours ago' }
496
+ ];
497
+
498
+ const activityHtml = activities.map(activity => `
499
+ <div class="d-flex justify-content-between align-items-center mb-2 p-2 bg-light rounded">
500
+ <div>
501
+ <small class="fw-bold">${activity.action}</small><br>
502
+ <small class="text-muted">${activity.item}</small>
503
+ </div>
504
+ <small class="text-muted">${activity.time}</small>
505
+ </div>
506
+ `).join('');
507
+
508
+ recentActivity.innerHTML = activityHtml;
509
+ }
510
+
511
  // Form submissions
512
  document.getElementById('uploadForm').addEventListener('submit', async (e) => {
513
  e.preventDefault();
static/styles.css CHANGED
@@ -4,6 +4,8 @@
4
  --warning-color: #ffc107;
5
  --danger-color: #dc3545;
6
  --info-color: #0dcaf0;
 
 
7
  }
8
 
9
  * {
@@ -15,308 +17,357 @@
15
  body {
16
  background-color: #f8f9fa;
17
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
18
- color: var(--text-primary);
19
- line-height: 1.6;
20
  }
21
 
22
- /* Modal Styles */
23
- .modal {
24
- display: flex;
25
  position: fixed;
26
- z-index: 1000;
27
  left: 0;
28
- top: 0;
29
- width: 100%;
30
- height: 100%;
31
- background-color: rgba(0,0,0,0.5);
32
- align-items: center;
33
- justify-content: center;
 
 
34
  }
35
 
36
- .modal-content {
37
- background-color: var(--card-bg);
38
- padding: 2rem;
39
- border-radius: 12px;
40
- box-shadow: 0 10px 25px rgba(0,0,0,0.2);
41
- width: 90%;
42
- max-width: 400px;
43
  }
44
 
45
- .modal-content h2 {
46
- text-align: center;
47
- margin-bottom: 1.5rem;
48
- color: var(--primary-color);
49
  }
50
 
51
- .container {
52
- max-width: 1200px;
53
- margin: 0 auto;
54
- padding: 20px;
55
  }
56
 
57
- .header {
58
- background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
59
- color: white;
60
- padding: 2rem;
61
- margin-bottom: 2rem;
62
- border-radius: 12px;
63
  }
64
 
65
- .header-content {
66
- display: flex;
67
- justify-content: space-between;
68
- align-items: center;
69
- flex-wrap: wrap;
70
- gap: 1rem;
71
  }
72
 
73
- .header h1 {
74
- font-size: 2.5rem;
75
- margin-bottom: 0.5rem;
 
76
  }
77
 
78
- .header p {
79
- font-size: 1.1rem;
80
- opacity: 0.9;
81
  }
82
 
83
- .user-info {
84
- display: flex;
85
- align-items: center;
86
- gap: 1rem;
87
  }
88
 
89
- .user-info span {
90
- font-weight: 500;
91
  }
92
 
93
- .dashboard-stats {
94
- display: grid;
95
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
96
- gap: 1.5rem;
97
- margin-bottom: 2rem;
 
 
98
  }
99
 
100
- .stat-card {
101
- background: linear-gradient(135deg, var(--primary-color), #0b5ed7);
 
102
  color: white;
103
- border-radius: 0.5rem;
104
- padding: 1.5rem;
105
- text-align: center;
106
- box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
107
  }
108
 
109
- .stat-card h3 {
110
- font-size: 2.5rem;
111
- font-weight: 700;
112
- margin-bottom: 0.5rem;
113
  }
114
 
115
- .stat-card p {
116
- margin-bottom: 0;
117
- opacity: 0.9;
 
 
 
 
118
  }
119
 
120
- .tabs {
121
  display: flex;
122
- margin-bottom: 2rem;
123
- background: var(--card-bg);
124
- border-radius: 12px;
125
  padding: 0.5rem;
126
- box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.1);
127
- }
128
-
129
- .tab-button {
130
- flex: 1;
131
- padding: 1rem;
132
- border: none;
133
- background: transparent;
134
  cursor: pointer;
135
- border-radius: 8px;
136
- font-weight: 500;
137
- transition: all 0.3s ease;
138
  }
139
 
140
- .tab-button.active {
141
- background: var(--primary-color);
142
- color: white;
143
  }
144
 
145
- .tab-content {
146
- display: none;
147
- background: var(--card-bg);
148
- padding: 2rem;
149
- border-radius: 12px;
150
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
151
- border: 1px solid var(--border-color);
152
- }
153
-
154
- .tab-content.active {
155
- display: block;
156
- }
157
-
158
- /* File Upload Styling */
159
- .file-upload {
160
- border: 2px dashed #dee2e6 !important;
161
- cursor: pointer;
162
- transition: all 0.3s ease;
163
- background-color: #f8f9fa;
164
  }
165
 
166
- .file-upload:hover {
167
- border-color: var(--primary-color) !important;
168
- background-color: #e3f2fd;
 
 
 
 
169
  }
170
 
171
- .file-upload.dragover {
172
- border-color: var(--primary-color) !important;
173
- background-color: #e3f2fd;
174
  }
175
 
176
- /* Form Styles */
177
- .form-group {
178
- margin-bottom: 1.5rem;
179
  }
180
 
181
- .form-group label {
182
- display: block;
183
- margin-bottom: 0.5rem;
184
  font-weight: 600;
185
- color: var(--text-primary);
186
  }
187
 
188
- .form-control {
189
- width: 100%;
190
- padding: 0.75rem;
191
- border: 2px solid var(--border-color);
192
- border-radius: 8px;
193
- font-size: 1rem;
194
- transition: border-color 0.3s ease;
195
  }
196
 
197
- .form-control:focus {
198
- outline: none;
199
- border-color: var(--primary-color);
200
- }
201
-
202
- /* Button Styles */
203
- .btn {
204
- padding: 0.75rem 1.5rem;
205
  border: none;
206
- border-radius: 8px;
207
- font-size: 1rem;
208
- font-weight: 600;
209
- cursor: pointer;
210
- transition: all 0.3s ease;
211
  }
212
 
213
- .btn-primary {
214
- background: var(--primary-color);
215
- color: white;
216
  }
217
 
218
- .btn-primary:hover {
219
- background: var(--secondary-color);
220
  }
221
 
222
- .btn-secondary {
223
- background: var(--text-secondary);
 
224
  color: white;
 
 
 
 
 
 
225
  }
226
 
227
- .btn-success {
228
- background: var(--success-color);
229
- color: white;
230
  }
231
 
232
- .btn-danger {
233
- background: var(--error-color);
234
- color: white;
 
235
  }
236
 
237
- .btn-danger:hover {
238
- background: #dc2626;
 
239
  }
240
 
241
- /* Result Boxes */
242
- .result-box {
243
- padding: 1rem;
244
- border-radius: 0.375rem;
245
- font-family: 'Courier New', monospace;
246
- white-space: pre-wrap;
247
- word-wrap: break-word;
248
  }
249
 
250
- .result-success {
251
- background-color: #d1e7dd;
252
- border: 1px solid #badbcc;
253
- color: #0f5132;
 
 
 
 
 
254
  }
255
 
256
- .result-error {
257
- background-color: #f8d7da;
258
- border: 1px solid #f5c2c7;
259
- color: #842029;
 
 
 
 
260
  }
261
 
262
- .result-warning {
263
- background-color: #fff3cd;
264
- border: 1px solid #ffecb5;
265
- color: #664d03;
266
  }
267
 
268
- .result-info {
269
- background-color: #cff4fc;
270
- border: 1px solid #b6effb;
271
- color: #055160;
 
 
 
 
272
  }
273
 
274
- /* Document Cards */
275
- .document-grid {
276
- display: grid;
277
- grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
278
- gap: 1rem;
279
  }
280
 
281
- .document-card {
282
- background: white;
283
- border: 1px solid #dee2e6;
284
- border-radius: 0.375rem;
285
- padding: 1rem;
286
- box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
287
- transition: box-shadow 0.15s ease-in-out;
288
  }
289
 
290
- .document-card:hover {
291
- box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
 
 
292
  }
293
 
294
  /* Category Buttons */
295
  .category-buttons {
296
  display: flex;
297
  flex-wrap: wrap;
298
- gap: 0.5rem;
299
  }
300
 
301
  .category-btn {
302
- padding: 0.375rem 0.75rem;
303
- border: 1px solid #dee2e6;
304
- border-radius: 1.5rem;
305
- background-color: white;
306
- color: #6c757d;
307
  cursor: pointer;
308
- transition: all 0.15s ease-in-out;
309
  text-decoration: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  }
311
 
312
  .category-btn:hover,
313
  .category-btn.active {
314
- background-color: var(--primary-color);
315
  border-color: var(--primary-color);
316
  color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  }
318
 
319
- /* Loading Spinner */
 
 
 
 
 
320
  .loading {
321
  display: inline-block;
322
  width: 1rem;
@@ -333,58 +384,46 @@ body {
333
  100% { transform: rotate(360deg); }
334
  }
335
 
336
- /* OCR Text Display */
337
- .ocr-text {
338
- max-height: 300px;
339
- overflow-y: auto;
340
- background: #f8f9fa;
341
  padding: 1rem;
342
- border-radius: 0.375rem;
343
- border: 1px solid #dee2e6;
344
  font-family: 'Courier New', monospace;
345
- font-size: 0.9rem;
346
- line-height: 1.4;
347
- }
348
-
349
- /* Similarity Badges */
350
- .similarity-badge {
351
- font-size: 0.75rem;
352
- font-weight: 600;
353
- padding: 0.25rem 0.5rem;
354
- border-radius: 0.25rem;
355
  }
356
 
357
- .similarity-high {
358
- background-color: #d1e7dd;
359
- color: #0f5132;
 
360
  }
361
 
362
- .similarity-medium {
363
- background-color: #fff3cd;
364
- color: #664d03;
 
365
  }
366
 
367
- .similarity-low {
368
- background-color: #f8d7da;
369
- color: #842029;
 
370
  }
371
 
372
- /* Responsive adjustments */
373
  @media (max-width: 768px) {
374
- .header-content {
375
- text-align: center;
376
- }
377
-
378
- .header h1 {
379
- font-size: 2rem;
380
  }
381
 
382
- .tabs {
383
- flex-direction: column;
384
  }
385
 
386
- .tab-button {
387
- text-align: center;
388
  }
389
 
390
  .document-grid {
@@ -394,27 +433,22 @@ body {
394
  .category-buttons {
395
  justify-content: center;
396
  }
397
-
398
- .navbar-brand {
399
- font-size: 1rem;
400
- }
401
  }
402
 
403
- /* Custom scrollbar */
404
- .ocr-text::-webkit-scrollbar {
405
- width: 8px;
406
  }
407
 
408
- .ocr-text::-webkit-scrollbar-track {
409
- background: #f1f1f1;
410
- border-radius: 4px;
411
  }
412
 
413
- .ocr-text::-webkit-scrollbar-thumb {
414
- background: #c1c1c1;
415
- border-radius: 4px;
416
  }
417
 
418
- .ocr-text::-webkit-scrollbar-thumb:hover {
419
- background: #a8a8a8;
420
  }
 
4
  --warning-color: #ffc107;
5
  --danger-color: #dc3545;
6
  --info-color: #0dcaf0;
7
+ --sidebar-width: 280px;
8
+ --navbar-height: 60px;
9
  }
10
 
11
  * {
 
17
  body {
18
  background-color: #f8f9fa;
19
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 
 
20
  }
21
 
22
+ /* Sidebar Styles */
23
+ .sidebar {
 
24
  position: fixed;
25
+ top: var(--navbar-height);
26
  left: 0;
27
+ width: var(--sidebar-width);
28
+ height: calc(100vh - var(--navbar-height));
29
+ background: linear-gradient(180deg, #1e3a5f 0%, #2c5f8a 100%);
30
+ color: white;
31
+ z-index: 1000;
32
+ overflow-y: auto;
33
+ transition: transform 0.3s ease;
34
+ box-shadow: 2px 0 10px rgba(0,0,0,0.1);
35
  }
36
 
37
+ .sidebar.collapsed {
38
+ transform: translateX(-100%);
 
 
 
 
 
39
  }
40
 
41
+ .sidebar-header {
42
+ padding: 1.5rem;
43
+ border-bottom: 1px solid rgba(255,255,255,0.1);
 
44
  }
45
 
46
+ .sidebar-header h4 {
47
+ margin: 0;
48
+ font-size: 1.1rem;
49
+ font-weight: 600;
50
  }
51
 
52
+ .sidebar-stats {
53
+ padding: 1rem;
54
+ border-bottom: 1px solid rgba(255,255,255,0.1);
 
 
 
55
  }
56
 
57
+ .sidebar-stat {
58
+ background: rgba(255,255,255,0.1);
59
+ border-radius: 8px;
60
+ padding: 0.75rem;
61
+ margin-bottom: 0.5rem;
62
+ text-align: center;
63
  }
64
 
65
+ .sidebar-stat h6 {
66
+ font-size: 1.5rem;
67
+ margin: 0;
68
+ color: #ffd700;
69
  }
70
 
71
+ .sidebar-stat small {
72
+ opacity: 0.8;
73
+ font-size: 0.8rem;
74
  }
75
 
76
+ .sidebar-nav {
77
+ list-style: none;
78
+ padding: 0;
79
+ margin: 1rem 0;
80
  }
81
 
82
+ .sidebar-nav .nav-item {
83
+ margin: 0;
84
  }
85
 
86
+ .sidebar-nav .nav-link {
87
+ display: block;
88
+ padding: 0.75rem 1.5rem;
89
+ color: rgba(255,255,255,0.8);
90
+ text-decoration: none;
91
+ transition: all 0.3s ease;
92
+ border-left: 3px solid transparent;
93
  }
94
 
95
+ .sidebar-nav .nav-link:hover,
96
+ .sidebar-nav .nav-link.active {
97
+ background: rgba(255,255,255,0.1);
98
  color: white;
99
+ border-left-color: #ffd700;
 
 
 
100
  }
101
 
102
+ .sidebar-section {
103
+ padding: 1rem;
104
+ border-top: 1px solid rgba(255,255,255,0.1);
 
105
  }
106
 
107
+ .sidebar-heading {
108
+ font-size: 0.9rem;
109
+ font-weight: 600;
110
+ margin-bottom: 0.5rem;
111
+ opacity: 0.8;
112
+ text-transform: uppercase;
113
+ letter-spacing: 0.5px;
114
  }
115
 
116
+ .sidebar-category {
117
  display: flex;
118
+ align-items: center;
 
 
119
  padding: 0.5rem;
120
+ margin-bottom: 0.25rem;
121
+ background: rgba(255,255,255,0.05);
122
+ border-radius: 6px;
123
+ font-size: 0.85rem;
 
 
 
 
124
  cursor: pointer;
125
+ transition: background 0.3s ease;
 
 
126
  }
127
 
128
+ .sidebar-category:hover {
129
+ background: rgba(255,255,255,0.1);
 
130
  }
131
 
132
+ .sidebar-category-icon {
133
+ width: 8px;
134
+ height: 8px;
135
+ background: #ffd700;
136
+ border-radius: 50%;
137
+ margin-right: 0.5rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  }
139
 
140
+ /* Main Content */
141
+ .main-content {
142
+ margin-left: var(--sidebar-width);
143
+ margin-top: var(--navbar-height);
144
+ padding: 2rem;
145
+ min-height: calc(100vh - var(--navbar-height));
146
+ transition: margin-left 0.3s ease;
147
  }
148
 
149
+ .main-content.sidebar-collapsed {
150
+ margin-left: 0;
 
151
  }
152
 
153
+ .content-header {
154
+ margin-bottom: 2rem;
 
155
  }
156
 
157
+ .content-header h2 {
158
+ color: #2c3e50;
 
159
  font-weight: 600;
 
160
  }
161
 
162
+ /* Navbar */
163
+ .navbar {
164
+ z-index: 1001;
165
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
 
 
 
166
  }
167
 
168
+ #sidebarToggle {
 
 
 
 
 
 
 
169
  border: none;
170
+ background: rgba(255,255,255,0.1);
 
 
 
 
171
  }
172
 
173
+ /* Tab Content */
174
+ .tab-content {
175
+ display: none;
176
  }
177
 
178
+ .tab-content.active {
179
+ display: block;
180
  }
181
 
182
+ /* Stats Cards */
183
+ .stat-card {
184
+ background: linear-gradient(135deg, var(--primary-color), #0b5ed7);
185
  color: white;
186
+ border-radius: 12px;
187
+ padding: 1.5rem;
188
+ text-align: center;
189
+ box-shadow: 0 4px 15px rgba(13,110,253,0.2);
190
+ border: none;
191
+ transition: transform 0.3s ease;
192
  }
193
 
194
+ .stat-card:hover {
195
+ transform: translateY(-2px);
 
196
  }
197
 
198
+ .stat-card h3 {
199
+ font-size: 2.5rem;
200
+ font-weight: 700;
201
+ margin-bottom: 0.5rem;
202
  }
203
 
204
+ .stat-card p {
205
+ margin-bottom: 0;
206
+ opacity: 0.9;
207
  }
208
 
209
+ /* Document Cards */
210
+ .document-grid {
211
+ display: grid;
212
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
213
+ gap: 1.5rem;
 
 
214
  }
215
 
216
+ .document-card {
217
+ background: white;
218
+ border: none;
219
+ border-radius: 12px;
220
+ padding: 1.5rem;
221
+ box-shadow: 0 2px 15px rgba(0,0,0,0.08);
222
+ transition: all 0.3s ease;
223
+ position: relative;
224
+ overflow: hidden;
225
  }
226
 
227
+ .document-card::before {
228
+ content: '';
229
+ position: absolute;
230
+ top: 0;
231
+ left: 0;
232
+ right: 0;
233
+ height: 4px;
234
+ background: linear-gradient(90deg, var(--primary-color), var(--info-color));
235
  }
236
 
237
+ .document-card:hover {
238
+ transform: translateY(-5px);
239
+ box-shadow: 0 8px 30px rgba(0,0,0,0.15);
 
240
  }
241
 
242
+ .document-preview {
243
+ width: 100%;
244
+ height: 200px;
245
+ object-fit: cover;
246
+ border-radius: 8px;
247
+ margin-bottom: 1rem;
248
+ cursor: pointer;
249
+ transition: transform 0.3s ease;
250
  }
251
 
252
+ .document-preview:hover {
253
+ transform: scale(1.02);
 
 
 
254
  }
255
 
256
+ .document-title {
257
+ font-size: 1.1rem;
258
+ font-weight: 600;
259
+ color: #2c3e50;
260
+ margin-bottom: 0.5rem;
 
 
261
  }
262
 
263
+ .document-meta {
264
+ font-size: 0.9rem;
265
+ color: #6c757d;
266
+ margin-bottom: 0.25rem;
267
  }
268
 
269
  /* Category Buttons */
270
  .category-buttons {
271
  display: flex;
272
  flex-wrap: wrap;
273
+ gap: 0.75rem;
274
  }
275
 
276
  .category-btn {
277
+ padding: 0.5rem 1rem;
278
+ border: 2px solid #e9ecef;
279
+ border-radius: 25px;
280
+ background: white;
281
+ color: #495057;
282
  cursor: pointer;
283
+ transition: all 0.3s ease;
284
  text-decoration: none;
285
+ font-weight: 500;
286
+ position: relative;
287
+ overflow: hidden;
288
+ }
289
+
290
+ .category-btn::before {
291
+ content: '';
292
+ position: absolute;
293
+ top: 0;
294
+ left: -100%;
295
+ width: 100%;
296
+ height: 100%;
297
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
298
+ transition: left 0.5s;
299
+ }
300
+
301
+ .category-btn:hover::before {
302
+ left: 100%;
303
  }
304
 
305
  .category-btn:hover,
306
  .category-btn.active {
307
+ background: var(--primary-color);
308
  border-color: var(--primary-color);
309
  color: white;
310
+ transform: translateY(-2px);
311
+ box-shadow: 0 4px 15px rgba(13,110,253,0.3);
312
+ }
313
+
314
+ /* File Upload */
315
+ .file-upload {
316
+ border: 2px dashed #dee2e6;
317
+ cursor: pointer;
318
+ transition: all 0.3s ease;
319
+ background: #f8f9fa;
320
+ position: relative;
321
+ overflow: hidden;
322
+ }
323
+
324
+ .file-upload::before {
325
+ content: '';
326
+ position: absolute;
327
+ top: -50%;
328
+ left: -50%;
329
+ width: 200%;
330
+ height: 200%;
331
+ background: radial-gradient(circle, rgba(13,110,253,0.1) 0%, transparent 70%);
332
+ transform: scale(0);
333
+ transition: transform 0.5s ease;
334
+ }
335
+
336
+ .file-upload:hover::before {
337
+ transform: scale(1);
338
+ }
339
+
340
+ .file-upload:hover {
341
+ border-color: var(--primary-color);
342
+ background: rgba(13,110,253,0.02);
343
+ }
344
+
345
+ /* Similarity Badges */
346
+ .similarity-badge {
347
+ font-size: 0.75rem;
348
+ font-weight: 600;
349
+ padding: 0.25rem 0.75rem;
350
+ border-radius: 15px;
351
+ text-transform: uppercase;
352
+ letter-spacing: 0.5px;
353
+ }
354
+
355
+ .similarity-high {
356
+ background: linear-gradient(45deg, #10b981, #059669);
357
+ color: white;
358
+ }
359
+
360
+ .similarity-medium {
361
+ background: linear-gradient(45deg, #f59e0b, #d97706);
362
+ color: white;
363
  }
364
 
365
+ .similarity-low {
366
+ background: linear-gradient(45deg, #ef4444, #dc2626);
367
+ color: white;
368
+ }
369
+
370
+ /* Loading Animation */
371
  .loading {
372
  display: inline-block;
373
  width: 1rem;
 
384
  100% { transform: rotate(360deg); }
385
  }
386
 
387
+ /* Result Boxes */
388
+ .result-box {
 
 
 
389
  padding: 1rem;
390
+ border-radius: 8px;
 
391
  font-family: 'Courier New', monospace;
392
+ white-space: pre-wrap;
393
+ word-wrap: break-word;
394
+ border-left: 4px solid;
 
 
 
 
 
 
 
395
  }
396
 
397
+ .result-success {
398
+ background: #f0f9ff;
399
+ border-color: #0ea5e9;
400
+ color: #0c4a6e;
401
  }
402
 
403
+ .result-error {
404
+ background: #fef2f2;
405
+ border-color: #ef4444;
406
+ color: #991b1b;
407
  }
408
 
409
+ .result-warning {
410
+ background: #fffbeb;
411
+ border-color: #f59e0b;
412
+ color: #92400e;
413
  }
414
 
415
+ /* Responsive Design */
416
  @media (max-width: 768px) {
417
+ .sidebar {
418
+ transform: translateX(-100%);
 
 
 
 
419
  }
420
 
421
+ .main-content {
422
+ margin-left: 0;
423
  }
424
 
425
+ .sidebar.show {
426
+ transform: translateX(0);
427
  }
428
 
429
  .document-grid {
 
433
  .category-buttons {
434
  justify-content: center;
435
  }
 
 
 
 
436
  }
437
 
438
+ /* Custom Scrollbar */
439
+ .sidebar::-webkit-scrollbar {
440
+ width: 6px;
441
  }
442
 
443
+ .sidebar::-webkit-scrollbar-track {
444
+ background: rgba(255,255,255,0.1);
 
445
  }
446
 
447
+ .sidebar::-webkit-scrollbar-thumb {
448
+ background: rgba(255,255,255,0.3);
449
+ border-radius: 3px;
450
  }
451
 
452
+ .sidebar::-webkit-scrollbar-thumb:hover {
453
+ background: rgba(255,255,255,0.5);
454
  }