yamanavijayavardhan commited on
Commit
372999e
·
1 Parent(s): ff76d74
Files changed (1) hide show
  1. templates/2.html +1461 -6
templates/2.html CHANGED
@@ -1,8 +1,1463 @@
1
- <!-- Add this in the <head> section -->
2
- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
3
- <script src="{{ url_for('static', filename='js/error-handler.js') }}"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
- <!-- Add this right after the <body> tag -->
6
- <div id="notification-container"></div>
 
 
 
 
 
 
7
 
8
- <!-- ... rest of your existing HTML ... -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Answer Generation</title>
7
+ <!-- Add Google Fonts -->
8
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap" rel="stylesheet">
9
+ <style>
10
+ :root {
11
+ --primary-color: #4361ee;
12
+ --secondary-color: #3f37c9;
13
+ --accent-color: #4895ef;
14
+ --background-color: #f8f9fa;
15
+ --text-color: #2b2d42;
16
+ --border-radius: 8px;
17
+ --box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
18
+ }
19
 
20
+ body {
21
+ font-family: 'Poppins', sans-serif;
22
+ margin: 0;
23
+ padding: 2rem;
24
+ background-color: var(--background-color);
25
+ color: var(--text-color);
26
+ line-height: 1.6;
27
+ }
28
 
29
+ .container {
30
+ max-width: 1200px;
31
+ margin: 0 auto;
32
+ padding: 2rem;
33
+ background: white;
34
+ border-radius: var(--border-radius);
35
+ box-shadow: var(--box-shadow);
36
+ }
37
+
38
+ h2 {
39
+ color: var(--primary-color);
40
+ margin-bottom: 1.5rem;
41
+ font-weight: 600;
42
+ position: relative;
43
+ padding-bottom: 0.5rem;
44
+ }
45
+
46
+ h2::after {
47
+ content: '';
48
+ position: absolute;
49
+ bottom: 0;
50
+ left: 0;
51
+ width: 50px;
52
+ height: 3px;
53
+ background-color: var(--accent-color);
54
+ border-radius: 2px;
55
+ }
56
+
57
+ .section {
58
+ background: white;
59
+ padding: 1.5rem;
60
+ border-radius: var(--border-radius);
61
+ margin-bottom: 2rem;
62
+ box-shadow: var(--box-shadow);
63
+ }
64
+
65
+ .upload-container {
66
+ margin-bottom: 1.5rem;
67
+ }
68
+
69
+ label {
70
+ display: block;
71
+ margin-bottom: 0.5rem;
72
+ font-weight: 500;
73
+ color: var(--text-color);
74
+ }
75
+
76
+ input[type="file"] {
77
+ width: 100%;
78
+ padding: 0.5rem;
79
+ margin-bottom: 1rem;
80
+ border: 2px dashed var(--accent-color);
81
+ border-radius: var(--border-radius);
82
+ background: #f8f9fa;
83
+ cursor: pointer;
84
+ }
85
+
86
+ input[type="file"]:hover {
87
+ border-color: var(--primary-color);
88
+ }
89
+
90
+ select {
91
+ width: 100%;
92
+ padding: 0.8rem;
93
+ border: 1px solid #ddd;
94
+ border-radius: var(--border-radius);
95
+ margin-bottom: 1rem;
96
+ font-family: 'Poppins', sans-serif;
97
+ appearance: none;
98
+ background: white url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23444' viewBox='0 0 16 16'%3E%3Cpath d='M8 12L2 6h12z'/%3E%3C/svg%3E") no-repeat right 0.8rem center;
99
+ }
100
+
101
+ button {
102
+ background-color: var(--primary-color);
103
+ color: white;
104
+ border: none;
105
+ padding: 0.8rem 1.5rem;
106
+ border-radius: var(--border-radius);
107
+ cursor: pointer;
108
+ font-weight: 500;
109
+ transition: all 0.3s ease;
110
+ font-family: 'Poppins', sans-serif;
111
+ width: auto;
112
+ min-width: 200px;
113
+ margin: 1rem auto;
114
+ display: block;
115
+ }
116
+
117
+ button:hover {
118
+ background-color: var(--secondary-color);
119
+ transform: translateY(-2px);
120
+ box-shadow: 0 4px 12px rgba(67, 97, 238, 0.3);
121
+ }
122
+
123
+ .answer-box {
124
+ width: 100%;
125
+ min-height: 100px;
126
+ padding: 1rem;
127
+ margin-bottom: 1rem;
128
+ border: 1px solid #ddd;
129
+ border-radius: var(--border-radius);
130
+ font-family: 'Poppins', sans-serif;
131
+ resize: vertical;
132
+ transition: border-color 0.3s ease;
133
+ }
134
+
135
+ .answer-box:focus {
136
+ outline: none;
137
+ border-color: var(--accent-color);
138
+ box-shadow: 0 0 0 3px rgba(72, 149, 239, 0.2);
139
+ }
140
+
141
+ table {
142
+ width: 100%;
143
+ border-collapse: separate;
144
+ border-spacing: 0;
145
+ margin-top: 1.5rem;
146
+ background: white;
147
+ border-radius: var(--border-radius);
148
+ overflow: hidden;
149
+ box-shadow: var(--box-shadow);
150
+ }
151
+
152
+ th, td {
153
+ padding: 1rem;
154
+ text-align: left;
155
+ border-bottom: 1px solid #eee;
156
+ }
157
+
158
+ th {
159
+ background-color: var(--primary-color);
160
+ color: white;
161
+ font-weight: 500;
162
+ }
163
+
164
+ tr:hover {
165
+ background-color: #f8f9fa;
166
+ }
167
+
168
+ .hidden {
169
+ display: none;
170
+ }
171
+
172
+ /* Responsive Design */
173
+ @media (max-width: 768px) {
174
+ body {
175
+ padding: 1rem;
176
+ }
177
+
178
+ .container {
179
+ padding: 1rem;
180
+ }
181
+
182
+ button {
183
+ padding: 0.7rem 1rem;
184
+ }
185
+ }
186
+
187
+ /* Animation */
188
+ @keyframes fadeIn {
189
+ from { opacity: 0; transform: translateY(10px); }
190
+ to { opacity: 1; transform: translateY(0); }
191
+ }
192
+
193
+ .section {
194
+ animation: fadeIn 0.5s ease-out;
195
+ }
196
+
197
+ .upload-methods {
198
+ display: flex;
199
+ gap: 1rem;
200
+ flex-wrap: wrap;
201
+ }
202
+
203
+ .upload-method {
204
+ flex: 1;
205
+ min-width: 300px;
206
+ }
207
+
208
+ .file-list {
209
+ margin-top: 1rem;
210
+ max-height: 200px;
211
+ overflow-y: auto;
212
+ border: 1px solid #ddd;
213
+ border-radius: var(--border-radius);
214
+ padding: 0.5rem;
215
+ }
216
+
217
+ .file-item {
218
+ display: flex;
219
+ justify-content: space-between;
220
+ align-items: center;
221
+ padding: 0.25rem 0;
222
+ border-bottom: 1px solid #eee;
223
+ }
224
+
225
+ .file-item:last-child {
226
+ border-bottom: none;
227
+ }
228
+
229
+ .remove-file {
230
+ color: red;
231
+ cursor: pointer;
232
+ padding: 0.25rem 0.5rem;
233
+ }
234
+
235
+ .folder-structure {
236
+ margin-top: 1rem;
237
+ padding: 1rem;
238
+ border: 1px solid #ddd;
239
+ border-radius: var(--border-radius);
240
+ background-color: #fff;
241
+ }
242
+
243
+ .folder-tree {
244
+ margin-left: 1rem;
245
+ min-height: 50px;
246
+ }
247
+
248
+ .folder {
249
+ margin: 0.5rem 0;
250
+ padding-left: 1.5rem;
251
+ position: relative;
252
+ }
253
+
254
+ .folder-name {
255
+ font-weight: 500;
256
+ color: var(--primary-color);
257
+ cursor: pointer;
258
+ display: flex;
259
+ align-items: center;
260
+ gap: 0.5rem;
261
+ }
262
+
263
+ .folder-icon {
264
+ color: var(--primary-color);
265
+ font-size: 1.2em;
266
+ }
267
+
268
+ .folder-contents {
269
+ margin-left: 1.5rem;
270
+ padding-left: 1rem;
271
+ border-left: 2px solid var(--accent-color);
272
+ display: none;
273
+ }
274
+
275
+ .folder-contents.expanded {
276
+ display: block;
277
+ animation: fadeIn 0.3s ease-out;
278
+ }
279
+
280
+ .file-item {
281
+ display: flex;
282
+ justify-content: space-between;
283
+ align-items: center;
284
+ padding: 0.5rem;
285
+ margin: 0.25rem 0;
286
+ background-color: #f8f9fa;
287
+ border-radius: 4px;
288
+ transition: background-color 0.2s;
289
+ }
290
+
291
+ .file-item:hover {
292
+ background-color: #e9ecef;
293
+ }
294
+
295
+ .file-name {
296
+ display: flex;
297
+ align-items: center;
298
+ gap: 0.5rem;
299
+ }
300
+
301
+ .file-icon {
302
+ color: #666;
303
+ }
304
+
305
+ .subfolder {
306
+ margin-left: 1.5rem;
307
+ border-left: 2px solid var(--accent-color);
308
+ padding-left: 1rem;
309
+ }
310
+
311
+ .file-count {
312
+ color: #666;
313
+ font-size: 0.9em;
314
+ margin-left: 0.5rem;
315
+ }
316
+
317
+ .file-info {
318
+ margin-top: 0.5rem;
319
+ padding: 0.5rem;
320
+ background-color: #e3f2fd;
321
+ border-radius: var(--border-radius);
322
+ display: none;
323
+ }
324
+
325
+ .no-files-message {
326
+ color: #666;
327
+ font-style: italic;
328
+ padding: 1rem;
329
+ text-align: center;
330
+ }
331
+
332
+ /* Add these CSS rules */
333
+ .csv-upload-visible {
334
+ display: block !important;
335
+ }
336
+
337
+ #csv-upload {
338
+ margin-top: 1rem;
339
+ margin-bottom: 1rem;
340
+ }
341
+
342
+ .upload-section {
343
+ margin: 20px 0;
344
+ padding: 20px;
345
+ border: 2px dashed #4361ee;
346
+ border-radius: 8px;
347
+ background: #f8f9fa;
348
+ display: none; /* Hide by default */
349
+ }
350
+
351
+ .upload-section.active {
352
+ display: block;
353
+ }
354
+
355
+ @keyframes fadeIn {
356
+ from {
357
+ opacity: 0;
358
+ transform: translateY(-10px);
359
+ }
360
+ to {
361
+ opacity: 1;
362
+ transform: translateY(0);
363
+ }
364
+ }
365
+
366
+ /* Remove conflicting hidden class */
367
+ .hidden {
368
+ display: none !important;
369
+ }
370
+
371
+ .file-input-container {
372
+ margin-top: 10px;
373
+ }
374
+
375
+ .helper-text {
376
+ color: #666;
377
+ font-size: 14px;
378
+ margin-top: 5px;
379
+ margin-bottom: 0;
380
+ }
381
+
382
+ /* Add these styles to your existing CSS */
383
+ .answers-header {
384
+ margin-bottom: 2rem;
385
+ border-bottom: 2px solid var(--accent-color);
386
+ padding-bottom: 1rem;
387
+ }
388
+
389
+ .answers-section {
390
+ display: flex;
391
+ flex-direction: column;
392
+ gap: 1.5rem;
393
+ margin-bottom: 2rem;
394
+ }
395
+
396
+ .answer-container {
397
+ background: #f8f9fa;
398
+ padding: 1.5rem;
399
+ border-radius: var(--border-radius);
400
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
401
+ }
402
+
403
+ .answer-container label {
404
+ display: block;
405
+ margin-bottom: 0.8rem;
406
+ color: var(--primary-color);
407
+ font-weight: 500;
408
+ }
409
+
410
+ .answer-box {
411
+ width: 100%;
412
+ min-height: 100px;
413
+ padding: 1rem;
414
+ border: 1px solid #ddd;
415
+ border-radius: var(--border-radius);
416
+ font-family: 'Poppins', sans-serif;
417
+ font-size: 0.95rem;
418
+ line-height: 1.6;
419
+ resize: vertical;
420
+ transition: all 0.3s ease;
421
+ background: white;
422
+ }
423
+
424
+ .answer-box:focus {
425
+ outline: none;
426
+ border-color: var(--accent-color);
427
+ box-shadow: 0 0 0 3px rgba(72, 149, 239, 0.1);
428
+ }
429
+
430
+ .save-answers-btn {
431
+ background-color: var(--accent-color);
432
+ color: white;
433
+ border: none;
434
+ padding: 0.8rem 1.5rem;
435
+ border-radius: var(--border-radius);
436
+ cursor: pointer;
437
+ font-weight: 500;
438
+ transition: all 0.3s ease;
439
+ margin-top: 1rem;
440
+ display: block;
441
+ width: auto;
442
+ min-width: 150px;
443
+ }
444
+
445
+ .save-answers-btn:hover {
446
+ background-color: var(--primary-color);
447
+ transform: translateY(-2px);
448
+ }
449
+
450
+ .helper-text {
451
+ color: #666;
452
+ font-size: 0.9rem;
453
+ margin-top: 0.5rem;
454
+ }
455
+
456
+ /* Add responsive styles */
457
+ @media (max-width: 768px) {
458
+ .answer-container {
459
+ padding: 1rem;
460
+ }
461
+
462
+ .answer-box {
463
+ min-height: 80px;
464
+ }
465
+ }
466
+
467
+ /* Add to your existing CSS */
468
+ table {
469
+ width: 100%;
470
+ margin-top: 1rem;
471
+ border-collapse: collapse;
472
+ }
473
+
474
+ th, td {
475
+ padding: 0.75rem;
476
+ text-align: left;
477
+ border-bottom: 1px solid #dee2e6;
478
+ }
479
+
480
+ th {
481
+ background-color: #4361ee;
482
+ color: white;
483
+ font-weight: 500;
484
+ }
485
+
486
+ tr:hover {
487
+ background-color: #f8f9fa;
488
+ }
489
+
490
+ .marks-summary {
491
+ margin-top: 1rem;
492
+ padding: 1rem;
493
+ background-color: #e3f2fd;
494
+ border-radius: 8px;
495
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
496
+ }
497
+
498
+ .marks-summary h4 {
499
+ margin-top: 0;
500
+ color: #4361ee;
501
+ }
502
+
503
+ .loading-overlay {
504
+ position: fixed;
505
+ top: 0;
506
+ left: 0;
507
+ width: 100%;
508
+ height: 100%;
509
+ background: rgba(0, 0, 0, 0.7);
510
+ display: none;
511
+ justify-content: center;
512
+ align-items: center;
513
+ z-index: 1000;
514
+ }
515
+
516
+ .loading-spinner {
517
+ width: 50px;
518
+ height: 50px;
519
+ border: 5px solid #f3f3f3;
520
+ border-top: 5px solid #4361ee;
521
+ border-radius: 50%;
522
+ animation: spin 1s linear infinite;
523
+ }
524
+
525
+ .loading-text {
526
+ color: white;
527
+ margin-top: 20px;
528
+ font-size: 18px;
529
+ }
530
+
531
+ @keyframes spin {
532
+ 0% { transform: rotate(0deg); }
533
+ 100% { transform: rotate(360deg); }
534
+ }
535
+
536
+ #marks-table-container {
537
+ margin-top: 20px;
538
+ overflow-x: auto;
539
+ }
540
+
541
+ #marks-table {
542
+ width: 100%;
543
+ border-collapse: collapse;
544
+ margin-top: 20px;
545
+ background: white;
546
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
547
+ }
548
+
549
+ #marks-table th,
550
+ #marks-table td {
551
+ padding: 12px;
552
+ text-align: left;
553
+ border-bottom: 1px solid #ddd;
554
+ }
555
+
556
+ #marks-table th {
557
+ background-color: var(--primary-color);
558
+ color: white;
559
+ font-weight: 500;
560
+ }
561
+
562
+ #marks-table tr:hover {
563
+ background-color: #f5f5f5;
564
+ }
565
+
566
+ #marks-table tbody tr:nth-child(even) {
567
+ background-color: #f8f9fa;
568
+ }
569
+
570
+ /* Notification System Styles */
571
+ .notification-container {
572
+ position: fixed;
573
+ top: 20px;
574
+ right: 20px;
575
+ z-index: 1000;
576
+ max-width: 400px;
577
+ max-height: 80vh;
578
+ overflow-y: auto;
579
+ }
580
+
581
+ .notification {
582
+ padding: 15px 20px;
583
+ margin-bottom: 10px;
584
+ border-radius: 8px;
585
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
586
+ animation: slideIn 0.3s ease-out;
587
+ position: relative;
588
+ display: flex;
589
+ justify-content: space-between;
590
+ align-items: flex-start;
591
+ }
592
+
593
+ .notification.info {
594
+ background-color: #e3f2fd;
595
+ border-left: 4px solid #2196f3;
596
+ color: #0d47a1;
597
+ }
598
+
599
+ .notification.success {
600
+ background-color: #e8f5e9;
601
+ border-left: 4px solid #4caf50;
602
+ color: #1b5e20;
603
+ }
604
+
605
+ .notification.warning {
606
+ background-color: #fff3e0;
607
+ border-left: 4px solid #ff9800;
608
+ color: #e65100;
609
+ }
610
+
611
+ .notification.error {
612
+ background-color: #ffebee;
613
+ border-left: 4px solid #f44336;
614
+ color: #b71c1c;
615
+ }
616
+
617
+ .notification-content {
618
+ flex-grow: 1;
619
+ margin-right: 10px;
620
+ word-break: break-word;
621
+ }
622
+
623
+ .notification-close {
624
+ background: none;
625
+ border: none;
626
+ color: inherit;
627
+ cursor: pointer;
628
+ font-size: 20px;
629
+ padding: 0 5px;
630
+ opacity: 0.7;
631
+ }
632
+
633
+ .notification-close:hover {
634
+ opacity: 1;
635
+ }
636
+
637
+ @keyframes slideIn {
638
+ from {
639
+ transform: translateX(100%);
640
+ opacity: 0;
641
+ }
642
+ to {
643
+ transform: translateX(0);
644
+ opacity: 1;
645
+ }
646
+ }
647
+
648
+ @keyframes slideOut {
649
+ from {
650
+ transform: translateX(0);
651
+ opacity: 1;
652
+ }
653
+ to {
654
+ transform: translateX(100%);
655
+ opacity: 0;
656
+ }
657
+ }
658
+
659
+ .notification.fade-out {
660
+ animation: slideOut 0.3s ease-out forwards;
661
+ }
662
+
663
+ /* Add these styles to your existing CSS */
664
+ .log-container {
665
+ background: #1e1e1e;
666
+ border-radius: var(--border-radius);
667
+ padding: 1rem;
668
+ margin-top: 1rem;
669
+ position: relative;
670
+ }
671
+
672
+ .log-display {
673
+ background: #1e1e1e;
674
+ color: #fff;
675
+ font-family: 'Consolas', 'Monaco', monospace;
676
+ font-size: 14px;
677
+ line-height: 1.5;
678
+ padding: 1rem;
679
+ border-radius: 4px;
680
+ height: 300px;
681
+ overflow-y: auto;
682
+ white-space: pre-wrap;
683
+ word-wrap: break-word;
684
+ }
685
+
686
+ .refresh-logs-btn {
687
+ position: absolute;
688
+ top: 1rem;
689
+ right: 1rem;
690
+ background: var(--accent-color);
691
+ color: white;
692
+ border: none;
693
+ padding: 0.5rem 1rem;
694
+ border-radius: 4px;
695
+ cursor: pointer;
696
+ font-size: 14px;
697
+ }
698
+
699
+ .refresh-logs-btn:hover {
700
+ background: var(--primary-color);
701
+ }
702
+
703
+ .log-entry {
704
+ margin: 0;
705
+ padding: 2px 0;
706
+ }
707
+
708
+ .log-entry.info {
709
+ color: #4caf50;
710
+ }
711
+
712
+ .log-entry.error {
713
+ color: #f44336;
714
+ }
715
+
716
+ .log-entry.warning {
717
+ color: #ff9800;
718
+ }
719
+
720
+ .log-timestamp {
721
+ color: #888;
722
+ font-size: 0.9em;
723
+ margin-right: 8px;
724
+ }
725
+ </style>
726
+ </head>
727
+ <body>
728
+ <div class="notification-container" id="notification-container"></div>
729
+
730
+ <div class="container">
731
+ <div class="section">
732
+ <h2>Upload Query CSV File</h2>
733
+ <div id="query-upload">
734
+ <div class="upload-container">
735
+ <label for="query-file">Query File:</label>
736
+ <input type="file" id="query-file" accept=".csv">
737
+ <p class="helper-text">Upload your query CSV file</p>
738
+ </div>
739
+ </div>
740
+ </div>
741
+
742
+ <div class="section">
743
+ <h2>Answer Generation</h2>
744
+ <div class="file-type-selection">
745
+ <label for="file-type">Select File Type:</label>
746
+ <select id="file-type" class="file-type-select">
747
+ <option value="pdf">PDF</option>
748
+ <option value="csv">CSV</option>
749
+ </select>
750
+ </div>
751
+
752
+ <!-- PDF Section -->
753
+ <div id="pdf-section" class="upload-section active">
754
+ <label>Upload PDF Files:</label>
755
+ <div class="file-input-container">
756
+ <input type="file" id="pdf-files" accept=".pdf" multiple>
757
+ <p class="helper-text">Please upload at least 2 PDF files</p>
758
+ </div>
759
+ </div>
760
+
761
+ <!-- CSV Section -->
762
+ <div id="csv-section" class="upload-section">
763
+ <label>Upload CSV File:</label>
764
+ <div class="file-input-container">
765
+ <input type="file" id="csv-file" accept=".csv">
766
+ <p class="helper-text">Upload a single CSV file</p>
767
+ </div>
768
+ </div>
769
+
770
+ <button id="compute-btn" onclick="computeAnswers()">Compute Answers</button>
771
+ </div>
772
+
773
+ <div class="section">
774
+ <h2>Student Answers Upload</h2>
775
+ <div class="upload-methods">
776
+ <div class="upload-method">
777
+ <label for="folder-upload">Upload Main Folder:</label>
778
+ <div class="upload-container">
779
+ <input type="file" id="folder-upload" webkitdirectory directory multiple>
780
+ <small class="help-text">Select the main folder containing student folders with answer images</small>
781
+ </div>
782
+ </div>
783
+ </div>
784
+ <div class="folder-structure">
785
+ <h3>Uploaded Files Structure:</h3>
786
+ <div id="folder-tree" class="folder-tree"></div>
787
+ </div>
788
+ <div id="file-list" class="file-list"></div>
789
+ </div>
790
+
791
+ <div class="section">
792
+ <div id="answers-container"></div>
793
+ <button id="compute-marks-btn" onclick="computeMarks()">Compute Marks</button>
794
+ <div id="marks-table-container">
795
+ <table id="marks-table">
796
+ <thead>
797
+ <tr>
798
+ <th>Student Folder</th>
799
+ <th>Image Name</th>
800
+ <th>Marks</th>
801
+ </tr>
802
+ </thead>
803
+ <tbody id="marks-table-body">
804
+ </tbody>
805
+ </table>
806
+ </div>
807
+ </div>
808
+
809
+ <div class="section">
810
+ <h2>Server Logs</h2>
811
+ <div class="log-container">
812
+ <div id="log-display" class="log-display"></div>
813
+ <button onclick="refreshLogs()" class="refresh-logs-btn">Refresh Logs</button>
814
+ </div>
815
+ </div>
816
+ </div>
817
+
818
+ <div class="loading-overlay" id="loading-overlay">
819
+ <div style="text-align: center;">
820
+ <div class="loading-spinner"></div>
821
+ <div class="loading-text">Processing... This may take a few minutes.</div>
822
+ </div>
823
+ </div>
824
+
825
+ <script>
826
+ const notificationSystem = {
827
+ container: null,
828
+ init() {
829
+ this.container = document.getElementById('notification-container');
830
+ },
831
+
832
+ show(message, type = 'info', duration = 5000) {
833
+ if (!this.container) this.init();
834
+
835
+ const notification = document.createElement('div');
836
+ notification.className = `notification ${type}`;
837
+
838
+ const content = document.createElement('div');
839
+ content.className = 'notification-content';
840
+ content.textContent = message;
841
+
842
+ const closeBtn = document.createElement('button');
843
+ closeBtn.className = 'notification-close';
844
+ closeBtn.innerHTML = '&times;';
845
+ closeBtn.onclick = () => this.remove(notification);
846
+
847
+ notification.appendChild(content);
848
+ notification.appendChild(closeBtn);
849
+ this.container.appendChild(notification);
850
+
851
+ if (duration > 0) {
852
+ setTimeout(() => this.remove(notification), duration);
853
+ }
854
+
855
+ return notification;
856
+ },
857
+
858
+ remove(notification) {
859
+ notification.classList.add('fade-out');
860
+ setTimeout(() => {
861
+ if (notification.parentElement === this.container) {
862
+ this.container.removeChild(notification);
863
+ }
864
+ }, 300);
865
+ },
866
+
867
+ success(message, duration = 5000) {
868
+ return this.show(message, 'success', duration);
869
+ },
870
+
871
+ error(message, duration = 8000) {
872
+ return this.show(message, 'error', duration);
873
+ },
874
+
875
+ warning(message, duration = 6000) {
876
+ return this.show(message, 'warning', duration);
877
+ },
878
+
879
+ info(message, duration = 4000) {
880
+ return this.show(message, 'info', duration);
881
+ }
882
+ };
883
+
884
+ document.addEventListener('DOMContentLoaded', function() {
885
+ const fileTypeSelect = document.getElementById('file-type');
886
+ const pdfSection = document.getElementById('pdf-section');
887
+ const csvSection = document.getElementById('csv-section');
888
+
889
+ function handleFileTypeChange() {
890
+ const selectedValue = fileTypeSelect.value;
891
+ console.log('Selected value:', selectedValue);
892
+
893
+ // Remove active class from both sections
894
+ pdfSection.classList.remove('active');
895
+ csvSection.classList.remove('active');
896
+
897
+ // Clear file inputs
898
+ document.getElementById('pdf-files').value = '';
899
+ document.getElementById('csv-file').value = '';
900
+
901
+ // Add active class to selected section
902
+ if (selectedValue === 'csv') {
903
+ csvSection.classList.add('active');
904
+ } else {
905
+ pdfSection.classList.add('active');
906
+ }
907
+ }
908
+
909
+ // Add event listener for file type change
910
+ fileTypeSelect.addEventListener('change', handleFileTypeChange);
911
+
912
+ // Add event listener for PDF files
913
+ document.getElementById('pdf-files').addEventListener('change', function(e) {
914
+ if (e.target.files.length < 2) {
915
+ alert('Please select at least 2 PDF files');
916
+ this.value = '';
917
+ } else {
918
+ console.log(`Selected ${e.target.files.length} PDF files`);
919
+ }
920
+ });
921
+
922
+ // Initial state setup
923
+ handleFileTypeChange();
924
+ });
925
+
926
+ function showLoading() {
927
+ document.getElementById('loading-overlay').style.display = 'flex';
928
+ }
929
+
930
+ function hideLoading() {
931
+ document.getElementById('loading-overlay').style.display = 'none';
932
+ }
933
+
934
+ async function computeAnswers() {
935
+ try {
936
+ showLoading();
937
+ const fileType = document.getElementById('file-type').value;
938
+ const queryfile = document.getElementById('query-file').files[0];
939
+ const anscsvFile = document.getElementById('csv-file').files[0];
940
+ const pdfFiles = document.getElementById('pdf-files').files;
941
+
942
+ if (!queryfile) {
943
+ notificationSystem.error("Please upload a query file first!");
944
+ hideLoading();
945
+ return;
946
+ }
947
+
948
+ notificationSystem.info("Processing files...");
949
+ const formData = new FormData();
950
+ formData.append('file_type', fileType);
951
+ formData.append('query_file', queryfile);
952
+
953
+ if (fileType === 'csv') {
954
+ if (!anscsvFile) {
955
+ notificationSystem.error("Please upload a CSV file for answers!");
956
+ hideLoading();
957
+ return;
958
+ }
959
+ formData.append('ans_csv_file', anscsvFile);
960
+ notificationSystem.info("Processing CSV file...");
961
+ } else if (fileType === 'pdf') {
962
+ if (!pdfFiles || pdfFiles.length < 2) {
963
+ notificationSystem.error("Please upload at least 2 PDF files!");
964
+ hideLoading();
965
+ return;
966
+ }
967
+ for (let file of pdfFiles) {
968
+ formData.append('pdf_files[]', file);
969
+ }
970
+ notificationSystem.info(`Processing ${pdfFiles.length} PDF files...`);
971
+ }
972
+
973
+ const computeBtn = document.getElementById('compute-btn');
974
+ computeBtn.disabled = true;
975
+
976
+ const response = await fetch('/compute_answers', {
977
+ method: 'POST',
978
+ body: formData
979
+ });
980
+
981
+ if (!response.ok) {
982
+ throw new Error('Server returned an error response');
983
+ }
984
+
985
+ const result = await response.json();
986
+ if (result.error) {
987
+ throw new Error(result.error);
988
+ }
989
+
990
+ if (result.answers) {
991
+ displayAnswers(result.answers);
992
+ notificationSystem.success("Successfully generated answers!");
993
+ } else {
994
+ throw new Error('No answers received from server');
995
+ }
996
+
997
+ } catch (error) {
998
+ console.error('Error:', error);
999
+ notificationSystem.error('Error: ' + error.message);
1000
+ } finally {
1001
+ hideLoading();
1002
+ const computeBtn = document.getElementById('compute-btn');
1003
+ computeBtn.disabled = false;
1004
+ }
1005
+ }
1006
+
1007
+ function displayAnswers(answers) {
1008
+ const container = document.getElementById('answers-container');
1009
+ container.innerHTML = '';
1010
+
1011
+ // Add header
1012
+ const header = document.createElement('div');
1013
+ header.className = 'answers-header';
1014
+ header.innerHTML = `
1015
+ <h3>Generated Answers</h3>
1016
+ <p class="helper-text">Review and edit answers if needed</p>
1017
+ `;
1018
+ container.appendChild(header);
1019
+
1020
+ // Create answers section
1021
+ const answersSection = document.createElement('div');
1022
+ answersSection.className = 'answers-section';
1023
+
1024
+ // Handle both single and multiple answer formats
1025
+ if (Array.isArray(answers[0])) {
1026
+ answers.forEach((answerSet, index) => {
1027
+ const answerContainer = document.createElement('div');
1028
+ answerContainer.className = 'answer-container';
1029
+
1030
+ const label = document.createElement('label');
1031
+ label.textContent = `Question ${index + 1} Answer:`;
1032
+
1033
+ const textBox = document.createElement('textarea');
1034
+ textBox.className = 'answer-box';
1035
+ textBox.value = Array.isArray(answerSet) ? answerSet.join('\n\n') : answerSet;
1036
+
1037
+ answerContainer.appendChild(label);
1038
+ answerContainer.appendChild(textBox);
1039
+ answersSection.appendChild(answerContainer);
1040
+ });
1041
+ } else {
1042
+ const textBox = document.createElement('textarea');
1043
+ textBox.className = 'answer-box';
1044
+ textBox.value = answers.join('\n\n');
1045
+ answersSection.appendChild(textBox);
1046
+ }
1047
+
1048
+ container.appendChild(answersSection);
1049
+ }
1050
+
1051
+ let selectedFiles = new Map(); // To store all selected files
1052
+ let folderStructure = new Map(); // To store folder structure
1053
+
1054
+ function createFolderStructure(files) {
1055
+ const structure = new Map();
1056
+
1057
+ for (let file of files) {
1058
+ if (file.type.startsWith('image/')) {
1059
+ const pathParts = file.webkitRelativePath.split('/');
1060
+ let currentLevel = structure;
1061
+ let path = '';
1062
+
1063
+ // Skip the first part as it's the root folder
1064
+ for (let i = 1; i < pathParts.length - 1; i++) {
1065
+ const folderName = pathParts[i];
1066
+ path = path ? `${path}/${folderName}` : folderName;
1067
+
1068
+ if (!currentLevel.has(folderName)) {
1069
+ currentLevel.set(folderName, {
1070
+ type: 'folder',
1071
+ name: folderName,
1072
+ path: path,
1073
+ contents: new Map()
1074
+ });
1075
+ }
1076
+ currentLevel = currentLevel.get(folderName).contents;
1077
+ }
1078
+
1079
+ // Add the file
1080
+ const fileName = pathParts[pathParts.length - 1];
1081
+ currentLevel.set(fileName, {
1082
+ type: 'file',
1083
+ name: fileName,
1084
+ path: `${path}/${fileName}`,
1085
+ file: file
1086
+ });
1087
+ }
1088
+ }
1089
+
1090
+ return structure;
1091
+ }
1092
+
1093
+ function renderFolderStructure(structure, container, level = 0) {
1094
+ structure.forEach((item, name) => {
1095
+ if (item.type === 'folder') {
1096
+ const folderDiv = document.createElement('div');
1097
+ folderDiv.className = 'folder';
1098
+
1099
+ const folderHeader = document.createElement('div');
1100
+ folderHeader.className = 'folder-name';
1101
+ const fileCount = countFiles(item.contents);
1102
+ folderHeader.innerHTML = `
1103
+ <span class="folder-icon">📁</span>
1104
+ ${name}
1105
+ <span class="file-count">(${fileCount} files)</span>
1106
+ <button class="toggle-btn" onclick="toggleFolder(this)">▼</button>
1107
+ `;
1108
+
1109
+ const contentsDiv = document.createElement('div');
1110
+ contentsDiv.className = 'folder-contents expanded';
1111
+
1112
+ folderDiv.appendChild(folderHeader);
1113
+ folderDiv.appendChild(contentsDiv);
1114
+ container.appendChild(folderDiv);
1115
+
1116
+ renderFolderStructure(item.contents, contentsDiv, level + 1);
1117
+ } else {
1118
+ const fileDiv = document.createElement('div');
1119
+ fileDiv.className = 'file-item';
1120
+ fileDiv.innerHTML = `
1121
+ <div class="file-name">
1122
+ <span class="file-icon">📄</span>
1123
+ ${name}
1124
+ </div>
1125
+ <span class="remove-file" onclick="removeFile('${item.path}')">&times;</span>
1126
+ `;
1127
+ container.appendChild(fileDiv);
1128
+ }
1129
+ });
1130
+ }
1131
+
1132
+ function countFiles(structure) {
1133
+ let count = 0;
1134
+ structure.forEach(item => {
1135
+ if (item.type === 'file') {
1136
+ count++;
1137
+ } else {
1138
+ count += countFiles(item.contents);
1139
+ }
1140
+ });
1141
+ return count;
1142
+ }
1143
+
1144
+ function toggleFolder(button) {
1145
+ const contents = button.closest('.folder-name').nextElementSibling;
1146
+ contents.classList.toggle('expanded');
1147
+ button.textContent = contents.classList.contains('expanded') ? '▼' : '▶';
1148
+ }
1149
+
1150
+ // Handle folder upload
1151
+ document.getElementById('folder-upload').addEventListener('change', (event) => {
1152
+ const files = event.target.files;
1153
+
1154
+ // Clear previous files
1155
+ selectedFiles.clear();
1156
+
1157
+ // Validate file types
1158
+ const validImageTypes = ['image/jpeg', 'image/jpg', 'image/png'];
1159
+ let invalidFiles = [];
1160
+
1161
+ for (let file of files) {
1162
+ if (file.type.startsWith('image/')) {
1163
+ if (!validImageTypes.includes(file.type)) {
1164
+ invalidFiles.push(file.name);
1165
+ } else {
1166
+ const pathParts = file.webkitRelativePath.split('/');
1167
+ // Store the full relative path
1168
+ const fullPath = file.webkitRelativePath;
1169
+ selectedFiles.set(fullPath, {
1170
+ file: file,
1171
+ mainFolder: pathParts[0],
1172
+ studentFolder: pathParts[1],
1173
+ fileName: pathParts[pathParts.length - 1],
1174
+ fullPath: fullPath
1175
+ });
1176
+ }
1177
+ }
1178
+ }
1179
+
1180
+ if (invalidFiles.length > 0) {
1181
+ alert(`Warning: The following files are not valid image files (only .jpg, .jpeg, and .png are allowed):\n${invalidFiles.join('\n')}`);
1182
+ }
1183
+
1184
+ folderStructure = createFolderStructure(Array.from(selectedFiles.values()).map(info => info.file));
1185
+
1186
+ // Update the visual tree
1187
+ const treeContainer = document.getElementById('folder-tree');
1188
+ treeContainer.innerHTML = '';
1189
+
1190
+ if (selectedFiles.size === 0) {
1191
+ treeContainer.innerHTML = '<div class="no-files-message">No valid image files uploaded yet</div>';
1192
+ return;
1193
+ }
1194
+
1195
+ renderFolderStructure(folderStructure, treeContainer);
1196
+
1197
+ // Show immediate feedback
1198
+ alert(`Successfully loaded ${selectedFiles.size} valid image files`);
1199
+ });
1200
+
1201
+ function removeFile(path) {
1202
+ selectedFiles.delete(path);
1203
+ // Rebuild the folder structure
1204
+ folderStructure = createFolderStructure(Array.from(selectedFiles.values()).map(info => info.file));
1205
+ const treeContainer = document.getElementById('folder-tree');
1206
+ treeContainer.innerHTML = '';
1207
+ if (selectedFiles.size === 0) {
1208
+ treeContainer.innerHTML = '<div class="no-files-message">No files uploaded yet</div>';
1209
+ } else {
1210
+ renderFolderStructure(folderStructure, treeContainer);
1211
+ }
1212
+ }
1213
+
1214
+ // Add these functions to handle file selection feedback
1215
+ document.getElementById('query-file').addEventListener('change', (event) => {
1216
+ const file = event.target.files[0];
1217
+ const fileInfo = document.getElementById('query-file-info');
1218
+ if (file) {
1219
+ fileInfo.style.display = 'block';
1220
+ fileInfo.textContent = `Selected file: ${file.name}`;
1221
+ } else {
1222
+ fileInfo.style.display = 'none';
1223
+ }
1224
+ });
1225
+
1226
+ async function computeMarks() {
1227
+ try {
1228
+ showLoading();
1229
+ const answerBoxes = document.querySelectorAll('.answer-box');
1230
+
1231
+ if (answerBoxes.length === 0) {
1232
+ notificationSystem.error("Please generate answers first!");
1233
+ hideLoading();
1234
+ return;
1235
+ }
1236
+
1237
+ const answers = Array.from(answerBoxes).map(box => box.value.trim());
1238
+
1239
+ if (answers.some(answer => !answer)) {
1240
+ notificationSystem.error("Please ensure all answer boxes are filled!");
1241
+ hideLoading();
1242
+ return;
1243
+ }
1244
+
1245
+ if (selectedFiles.size === 0) {
1246
+ notificationSystem.error("Please upload student answer files!");
1247
+ hideLoading();
1248
+ return;
1249
+ }
1250
+
1251
+ const formData = new FormData();
1252
+ formData.append('answers', JSON.stringify(answers));
1253
+
1254
+ // Add files with their folder structure
1255
+ selectedFiles.forEach((fileInfo, path) => {
1256
+ if (fileInfo.file.type.startsWith('image/')) {
1257
+ formData.append('file', fileInfo.file, fileInfo.fullPath);
1258
+ }
1259
+ });
1260
+
1261
+ const response = await fetch('/compute_marks', {
1262
+ method: 'POST',
1263
+ body: formData
1264
+ });
1265
+
1266
+ if (!response.ok) {
1267
+ throw new Error('Server returned an error response');
1268
+ }
1269
+
1270
+ const result = await response.json();
1271
+
1272
+ if (result.error) {
1273
+ throw new Error(result.error);
1274
+ }
1275
+
1276
+ if (result.results) {
1277
+ displayMarks(result.results);
1278
+ notificationSystem.success("Successfully computed marks!");
1279
+ } else {
1280
+ throw new Error('No results received from server');
1281
+ }
1282
+
1283
+ } catch (error) {
1284
+ console.error('Error:', error);
1285
+ notificationSystem.error(error.message);
1286
+ } finally {
1287
+ hideLoading();
1288
+ }
1289
+ }
1290
+
1291
+ function displayMarks(results) {
1292
+ const tableBody = document.getElementById('marks-table-body');
1293
+ tableBody.innerHTML = ''; // Clear existing rows
1294
+
1295
+ // Sort results by subfolder and image name
1296
+ const sortedResults = results.sort((a, b) => {
1297
+ if (a.subfolder !== b.subfolder) {
1298
+ return a.subfolder.localeCompare(b.subfolder);
1299
+ }
1300
+ return a.image.localeCompare(b.image);
1301
+ });
1302
+
1303
+ // Group results by subfolder
1304
+ const groupedResults = {};
1305
+ sortedResults.forEach(result => {
1306
+ if (!groupedResults[result.subfolder]) {
1307
+ groupedResults[result.subfolder] = [];
1308
+ }
1309
+ groupedResults[result.subfolder].push(result);
1310
+ });
1311
+
1312
+ // Create summary section
1313
+ const summarySection = document.createElement('div');
1314
+ summarySection.className = 'marks-summary';
1315
+ summarySection.innerHTML = `
1316
+ <h4>Processing Summary</h4>
1317
+ <p>Total students processed: ${Object.keys(groupedResults).length}</p>
1318
+ <p>Total answers evaluated: ${results.length}</p>
1319
+ `;
1320
+
1321
+ // Insert summary before the table
1322
+ const tableContainer = document.getElementById('marks-table-container');
1323
+ tableContainer.insertBefore(summarySection, tableContainer.firstChild);
1324
+
1325
+ // Display results in table
1326
+ Object.entries(groupedResults).forEach(([subfolder, folderResults]) => {
1327
+ folderResults.forEach(result => {
1328
+ const row = document.createElement('tr');
1329
+ row.innerHTML = `
1330
+ <td>${result.subfolder}</td>
1331
+ <td>${result.image}</td>
1332
+ <td>${result.marks.toFixed(2)}</td>
1333
+ `;
1334
+ tableBody.appendChild(row);
1335
+ });
1336
+ });
1337
+ }
1338
+
1339
+ // Add this function to fetch and display logs
1340
+ async function refreshLogs() {
1341
+ try {
1342
+ const logDisplay = document.getElementById('log-display');
1343
+ if (!logDisplay) {
1344
+ console.error('Log display element not found');
1345
+ return;
1346
+ }
1347
+
1348
+ // Add loading indicator
1349
+ logDisplay.innerHTML = '<div class="log-entry info">Loading logs...</div>';
1350
+
1351
+ const response = await fetch('/check_logs');
1352
+ let data;
1353
+ const contentType = response.headers.get('content-type');
1354
+ if (contentType && contentType.includes('application/json')) {
1355
+ data = await response.json();
1356
+ } else {
1357
+ throw new Error('Server returned non-JSON response');
1358
+ }
1359
+
1360
+ if (!response.ok) {
1361
+ throw new Error(data.message || data.error || `Failed to fetch logs: ${response.status}`);
1362
+ }
1363
+
1364
+ if (data.status === 'error') {
1365
+ throw new Error(data.error);
1366
+ }
1367
+
1368
+ if (!data.logs) {
1369
+ throw new Error('No logs received from server');
1370
+ }
1371
+
1372
+ // Clear existing logs
1373
+ logDisplay.innerHTML = '';
1374
+
1375
+ // Split logs into lines and format them
1376
+ const logLines = data.logs.split('\n').filter(line => line.trim());
1377
+ if (logLines.length === 0) {
1378
+ logDisplay.innerHTML = '<div class="log-entry info">No logs available yet.</div>';
1379
+ return;
1380
+ }
1381
+
1382
+ logLines.forEach(line => {
1383
+ const logEntry = document.createElement('div');
1384
+ logEntry.className = 'log-entry';
1385
+
1386
+ // Determine log level and apply appropriate styling
1387
+ if (line.includes('ERROR')) {
1388
+ logEntry.classList.add('error');
1389
+ } else if (line.includes('WARNING')) {
1390
+ logEntry.classList.add('warning');
1391
+ } else {
1392
+ logEntry.classList.add('info');
1393
+ }
1394
+
1395
+ // Add timestamp if present
1396
+ const timestampMatch = line.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3})/);
1397
+ if (timestampMatch) {
1398
+ const timestamp = timestampMatch[1];
1399
+ const message = line.substring(timestamp.length + 2);
1400
+ logEntry.innerHTML = `<span class="log-timestamp">${timestamp}</span> ${message}`;
1401
+ } else {
1402
+ logEntry.textContent = line;
1403
+ }
1404
+
1405
+ logDisplay.appendChild(logEntry);
1406
+ });
1407
+
1408
+ // Scroll to bottom
1409
+ logDisplay.scrollTop = logDisplay.scrollHeight;
1410
+
1411
+ } catch (error) {
1412
+ console.error('Error fetching logs:', error);
1413
+ const logDisplay = document.getElementById('log-display');
1414
+ if (logDisplay) {
1415
+ logDisplay.innerHTML = `<div class="log-entry error">Error loading logs: ${error.message}</div>`;
1416
+ }
1417
+ notificationSystem.error('Failed to fetch logs: ' + error.message);
1418
+ }
1419
+ }
1420
+
1421
+ // Add auto-refresh for logs with error handling
1422
+ let logRefreshInterval;
1423
+ let consecutiveErrors = 0;
1424
+ const MAX_CONSECUTIVE_ERRORS = 3;
1425
+
1426
+ function startLogRefresh() {
1427
+ // Initial fetch
1428
+ refreshLogs();
1429
+
1430
+ // Set up interval for auto-refresh
1431
+ logRefreshInterval = setInterval(async () => {
1432
+ try {
1433
+ await refreshLogs();
1434
+ consecutiveErrors = 0; // Reset error counter on success
1435
+ } catch (error) {
1436
+ consecutiveErrors++;
1437
+ if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
1438
+ stopLogRefresh();
1439
+ notificationSystem.error('Stopped auto-refreshing logs due to multiple errors');
1440
+ }
1441
+ }
1442
+ }, 5000); // Refresh every 5 seconds
1443
+ }
1444
+
1445
+ function stopLogRefresh() {
1446
+ if (logRefreshInterval) {
1447
+ clearInterval(logRefreshInterval);
1448
+ logRefreshInterval = null;
1449
+ }
1450
+ }
1451
+
1452
+ // Start log refresh when page loads
1453
+ document.addEventListener('DOMContentLoaded', function() {
1454
+ startLogRefresh();
1455
+ });
1456
+
1457
+ // Stop log refresh when page is unloaded
1458
+ window.addEventListener('beforeunload', function() {
1459
+ stopLogRefresh();
1460
+ });
1461
+ </script>
1462
+ </body>
1463
+ </html>