IZERE HIRWA Roger commited on
Commit
5753ed4
·
1 Parent(s): dd5d745
Files changed (3) hide show
  1. app.py +125 -115
  2. static/index.html +166 -110
  3. static/styles.css +134 -86
app.py CHANGED
@@ -1,4 +1,4 @@
1
- from flask import Flask, request, jsonify, render_template_string, send_from_directory
2
  from werkzeug.utils import secure_filename
3
  from werkzeug.security import generate_password_hash, check_password_hash
4
  import pytesseract
@@ -190,117 +190,11 @@ def save_uploaded_file(file_content: bytes, filename: str) -> str:
190
  # Routes
191
  @app.route("/")
192
  def dashboard():
193
- return render_template_string('''
194
- <!DOCTYPE html>
195
- <html>
196
- <head>
197
- <title>Document Classification System</title>
198
- <style>
199
- body { font-family: Arial, sans-serif; margin: 40px; }
200
- .container { max-width: 800px; margin: 0 auto; }
201
- .form-group { margin: 20px 0; }
202
- input, button { padding: 10px; margin: 5px; }
203
- button { background: #007bff; color: white; border: none; cursor: pointer; }
204
- .result { margin: 20px 0; padding: 10px; background: #f8f9fa; border: 1px solid #dee2e6; }
205
- </style>
206
- </head>
207
- <body>
208
- <div class="container">
209
- <h1>Document Classification System</h1>
210
-
211
- <div class="form-group">
212
- <h3>Login</h3>
213
- <form id="loginForm">
214
- <input type="text" id="username" placeholder="Username" required>
215
- <input type="password" id="password" placeholder="Password" required>
216
- <button type="submit">Login</button>
217
- </form>
218
- </div>
219
-
220
- <div id="mainContent" style="display:none;">
221
- <div class="form-group">
222
- <h3>Upload Category</h3>
223
- <form id="categoryForm" enctype="multipart/form-data">
224
- <input type="file" id="categoryFile" accept="image/*,.pdf" required>
225
- <input type="text" id="categoryLabel" placeholder="Category Label" required>
226
- <button type="submit">Add Category</button>
227
- </form>
228
- </div>
229
-
230
- <div class="form-group">
231
- <h3>Classify Document</h3>
232
- <form id="classifyForm" enctype="multipart/form-data">
233
- <input type="file" id="classifyFile" accept="image/*,.pdf" required>
234
- <button type="submit">Classify</button>
235
- </form>
236
- </div>
237
-
238
- <div id="result" class="result" style="display:none;"></div>
239
- </div>
240
- </div>
241
-
242
- <script>
243
- let token = null;
244
-
245
- document.getElementById('loginForm').onsubmit = async (e) => {
246
- e.preventDefault();
247
- const formData = new FormData();
248
- formData.append('username', document.getElementById('username').value);
249
- formData.append('password', document.getElementById('password').value);
250
-
251
- const response = await fetch('/api/login', {
252
- method: 'POST',
253
- body: formData
254
- });
255
-
256
- const result = await response.json();
257
- if (response.ok) {
258
- token = result.access_token;
259
- document.getElementById('mainContent').style.display = 'block';
260
- document.getElementById('result').innerHTML = 'Login successful!';
261
- document.getElementById('result').style.display = 'block';
262
- } else {
263
- document.getElementById('result').innerHTML = 'Login failed: ' + result.detail;
264
- document.getElementById('result').style.display = 'block';
265
- }
266
- };
267
-
268
- document.getElementById('categoryForm').onsubmit = async (e) => {
269
- e.preventDefault();
270
- const formData = new FormData();
271
- formData.append('file', document.getElementById('categoryFile').files[0]);
272
- formData.append('label', document.getElementById('categoryLabel').value);
273
-
274
- const response = await fetch('/api/upload-category', {
275
- method: 'POST',
276
- body: formData,
277
- headers: {'Authorization': 'Bearer ' + token}
278
- });
279
-
280
- const result = await response.json();
281
- document.getElementById('result').innerHTML = JSON.stringify(result, null, 2);
282
- document.getElementById('result').style.display = 'block';
283
- };
284
-
285
- document.getElementById('classifyForm').onsubmit = async (e) => {
286
- e.preventDefault();
287
- const formData = new FormData();
288
- formData.append('file', document.getElementById('classifyFile').files[0]);
289
-
290
- const response = await fetch('/api/classify-document', {
291
- method: 'POST',
292
- body: formData,
293
- headers: {'Authorization': 'Bearer ' + token}
294
- });
295
-
296
- const result = await response.json();
297
- document.getElementById('result').innerHTML = JSON.stringify(result, null, 2);
298
- document.getElementById('result').style.display = 'block';
299
- };
300
- </script>
301
- </body>
302
- </html>
303
- ''')
304
 
305
  @app.route("/api/login", methods=["POST"])
306
  def login():
@@ -440,8 +334,27 @@ def classify_document():
440
  except Exception as e:
441
  return jsonify({"error": str(e)}), 500
442
 
443
- @app.route("/api/documents", methods=["GET"])
444
- def get_all_documents():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  # Verify token
446
  auth_header = request.headers.get('Authorization')
447
  if not auth_header or not auth_header.startswith('Bearer '):
@@ -454,7 +367,7 @@ def get_all_documents():
454
 
455
  conn = sqlite3.connect(DATABASE_PATH)
456
  cursor = conn.cursor()
457
- cursor.execute('SELECT * FROM documents ORDER BY upload_date DESC')
458
  documents = []
459
  for row in cursor.fetchall():
460
  documents.append({
@@ -471,5 +384,102 @@ def get_all_documents():
471
 
472
  return jsonify({"documents": documents, "count": len(documents)})
473
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
  if __name__ == "__main__":
475
  app.run(host="0.0.0.0", port=7860, debug=True)
 
1
+ from flask import Flask, request, jsonify, send_from_directory
2
  from werkzeug.utils import secure_filename
3
  from werkzeug.security import generate_password_hash, check_password_hash
4
  import pytesseract
 
190
  # Routes
191
  @app.route("/")
192
  def dashboard():
193
+ return send_from_directory('static', 'index.html')
194
+
195
+ @app.route("/static/<path:filename>")
196
+ def static_files(filename):
197
+ return send_from_directory('static', filename)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
  @app.route("/api/login", methods=["POST"])
200
  def login():
 
334
  except Exception as e:
335
  return jsonify({"error": str(e)}), 500
336
 
337
+ @app.route("/api/categories", methods=["GET"])
338
+ def get_categories():
339
+ # Verify token
340
+ auth_header = request.headers.get('Authorization')
341
+ if not auth_header or not auth_header.startswith('Bearer '):
342
+ return jsonify({"error": "Missing or invalid token"}), 401
343
+
344
+ token = auth_header.split(' ')[1]
345
+ username = verify_token(token)
346
+ if not username:
347
+ return jsonify({"error": "Invalid token"}), 401
348
+
349
+ categories = list(set(labels)) # Remove duplicates
350
+ category_counts = {}
351
+ for label in labels:
352
+ category_counts[label] = category_counts.get(label, 0) + 1
353
+
354
+ return jsonify({"categories": categories, "counts": category_counts})
355
+
356
+ @app.route("/api/documents/<category>", methods=["GET"])
357
+ def get_documents_by_category(category):
358
  # Verify token
359
  auth_header = request.headers.get('Authorization')
360
  if not auth_header or not auth_header.startswith('Bearer '):
 
367
 
368
  conn = sqlite3.connect(DATABASE_PATH)
369
  cursor = conn.cursor()
370
+ cursor.execute('SELECT * FROM documents WHERE category = ? ORDER BY upload_date DESC', (category,))
371
  documents = []
372
  for row in cursor.fetchall():
373
  documents.append({
 
384
 
385
  return jsonify({"documents": documents, "count": len(documents)})
386
 
387
+ @app.route("/api/documents/<document_id>", methods=["DELETE"])
388
+ def delete_document(document_id):
389
+ # Verify token
390
+ auth_header = request.headers.get('Authorization')
391
+ if not auth_header or not auth_header.startswith('Bearer '):
392
+ return jsonify({"error": "Missing or invalid token"}), 401
393
+
394
+ token = auth_header.split(' ')[1]
395
+ username = verify_token(token)
396
+ if not username:
397
+ return jsonify({"error": "Invalid token"}), 401
398
+
399
+ try:
400
+ conn = sqlite3.connect(DATABASE_PATH)
401
+ cursor = conn.cursor()
402
+
403
+ # Get document info first
404
+ cursor.execute('SELECT file_path FROM documents WHERE id = ?', (document_id,))
405
+ result = cursor.fetchone()
406
+
407
+ if not result:
408
+ conn.close()
409
+ return jsonify({"error": "Document not found"}), 404
410
+
411
+ file_path = result[0]
412
+
413
+ # Delete physical file
414
+ if file_path and os.path.exists(file_path):
415
+ os.remove(file_path)
416
+
417
+ # Delete from database
418
+ cursor.execute('DELETE FROM documents WHERE id = ?', (document_id,))
419
+ conn.commit()
420
+ conn.close()
421
+
422
+ return jsonify({"message": "Document deleted successfully", "status": "success"})
423
+ except Exception as e:
424
+ return jsonify({"error": str(e)}), 500
425
+
426
+ @app.route("/api/ocr", methods=["POST"])
427
+ def ocr_document():
428
+ # Verify token
429
+ auth_header = request.headers.get('Authorization')
430
+ if not auth_header or not auth_header.startswith('Bearer '):
431
+ return jsonify({"error": "Missing or invalid token"}), 401
432
+
433
+ token = auth_header.split(' ')[1]
434
+ username = verify_token(token)
435
+ if not username:
436
+ return jsonify({"error": "Invalid token"}), 401
437
+
438
+ try:
439
+ file = request.files.get("file")
440
+ if not file:
441
+ return jsonify({"error": "Missing file"}), 400
442
+
443
+ file_content = file.read()
444
+ if file.content_type and file.content_type.startswith('application/pdf'):
445
+ image = image_from_pdf(file_content)
446
+ else:
447
+ image = Image.open(io.BytesIO(file_content))
448
+
449
+ if image is None:
450
+ return jsonify({"error": "Failed to process image"}), 400
451
+
452
+ text = extract_text(image)
453
+ return jsonify({"text": text, "status": "success"})
454
+ except Exception as e:
455
+ return jsonify({"error": str(e)}), 500
456
+
457
+ @app.route("/api/stats", methods=["GET"])
458
+ def get_stats():
459
+ # Verify token
460
+ auth_header = request.headers.get('Authorization')
461
+ if not auth_header or not auth_header.startswith('Bearer '):
462
+ return jsonify({"error": "Missing or invalid token"}), 401
463
+
464
+ token = auth_header.split(' ')[1]
465
+ username = verify_token(token)
466
+ if not username:
467
+ return jsonify({"error": "Invalid token"}), 401
468
+
469
+ conn = sqlite3.connect(DATABASE_PATH)
470
+ cursor = conn.cursor()
471
+ cursor.execute('SELECT category, COUNT(*) FROM documents GROUP BY category')
472
+ category_stats = dict(cursor.fetchall())
473
+
474
+ cursor.execute('SELECT COUNT(*) FROM documents')
475
+ total_documents = cursor.fetchone()[0]
476
+ conn.close()
477
+
478
+ return jsonify({
479
+ "total_categories": len(set(labels)),
480
+ "total_documents": total_documents,
481
+ "category_distribution": category_stats
482
+ })
483
+
484
  if __name__ == "__main__":
485
  app.run(host="0.0.0.0", port=7860, debug=True)
static/index.html CHANGED
@@ -4,158 +4,214 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Handwritten Archive Document Digitalization System</title>
 
 
 
7
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
 
8
  <link rel="stylesheet" href="/static/styles.css">
9
  </head>
10
  <body>
11
  <!-- Login Modal -->
12
- <div id="loginModal" class="modal">
13
- <div class="modal-content">
14
- <h2><i class="fas fa-lock"></i> Login Required</h2>
15
- <form id="loginForm">
16
- <div class="form-group">
17
- <label for="username">Username</label>
18
- <input type="text" id="username" class="form-control" required value="admin">
19
  </div>
20
- <div class="form-group">
21
- <label for="password">Password</label>
22
- <input type="password" id="password" class="form-control" required value="admin123">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  </div>
24
- <button type="submit" class="btn btn-primary">
25
- <i class="fas fa-sign-in-alt"></i> Login
26
- </button>
27
- </form>
28
- <div id="loginResult"></div>
29
  </div>
30
  </div>
31
 
32
  <!-- Main Application -->
33
  <div id="mainApp" style="display: none;">
34
- <div class="container">
35
- <div class="header">
36
- <div class="header-content">
37
- <div>
38
- <h1><i class="fas fa-archive"></i> Archive Digitalization System</h1>
39
- <p>Handwritten Document Classification & Storage</p>
40
- </div>
41
- <div class="user-info">
42
- <span id="welcomeUser"></span>
43
- <button class="btn btn-secondary" onclick="logout()">
44
- <i class="fas fa-sign-out-alt"></i> Logout
45
- </button>
46
- </div>
47
  </div>
48
  </div>
 
49
 
50
- <div class="dashboard-stats" id="stats">
 
 
51
  <!-- Stats will be loaded here -->
52
  </div>
53
 
54
- <div class="tabs">
55
- <button class="tab-button active" onclick="showTab('upload')">
56
- <i class="fas fa-upload"></i> Upload Categories
57
- </button>
58
- <button class="tab-button" onclick="showTab('classify')">
59
- <i class="fas fa-search"></i> Classify Documents
60
- </button>
61
- <button class="tab-button" onclick="showTab('browse')">
62
- <i class="fas fa-folder-open"></i> Browse Archive
63
- </button>
64
- <button class="tab-button" onclick="showTab('ocr')">
65
- <i class="fas fa-eye"></i> OCR Text
66
- </button>
67
- </div>
 
 
 
 
 
 
 
 
 
68
 
69
  <!-- Upload Categories Tab -->
70
  <div id="upload" class="tab-content active">
71
- <h2><i class="fas fa-tags"></i> Upload Document Categories</h2>
72
- <p>Upload sample documents for each category to train the classification system.</p>
73
-
74
- <form id="uploadForm">
75
- <div class="form-group">
76
- <label for="categoryFile">Document File (Image or PDF)</label>
77
- <div class="file-upload" id="categoryUpload">
78
- <i class="fas fa-cloud-upload-alt fa-2x"></i>
79
- <p>Click to select or drag & drop files here</p>
80
- <input type="file" id="categoryFile" accept="image/*,.pdf" style="display: none;">
81
- </div>
82
  </div>
83
-
84
- <div class="form-group">
85
- <label for="categoryLabel">Category Label</label>
86
- <input type="text" id="categoryLabel" class="form-control" placeholder="e.g., birth_certificate, passport, diploma">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  </div>
88
-
89
- <button type="submit" class="btn btn-primary">
90
- <i class="fas fa-plus"></i> Add Category
91
- </button>
92
- </form>
93
-
94
- <div id="uploadResult"></div>
95
  </div>
96
 
97
  <!-- Classify Documents Tab -->
98
  <div id="classify" class="tab-content">
99
- <h2><i class="fas fa-robot"></i> Classify & Store Documents</h2>
100
- <p>Upload documents to automatically classify and store them in the archive (min. 35% confidence).</p>
101
-
102
- <form id="classifyForm">
103
- <div class="form-group">
104
- <label for="classifyFile">Document to Classify</label>
105
- <div class="file-upload" id="classifyUpload">
106
- <i class="fas fa-file fa-2x"></i>
107
- <p>Click to select or drag & drop files here</p>
108
- <input type="file" id="classifyFile" accept="image/*,.pdf" style="display: none;">
109
- </div>
110
  </div>
111
-
112
- <button type="submit" class="btn btn-success">
113
- <i class="fas fa-search"></i> Classify Document
114
- </button>
115
- </form>
116
-
117
- <div id="classifyResult"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  </div>
119
 
120
  <!-- Browse Archive Tab -->
121
  <div id="browse" class="tab-content">
122
- <h2><i class="fas fa-archive"></i> Browse Document Archive</h2>
123
- <p>Browse and search through your classified documents by category.</p>
124
-
125
- <div class="category-buttons" id="categoryButtons">
126
- <!-- Category buttons will be loaded here -->
127
- </div>
128
-
129
- <div id="documentsContainer">
130
- <!-- Documents will be loaded here -->
 
 
 
 
 
 
131
  </div>
132
  </div>
133
 
134
  <!-- OCR Text Tab -->
135
  <div id="ocr" class="tab-content">
136
- <h2><i class="fas fa-eye"></i> OCR Text Extraction</h2>
137
- <p>Extract text from documents using Optical Character Recognition.</p>
138
-
139
- <form id="ocrForm">
140
- <div class="form-group">
141
- <label for="ocrFile">Document File</label>
142
- <div class="file-upload" id="ocrUpload">
143
- <i class="fas fa-file-alt fa-2x"></i>
144
- <p>Click to select or drag & drop files here</p>
145
- <input type="file" id="ocrFile" accept="image/*,.pdf" style="display: none;">
146
- </div>
147
  </div>
148
-
149
- <button type="submit" class="btn btn-primary">
150
- <i class="fas fa-search"></i> Extract Text
151
- </button>
152
- </form>
153
-
154
- <div id="ocrResult"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  </div>
156
  </div>
157
  </div>
158
 
 
 
 
159
  <script src="/static/script.js"></script>
160
  </body>
161
  </html>
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Handwritten Archive Document Digitalization System</title>
7
+ <!-- Bootstrap CSS -->
8
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ <!-- Font Awesome -->
10
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
11
+ <!-- Custom CSS -->
12
  <link rel="stylesheet" href="/static/styles.css">
13
  </head>
14
  <body>
15
  <!-- Login Modal -->
16
+ <div id="loginModal" class="modal fade show" style="display: block; background: rgba(0,0,0,0.8);">
17
+ <div class="modal-dialog modal-dialog-centered">
18
+ <div class="modal-content shadow-lg">
19
+ <div class="modal-header bg-primary text-white">
20
+ <h4 class="modal-title"><i class="fas fa-lock me-2"></i>Login Required</h4>
 
 
21
  </div>
22
+ <div class="modal-body p-4">
23
+ <form id="loginForm">
24
+ <div class="mb-3">
25
+ <label for="username" class="form-label">Username</label>
26
+ <div class="input-group">
27
+ <span class="input-group-text"><i class="fas fa-user"></i></span>
28
+ <input type="text" id="username" class="form-control" required value="admin" placeholder="Enter username">
29
+ </div>
30
+ </div>
31
+ <div class="mb-3">
32
+ <label for="password" class="form-label">Password</label>
33
+ <div class="input-group">
34
+ <span class="input-group-text"><i class="fas fa-lock"></i></span>
35
+ <input type="password" id="password" class="form-control" required value="admin123" placeholder="Enter password">
36
+ </div>
37
+ </div>
38
+ <button type="submit" class="btn btn-primary w-100">
39
+ <i class="fas fa-sign-in-alt me-2"></i>Login
40
+ </button>
41
+ </form>
42
+ <div id="loginResult" class="mt-3"></div>
43
  </div>
44
+ </div>
 
 
 
 
45
  </div>
46
  </div>
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>
56
+ <div class="navbar-nav ms-auto">
57
+ <span class="navbar-text me-3" id="welcomeUser"></span>
58
+ <button class="btn btn-outline-light btn-sm" onclick="logout()">
59
+ <i class="fas fa-sign-out-alt me-1"></i>Logout
60
+ </button>
 
 
61
  </div>
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>
107
+ <div class="file-upload border rounded p-4 text-center" id="categoryUpload">
108
+ <i class="fas fa-cloud-upload-alt fa-3x text-primary mb-3"></i>
109
+ <p class="mb-0">Click to select or drag & drop files here</p>
110
+ <input type="file" id="categoryFile" accept="image/*,.pdf" class="d-none">
111
+ </div>
112
+ </div>
113
+ <div class="col-md-6">
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>
123
+ </form>
124
+
125
+ <div id="uploadResult" class="mt-3"></div>
126
  </div>
127
+ </div>
 
 
 
 
 
 
128
  </div>
129
 
130
  <!-- Classify Documents Tab -->
131
  <div id="classify" class="tab-content">
132
+ <div class="card">
133
+ <div class="card-header bg-success text-white">
134
+ <h4><i class="fas fa-robot me-2"></i>Classify & Store Documents</h4>
 
 
 
 
 
 
 
 
135
  </div>
136
+ <div class="card-body">
137
+ <p class="text-muted">Upload documents to automatically classify and store them in the archive (min. 35% confidence).</p>
138
+
139
+ <form id="classifyForm" class="row g-3">
140
+ <div class="col-12">
141
+ <label for="classifyFile" class="form-label">Document to Classify</label>
142
+ <div class="file-upload border rounded p-4 text-center" id="classifyUpload">
143
+ <i class="fas fa-file fa-3x text-success mb-3"></i>
144
+ <p class="mb-0">Click to select or drag & drop files here</p>
145
+ <input type="file" id="classifyFile" accept="image/*,.pdf" class="d-none">
146
+ </div>
147
+ </div>
148
+ <div class="col-12">
149
+ <button type="submit" class="btn btn-success">
150
+ <i class="fas fa-search me-2"></i>Classify Document
151
+ </button>
152
+ </div>
153
+ </form>
154
+
155
+ <div id="classifyResult" class="mt-3"></div>
156
+ </div>
157
+ </div>
158
  </div>
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 -->
181
  <div id="ocr" class="tab-content">
182
+ <div class="card">
183
+ <div class="card-header bg-warning text-dark">
184
+ <h4><i class="fas fa-eye me-2"></i>OCR Text Extraction</h4>
 
 
 
 
 
 
 
 
185
  </div>
186
+ <div class="card-body">
187
+ <p class="text-muted">Extract text from documents using Optical Character Recognition.</p>
188
+
189
+ <form id="ocrForm" class="row g-3">
190
+ <div class="col-12">
191
+ <label for="ocrFile" class="form-label">Document File</label>
192
+ <div class="file-upload border rounded p-4 text-center" id="ocrUpload">
193
+ <i class="fas fa-file-alt fa-3x text-warning mb-3"></i>
194
+ <p class="mb-0">Click to select or drag & drop files here</p>
195
+ <input type="file" id="ocrFile" accept="image/*,.pdf" class="d-none">
196
+ </div>
197
+ </div>
198
+ <div class="col-12">
199
+ <button type="submit" class="btn btn-warning">
200
+ <i class="fas fa-search me-2"></i>Extract Text
201
+ </button>
202
+ </div>
203
+ </form>
204
+
205
+ <div id="ocrResult" class="mt-3"></div>
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>
217
  </html>
static/styles.css CHANGED
@@ -1,14 +1,9 @@
1
  :root {
2
- --primary-color: #2563eb;
3
- --secondary-color: #1e40af;
4
- --success-color: #10b981;
5
- --warning-color: #f59e0b;
6
- --error-color: #ef4444;
7
- --bg-color: #f8fafc;
8
- --card-bg: #ffffff;
9
- --text-primary: #1f2937;
10
- --text-secondary: #6b7280;
11
- --border-color: #e5e7eb;
12
  }
13
 
14
  * {
@@ -18,8 +13,8 @@
18
  }
19
 
20
  body {
 
21
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
22
- background-color: var(--bg-color);
23
  color: var(--text-primary);
24
  line-height: 1.6;
25
  }
@@ -103,22 +98,23 @@ body {
103
  }
104
 
105
  .stat-card {
106
- background: var(--card-bg);
 
 
107
  padding: 1.5rem;
108
- border-radius: 12px;
109
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
110
- border: 1px solid var(--border-color);
111
  }
112
 
113
  .stat-card h3 {
114
- font-size: 2rem;
115
- color: var(--primary-color);
116
  margin-bottom: 0.5rem;
117
  }
118
 
119
  .stat-card p {
120
- color: var(--text-secondary);
121
- font-size: 0.9rem;
122
  }
123
 
124
  .tabs {
@@ -159,6 +155,25 @@ body {
159
  display: block;
160
  }
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  .form-group {
163
  margin-bottom: 1.5rem;
164
  }
@@ -184,6 +199,7 @@ body {
184
  border-color: var(--primary-color);
185
  }
186
 
 
187
  .btn {
188
  padding: 0.75rem 1.5rem;
189
  border: none;
@@ -222,107 +238,94 @@ body {
222
  background: #dc2626;
223
  }
224
 
225
- .file-upload {
226
- border: 2px dashed var(--border-color);
227
- border-radius: 8px;
228
- padding: 2rem;
229
- text-align: center;
230
- cursor: pointer;
231
- transition: all 0.3s ease;
232
- }
233
-
234
- .file-upload:hover {
235
- border-color: var(--primary-color);
236
- background-color: #f0f9ff;
237
- }
238
-
239
- .file-upload.dragover {
240
- border-color: var(--primary-color);
241
- background-color: #f0f9ff;
242
- }
243
-
244
  .result-box {
245
- margin-top: 1rem;
246
  padding: 1rem;
247
- border-radius: 8px;
248
- font-family: monospace;
249
  white-space: pre-wrap;
 
250
  }
251
 
252
  .result-success {
253
- background-color: #f0fdf4;
254
- border: 1px solid var(--success-color);
255
- color: #166534;
256
  }
257
 
258
  .result-error {
259
- background-color: #fef2f2;
260
- border: 1px solid var(--error-color);
261
- color: #dc2626;
262
  }
263
 
264
  .result-warning {
265
- background-color: #fffbeb;
266
- border: 1px solid var(--warning-color);
267
- color: #92400e;
268
  }
269
 
 
 
 
 
 
 
 
270
  .document-grid {
271
  display: grid;
272
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
273
  gap: 1rem;
274
- margin-top: 1rem;
275
  }
276
 
277
  .document-card {
278
- background: var(--card-bg);
279
- border: 1px solid var(--border-color);
280
- border-radius: 8px;
281
  padding: 1rem;
282
- box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.1);
 
283
  }
284
 
285
- .document-card h4 {
286
- color: var(--primary-color);
287
- margin-bottom: 0.5rem;
288
- }
289
-
290
- .document-actions {
291
- display: flex;
292
- gap: 0.5rem;
293
- margin-top: 1rem;
294
  }
295
 
 
296
  .category-buttons {
297
  display: flex;
298
  flex-wrap: wrap;
299
  gap: 0.5rem;
300
- margin-bottom: 1rem;
301
  }
302
 
303
  .category-btn {
304
- padding: 0.5rem 1rem;
305
- background: var(--bg-color);
306
- border: 1px solid var(--border-color);
307
- border-radius: 20px;
 
308
  cursor: pointer;
309
- transition: all 0.3s ease;
 
310
  }
311
 
312
- .category-btn:hover, .category-btn.active {
313
- background: var(--primary-color);
314
- color: white;
315
  border-color: var(--primary-color);
 
316
  }
317
 
 
318
  .loading {
319
  display: inline-block;
320
- width: 20px;
321
- height: 20px;
322
  border: 2px solid #f3f3f3;
323
  border-top: 2px solid var(--primary-color);
324
  border-radius: 50%;
325
  animation: spin 1s linear infinite;
 
326
  }
327
 
328
  @keyframes spin {
@@ -330,29 +333,43 @@ body {
330
  100% { transform: rotate(360deg); }
331
  }
332
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  .similarity-badge {
334
- display: inline-block;
335
- padding: 0.25rem 0.5rem;
336
- border-radius: 12px;
337
- font-size: 0.8rem;
338
  font-weight: 600;
 
 
339
  }
340
 
341
  .similarity-high {
342
- background: #dcfce7;
343
- color: #166534;
344
  }
345
 
346
  .similarity-medium {
347
- background: #fef3c7;
348
- color: #92400e;
349
  }
350
 
351
  .similarity-low {
352
- background: #fecaca;
353
- color: #dc2626;
354
  }
355
 
 
356
  @media (max-width: 768px) {
357
  .header-content {
358
  text-align: center;
@@ -369,4 +386,35 @@ body {
369
  .tab-button {
370
  text-align: center;
371
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  }
 
1
  :root {
2
+ --primary-color: #0d6efd;
3
+ --success-color: #198754;
4
+ --warning-color: #ffc107;
5
+ --danger-color: #dc3545;
6
+ --info-color: #0dcaf0;
 
 
 
 
 
7
  }
8
 
9
  * {
 
13
  }
14
 
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
  }
 
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 {
 
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
  }
 
199
  border-color: var(--primary-color);
200
  }
201
 
202
+ /* Button Styles */
203
  .btn {
204
  padding: 0.75rem 1.5rem;
205
  border: none;
 
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;
323
+ height: 1rem;
324
  border: 2px solid #f3f3f3;
325
  border-top: 2px solid var(--primary-color);
326
  border-radius: 50%;
327
  animation: spin 1s linear infinite;
328
+ margin-right: 0.5rem;
329
  }
330
 
331
  @keyframes spin {
 
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;
 
386
  .tab-button {
387
  text-align: center;
388
  }
389
+
390
+ .document-grid {
391
+ grid-template-columns: 1fr;
392
+ }
393
+
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
  }