Commit
·
3f6ca1b
1
Parent(s):
5ed4851
fix index.html new new
Browse files- main.py +30 -6
- templates/index.html +281 -45
main.py
CHANGED
@@ -322,7 +322,10 @@ def compute_marks():
|
|
322 |
try:
|
323 |
# Get correct answers
|
324 |
correct_answers = request.form.getlist('correct_answers[]')
|
|
|
|
|
325 |
if not correct_answers:
|
|
|
326 |
return jsonify({
|
327 |
"error": "Missing data",
|
328 |
"message": "No correct answers provided"
|
@@ -331,7 +334,9 @@ def compute_marks():
|
|
331 |
# Create TFIDF values for correct answers
|
332 |
try:
|
333 |
max_tfidf = create_tfidf_values(correct_answers)
|
|
|
334 |
except Exception as e:
|
|
|
335 |
return jsonify({
|
336 |
"error": "TFIDF error",
|
337 |
"message": f"Error creating TFIDF values: {str(e)}"
|
@@ -339,7 +344,10 @@ def compute_marks():
|
|
339 |
|
340 |
# Get all uploaded files
|
341 |
files = request.files.getlist('file')
|
|
|
|
|
342 |
if not files:
|
|
|
343 |
return jsonify({
|
344 |
"error": "Missing data",
|
345 |
"message": "No files uploaded"
|
@@ -347,13 +355,14 @@ def compute_marks():
|
|
347 |
|
348 |
# Validate folder structure
|
349 |
is_valid, message = validate_folder_structure(files)
|
|
|
|
|
350 |
if not is_valid:
|
|
|
351 |
return jsonify({
|
352 |
"error": "Invalid folder structure",
|
353 |
"message": message
|
354 |
}), 400
|
355 |
-
|
356 |
-
log_print(f"Folder structure validation: {message}")
|
357 |
|
358 |
# Create a temporary directory for processing
|
359 |
base_temp_dir = tempfile.mkdtemp()
|
@@ -372,6 +381,8 @@ def compute_marks():
|
|
372 |
log_print("Skipping invalid file", "WARNING")
|
373 |
continue
|
374 |
|
|
|
|
|
375 |
# Get folder structure from file path
|
376 |
path_parts = file.filename.split('/')
|
377 |
if len(path_parts) < 2:
|
@@ -380,6 +391,7 @@ def compute_marks():
|
|
380 |
|
381 |
student_folder = path_parts[-2]
|
382 |
filename = path_parts[-1]
|
|
|
383 |
|
384 |
# Validate file type
|
385 |
if not is_valid_image_file(filename):
|
@@ -393,6 +405,7 @@ def compute_marks():
|
|
393 |
# Initialize student results if not exists
|
394 |
if student_folder not in results:
|
395 |
results[student_folder] = {}
|
|
|
396 |
|
397 |
# Save and process file
|
398 |
student_dir = os.path.join(base_temp_dir, student_folder)
|
@@ -402,7 +415,7 @@ def compute_marks():
|
|
402 |
# Ensure filepath is secure
|
403 |
filepath = secure_filename(filepath)
|
404 |
file.save(filepath)
|
405 |
-
log_print(f"Saved file: {filepath}")
|
406 |
|
407 |
# Extract text
|
408 |
extracted_text = extract_text_from_image(filepath)
|
@@ -414,6 +427,8 @@ def compute_marks():
|
|
414 |
})
|
415 |
continue
|
416 |
|
|
|
|
|
417 |
# Clean the extracted text for JSON
|
418 |
extracted_text = extracted_text.encode('ascii', 'ignore').decode('ascii')
|
419 |
|
@@ -441,6 +456,8 @@ def compute_marks():
|
|
441 |
llm_marks * 0.1
|
442 |
)
|
443 |
|
|
|
|
|
444 |
if combined_score > best_score:
|
445 |
best_score = combined_score
|
446 |
best_answer_index = i
|
@@ -456,7 +473,7 @@ def compute_marks():
|
|
456 |
|
457 |
marks = new_value(best_score, 0, 1, 0, 5)
|
458 |
results[student_folder][filename] = round(marks, 2)
|
459 |
-
log_print(f"
|
460 |
|
461 |
except Exception as e:
|
462 |
error_msg = str(e).encode('ascii', 'ignore').decode('ascii')
|
@@ -476,6 +493,7 @@ def compute_marks():
|
|
476 |
log_print(f"Warning: Could not clean up temporary directory: {e}", "WARNING")
|
477 |
|
478 |
if not results:
|
|
|
479 |
return jsonify({
|
480 |
"error": "Processing error",
|
481 |
"message": "No results computed"
|
@@ -491,13 +509,19 @@ def compute_marks():
|
|
491 |
clean_scores[clean_filename] = mark
|
492 |
clean_results[clean_student] = clean_scores
|
493 |
|
494 |
-
|
|
|
|
|
495 |
"results": clean_results,
|
496 |
"failed_files": [{
|
497 |
"file": f["file"].encode('ascii', 'ignore').decode('ascii'),
|
498 |
"error": f["error"].encode('ascii', 'ignore').decode('ascii')
|
499 |
} for f in failed_files]
|
500 |
-
}
|
|
|
|
|
|
|
|
|
501 |
response.headers['Content-Type'] = 'application/json'
|
502 |
return response
|
503 |
|
|
|
322 |
try:
|
323 |
# Get correct answers
|
324 |
correct_answers = request.form.getlist('correct_answers[]')
|
325 |
+
log_print(f"Received correct answers: {correct_answers}")
|
326 |
+
|
327 |
if not correct_answers:
|
328 |
+
log_print("No correct answers provided", "ERROR")
|
329 |
return jsonify({
|
330 |
"error": "Missing data",
|
331 |
"message": "No correct answers provided"
|
|
|
334 |
# Create TFIDF values for correct answers
|
335 |
try:
|
336 |
max_tfidf = create_tfidf_values(correct_answers)
|
337 |
+
log_print("Created TFIDF values successfully")
|
338 |
except Exception as e:
|
339 |
+
log_print(f"TFIDF error: {str(e)}", "ERROR")
|
340 |
return jsonify({
|
341 |
"error": "TFIDF error",
|
342 |
"message": f"Error creating TFIDF values: {str(e)}"
|
|
|
344 |
|
345 |
# Get all uploaded files
|
346 |
files = request.files.getlist('file')
|
347 |
+
log_print(f"Received {len(files)} files")
|
348 |
+
|
349 |
if not files:
|
350 |
+
log_print("No files uploaded", "ERROR")
|
351 |
return jsonify({
|
352 |
"error": "Missing data",
|
353 |
"message": "No files uploaded"
|
|
|
355 |
|
356 |
# Validate folder structure
|
357 |
is_valid, message = validate_folder_structure(files)
|
358 |
+
log_print(f"Folder structure validation: {message}")
|
359 |
+
|
360 |
if not is_valid:
|
361 |
+
log_print(f"Invalid folder structure: {message}", "ERROR")
|
362 |
return jsonify({
|
363 |
"error": "Invalid folder structure",
|
364 |
"message": message
|
365 |
}), 400
|
|
|
|
|
366 |
|
367 |
# Create a temporary directory for processing
|
368 |
base_temp_dir = tempfile.mkdtemp()
|
|
|
381 |
log_print("Skipping invalid file", "WARNING")
|
382 |
continue
|
383 |
|
384 |
+
log_print(f"Processing file: {file.filename}")
|
385 |
+
|
386 |
# Get folder structure from file path
|
387 |
path_parts = file.filename.split('/')
|
388 |
if len(path_parts) < 2:
|
|
|
391 |
|
392 |
student_folder = path_parts[-2]
|
393 |
filename = path_parts[-1]
|
394 |
+
log_print(f"Student folder: {student_folder}, Filename: {filename}")
|
395 |
|
396 |
# Validate file type
|
397 |
if not is_valid_image_file(filename):
|
|
|
405 |
# Initialize student results if not exists
|
406 |
if student_folder not in results:
|
407 |
results[student_folder] = {}
|
408 |
+
log_print(f"Initialized results for student: {student_folder}")
|
409 |
|
410 |
# Save and process file
|
411 |
student_dir = os.path.join(base_temp_dir, student_folder)
|
|
|
415 |
# Ensure filepath is secure
|
416 |
filepath = secure_filename(filepath)
|
417 |
file.save(filepath)
|
418 |
+
log_print(f"Saved file to: {filepath}")
|
419 |
|
420 |
# Extract text
|
421 |
extracted_text = extract_text_from_image(filepath)
|
|
|
427 |
})
|
428 |
continue
|
429 |
|
430 |
+
log_print(f"Extracted text: {extracted_text[:100]}...")
|
431 |
+
|
432 |
# Clean the extracted text for JSON
|
433 |
extracted_text = extracted_text.encode('ascii', 'ignore').decode('ascii')
|
434 |
|
|
|
456 |
llm_marks * 0.1
|
457 |
)
|
458 |
|
459 |
+
log_print(f"Scores for answer {i+1}: semantic={semantic_score}, word={word_score}, tfidf={tfidf_score}, ft={ft_score}, llm={llm_marks}, combined={combined_score}")
|
460 |
+
|
461 |
if combined_score > best_score:
|
462 |
best_score = combined_score
|
463 |
best_answer_index = i
|
|
|
473 |
|
474 |
marks = new_value(best_score, 0, 1, 0, 5)
|
475 |
results[student_folder][filename] = round(marks, 2)
|
476 |
+
log_print(f"Assigned marks for {filename} in {student_folder}: {marks}")
|
477 |
|
478 |
except Exception as e:
|
479 |
error_msg = str(e).encode('ascii', 'ignore').decode('ascii')
|
|
|
493 |
log_print(f"Warning: Could not clean up temporary directory: {e}", "WARNING")
|
494 |
|
495 |
if not results:
|
496 |
+
log_print("No results computed", "ERROR")
|
497 |
return jsonify({
|
498 |
"error": "Processing error",
|
499 |
"message": "No results computed"
|
|
|
509 |
clean_scores[clean_filename] = mark
|
510 |
clean_results[clean_student] = clean_scores
|
511 |
|
512 |
+
log_print(f"Final results: {clean_results}")
|
513 |
+
|
514 |
+
response_data = {
|
515 |
"results": clean_results,
|
516 |
"failed_files": [{
|
517 |
"file": f["file"].encode('ascii', 'ignore').decode('ascii'),
|
518 |
"error": f["error"].encode('ascii', 'ignore').decode('ascii')
|
519 |
} for f in failed_files]
|
520 |
+
}
|
521 |
+
|
522 |
+
log_print(f"Sending response: {response_data}")
|
523 |
+
|
524 |
+
response = jsonify(response_data)
|
525 |
response.headers['Content-Type'] = 'application/json'
|
526 |
return response
|
527 |
|
templates/index.html
CHANGED
@@ -532,9 +532,138 @@
|
|
532 |
0% { transform: rotate(0deg); }
|
533 |
100% { transform: rotate(360deg); }
|
534 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
535 |
</style>
|
536 |
</head>
|
537 |
<body>
|
|
|
|
|
538 |
<div class="container">
|
539 |
<div class="section">
|
540 |
<h2>Upload Query CSV File</h2>
|
@@ -599,7 +728,19 @@
|
|
599 |
<div class="section">
|
600 |
<div id="answers-container"></div>
|
601 |
<button id="compute-marks-btn" onclick="computeMarks()">Compute Marks</button>
|
602 |
-
<div id="marks-table-container"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
603 |
</div>
|
604 |
</div>
|
605 |
|
@@ -611,6 +752,64 @@
|
|
611 |
</div>
|
612 |
|
613 |
<script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
614 |
document.addEventListener('DOMContentLoaded', function() {
|
615 |
const fileTypeSelect = document.getElementById('file-type');
|
616 |
const pdfSection = document.getElementById('pdf-section');
|
@@ -670,31 +869,34 @@
|
|
670 |
const pdfFiles = document.getElementById('pdf-files').files;
|
671 |
|
672 |
if (!queryfile) {
|
673 |
-
|
674 |
hideLoading();
|
675 |
return;
|
676 |
}
|
677 |
|
|
|
678 |
const formData = new FormData();
|
679 |
formData.append('file_type', fileType);
|
680 |
formData.append('query_file', queryfile);
|
681 |
|
682 |
if (fileType === 'csv') {
|
683 |
if (!anscsvFile) {
|
684 |
-
|
685 |
hideLoading();
|
686 |
return;
|
687 |
}
|
688 |
formData.append('ans_csv_file', anscsvFile);
|
|
|
689 |
} else if (fileType === 'pdf') {
|
690 |
if (!pdfFiles || pdfFiles.length < 2) {
|
691 |
-
|
692 |
hideLoading();
|
693 |
return;
|
694 |
}
|
695 |
for (let file of pdfFiles) {
|
696 |
formData.append('pdf_files[]', file);
|
697 |
}
|
|
|
698 |
}
|
699 |
|
700 |
const computeBtn = document.getElementById('compute-btn');
|
@@ -716,13 +918,14 @@
|
|
716 |
|
717 |
if (result.answers) {
|
718 |
displayAnswers(result.answers);
|
|
|
719 |
} else {
|
720 |
throw new Error('No answers received from server');
|
721 |
}
|
722 |
|
723 |
} catch (error) {
|
724 |
console.error('Error:', error);
|
725 |
-
|
726 |
} finally {
|
727 |
hideLoading();
|
728 |
const computeBtn = document.getElementById('compute-btn');
|
@@ -953,42 +1156,42 @@
|
|
953 |
try {
|
954 |
showLoading();
|
955 |
const answerBoxes = document.querySelectorAll('.answer-box');
|
|
|
956 |
if (answerBoxes.length === 0) {
|
957 |
-
|
958 |
hideLoading();
|
959 |
return;
|
960 |
}
|
961 |
|
962 |
const answerValues = Array.from(answerBoxes).map(box => box.value.trim());
|
|
|
963 |
if (answerValues.some(answer => !answer)) {
|
964 |
-
|
965 |
hideLoading();
|
966 |
return;
|
967 |
}
|
968 |
|
969 |
if (selectedFiles.size === 0) {
|
970 |
-
|
971 |
hideLoading();
|
972 |
return;
|
973 |
}
|
974 |
|
|
|
|
|
975 |
const computeBtn = document.getElementById('compute-marks-btn');
|
976 |
computeBtn.disabled = true;
|
977 |
|
978 |
const formData = new FormData();
|
979 |
|
980 |
-
|
981 |
-
answerValues.forEach(answer => {
|
982 |
formData.append('correct_answers[]', answer);
|
983 |
});
|
984 |
|
985 |
-
// Add files with their folder structure
|
986 |
let validFiles = 0;
|
987 |
selectedFiles.forEach((fileInfo, path) => {
|
988 |
if (fileInfo.file.type.startsWith('image/')) {
|
989 |
-
|
990 |
-
const relativePath = fileInfo.fullPath;
|
991 |
-
formData.append('file', fileInfo.file, relativePath);
|
992 |
validFiles++;
|
993 |
}
|
994 |
});
|
@@ -997,7 +1200,8 @@
|
|
997 |
throw new Error("No valid image files found in the uploaded folder");
|
998 |
}
|
999 |
|
1000 |
-
|
|
|
1001 |
const response = await fetch('/compute_marks', {
|
1002 |
method: 'POST',
|
1003 |
headers: {
|
@@ -1006,15 +1210,15 @@
|
|
1006 |
body: formData
|
1007 |
});
|
1008 |
|
1009 |
-
|
1010 |
-
|
1011 |
-
|
1012 |
-
|
|
|
|
|
|
|
1013 |
}
|
1014 |
|
1015 |
-
const result = await response.json();
|
1016 |
-
console.log('Parsed response:', result);
|
1017 |
-
|
1018 |
if (result.error) {
|
1019 |
throw new Error(result.message || result.error);
|
1020 |
}
|
@@ -1024,18 +1228,18 @@
|
|
1024 |
}
|
1025 |
|
1026 |
displayMarks(result.results);
|
|
|
1027 |
|
1028 |
if (result.failed_files && result.failed_files.length > 0) {
|
1029 |
-
console.warn('Some files failed to process:', result.failed_files);
|
1030 |
const failedMessage = result.failed_files
|
1031 |
.map(f => `${f.file}: ${f.error}`)
|
1032 |
.join('\n');
|
1033 |
-
|
1034 |
}
|
1035 |
|
1036 |
} catch (error) {
|
1037 |
console.error('Error details:', error);
|
1038 |
-
|
1039 |
} finally {
|
1040 |
hideLoading();
|
1041 |
const computeBtn = document.getElementById('compute-marks-btn');
|
@@ -1045,29 +1249,61 @@
|
|
1045 |
|
1046 |
function displayMarks(results) {
|
1047 |
const tableBody = document.getElementById('marks-table-body');
|
|
|
|
|
|
|
|
|
|
|
1048 |
tableBody.innerHTML = '';
|
1049 |
|
1050 |
-
//
|
1051 |
-
const
|
1052 |
-
headerRow.innerHTML = `
|
1053 |
-
<th>Student Folder</th>
|
1054 |
-
<th>Image Name</th>
|
1055 |
-
<th>Marks</th>
|
1056 |
-
`;
|
1057 |
-
tableBody.appendChild(headerRow);
|
1058 |
|
1059 |
-
|
1060 |
-
|
1061 |
-
|
1062 |
-
|
1063 |
-
|
1064 |
-
|
1065 |
-
|
1066 |
-
|
1067 |
-
|
1068 |
-
|
1069 |
-
|
1070 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1071 |
}
|
1072 |
</script>
|
1073 |
</body>
|
|
|
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 |
</style>
|
663 |
</head>
|
664 |
<body>
|
665 |
+
<div class="notification-container" id="notification-container"></div>
|
666 |
+
|
667 |
<div class="container">
|
668 |
<div class="section">
|
669 |
<h2>Upload Query CSV File</h2>
|
|
|
728 |
<div class="section">
|
729 |
<div id="answers-container"></div>
|
730 |
<button id="compute-marks-btn" onclick="computeMarks()">Compute Marks</button>
|
731 |
+
<div id="marks-table-container">
|
732 |
+
<table id="marks-table">
|
733 |
+
<thead>
|
734 |
+
<tr>
|
735 |
+
<th>Student Folder</th>
|
736 |
+
<th>Image Name</th>
|
737 |
+
<th>Marks</th>
|
738 |
+
</tr>
|
739 |
+
</thead>
|
740 |
+
<tbody id="marks-table-body">
|
741 |
+
</tbody>
|
742 |
+
</table>
|
743 |
+
</div>
|
744 |
</div>
|
745 |
</div>
|
746 |
|
|
|
752 |
</div>
|
753 |
|
754 |
<script>
|
755 |
+
const notificationSystem = {
|
756 |
+
container: null,
|
757 |
+
init() {
|
758 |
+
this.container = document.getElementById('notification-container');
|
759 |
+
},
|
760 |
+
|
761 |
+
show(message, type = 'info', duration = 5000) {
|
762 |
+
if (!this.container) this.init();
|
763 |
+
|
764 |
+
const notification = document.createElement('div');
|
765 |
+
notification.className = `notification ${type}`;
|
766 |
+
|
767 |
+
const content = document.createElement('div');
|
768 |
+
content.className = 'notification-content';
|
769 |
+
content.textContent = message;
|
770 |
+
|
771 |
+
const closeBtn = document.createElement('button');
|
772 |
+
closeBtn.className = 'notification-close';
|
773 |
+
closeBtn.innerHTML = '×';
|
774 |
+
closeBtn.onclick = () => this.remove(notification);
|
775 |
+
|
776 |
+
notification.appendChild(content);
|
777 |
+
notification.appendChild(closeBtn);
|
778 |
+
this.container.appendChild(notification);
|
779 |
+
|
780 |
+
if (duration > 0) {
|
781 |
+
setTimeout(() => this.remove(notification), duration);
|
782 |
+
}
|
783 |
+
|
784 |
+
return notification;
|
785 |
+
},
|
786 |
+
|
787 |
+
remove(notification) {
|
788 |
+
notification.classList.add('fade-out');
|
789 |
+
setTimeout(() => {
|
790 |
+
if (notification.parentElement === this.container) {
|
791 |
+
this.container.removeChild(notification);
|
792 |
+
}
|
793 |
+
}, 300);
|
794 |
+
},
|
795 |
+
|
796 |
+
success(message, duration = 5000) {
|
797 |
+
return this.show(message, 'success', duration);
|
798 |
+
},
|
799 |
+
|
800 |
+
error(message, duration = 8000) {
|
801 |
+
return this.show(message, 'error', duration);
|
802 |
+
},
|
803 |
+
|
804 |
+
warning(message, duration = 6000) {
|
805 |
+
return this.show(message, 'warning', duration);
|
806 |
+
},
|
807 |
+
|
808 |
+
info(message, duration = 4000) {
|
809 |
+
return this.show(message, 'info', duration);
|
810 |
+
}
|
811 |
+
};
|
812 |
+
|
813 |
document.addEventListener('DOMContentLoaded', function() {
|
814 |
const fileTypeSelect = document.getElementById('file-type');
|
815 |
const pdfSection = document.getElementById('pdf-section');
|
|
|
869 |
const pdfFiles = document.getElementById('pdf-files').files;
|
870 |
|
871 |
if (!queryfile) {
|
872 |
+
notificationSystem.error("Please upload a query file first!");
|
873 |
hideLoading();
|
874 |
return;
|
875 |
}
|
876 |
|
877 |
+
notificationSystem.info("Processing files...");
|
878 |
const formData = new FormData();
|
879 |
formData.append('file_type', fileType);
|
880 |
formData.append('query_file', queryfile);
|
881 |
|
882 |
if (fileType === 'csv') {
|
883 |
if (!anscsvFile) {
|
884 |
+
notificationSystem.error("Please upload a CSV file for answers!");
|
885 |
hideLoading();
|
886 |
return;
|
887 |
}
|
888 |
formData.append('ans_csv_file', anscsvFile);
|
889 |
+
notificationSystem.info("Processing CSV file...");
|
890 |
} else if (fileType === 'pdf') {
|
891 |
if (!pdfFiles || pdfFiles.length < 2) {
|
892 |
+
notificationSystem.error("Please upload at least 2 PDF files!");
|
893 |
hideLoading();
|
894 |
return;
|
895 |
}
|
896 |
for (let file of pdfFiles) {
|
897 |
formData.append('pdf_files[]', file);
|
898 |
}
|
899 |
+
notificationSystem.info(`Processing ${pdfFiles.length} PDF files...`);
|
900 |
}
|
901 |
|
902 |
const computeBtn = document.getElementById('compute-btn');
|
|
|
918 |
|
919 |
if (result.answers) {
|
920 |
displayAnswers(result.answers);
|
921 |
+
notificationSystem.success("Successfully generated answers!");
|
922 |
} else {
|
923 |
throw new Error('No answers received from server');
|
924 |
}
|
925 |
|
926 |
} catch (error) {
|
927 |
console.error('Error:', error);
|
928 |
+
notificationSystem.error('Error: ' + error.message);
|
929 |
} finally {
|
930 |
hideLoading();
|
931 |
const computeBtn = document.getElementById('compute-btn');
|
|
|
1156 |
try {
|
1157 |
showLoading();
|
1158 |
const answerBoxes = document.querySelectorAll('.answer-box');
|
1159 |
+
|
1160 |
if (answerBoxes.length === 0) {
|
1161 |
+
notificationSystem.error("Please generate answers first!");
|
1162 |
hideLoading();
|
1163 |
return;
|
1164 |
}
|
1165 |
|
1166 |
const answerValues = Array.from(answerBoxes).map(box => box.value.trim());
|
1167 |
+
|
1168 |
if (answerValues.some(answer => !answer)) {
|
1169 |
+
notificationSystem.error("Please ensure all answer boxes are filled!");
|
1170 |
hideLoading();
|
1171 |
return;
|
1172 |
}
|
1173 |
|
1174 |
if (selectedFiles.size === 0) {
|
1175 |
+
notificationSystem.error("Please upload student answer files!");
|
1176 |
hideLoading();
|
1177 |
return;
|
1178 |
}
|
1179 |
|
1180 |
+
notificationSystem.info(`Processing ${selectedFiles.size} student files...`);
|
1181 |
+
|
1182 |
const computeBtn = document.getElementById('compute-marks-btn');
|
1183 |
computeBtn.disabled = true;
|
1184 |
|
1185 |
const formData = new FormData();
|
1186 |
|
1187 |
+
answerValues.forEach((answer, index) => {
|
|
|
1188 |
formData.append('correct_answers[]', answer);
|
1189 |
});
|
1190 |
|
|
|
1191 |
let validFiles = 0;
|
1192 |
selectedFiles.forEach((fileInfo, path) => {
|
1193 |
if (fileInfo.file.type.startsWith('image/')) {
|
1194 |
+
formData.append('file', fileInfo.file, fileInfo.fullPath);
|
|
|
|
|
1195 |
validFiles++;
|
1196 |
}
|
1197 |
});
|
|
|
1200 |
throw new Error("No valid image files found in the uploaded folder");
|
1201 |
}
|
1202 |
|
1203 |
+
notificationSystem.info(`Processing ${validFiles} valid image files...`);
|
1204 |
+
|
1205 |
const response = await fetch('/compute_marks', {
|
1206 |
method: 'POST',
|
1207 |
headers: {
|
|
|
1210 |
body: formData
|
1211 |
});
|
1212 |
|
1213 |
+
const responseText = await response.text();
|
1214 |
+
let result;
|
1215 |
+
try {
|
1216 |
+
result = JSON.parse(responseText);
|
1217 |
+
} catch (e) {
|
1218 |
+
console.error('Error parsing JSON:', e);
|
1219 |
+
throw new Error('Invalid JSON response from server');
|
1220 |
}
|
1221 |
|
|
|
|
|
|
|
1222 |
if (result.error) {
|
1223 |
throw new Error(result.message || result.error);
|
1224 |
}
|
|
|
1228 |
}
|
1229 |
|
1230 |
displayMarks(result.results);
|
1231 |
+
notificationSystem.success("Successfully computed marks!");
|
1232 |
|
1233 |
if (result.failed_files && result.failed_files.length > 0) {
|
|
|
1234 |
const failedMessage = result.failed_files
|
1235 |
.map(f => `${f.file}: ${f.error}`)
|
1236 |
.join('\n');
|
1237 |
+
notificationSystem.warning(`Some files failed to process:\n${failedMessage}`);
|
1238 |
}
|
1239 |
|
1240 |
} catch (error) {
|
1241 |
console.error('Error details:', error);
|
1242 |
+
notificationSystem.error('Error computing marks: ' + error.message);
|
1243 |
} finally {
|
1244 |
hideLoading();
|
1245 |
const computeBtn = document.getElementById('compute-marks-btn');
|
|
|
1249 |
|
1250 |
function displayMarks(results) {
|
1251 |
const tableBody = document.getElementById('marks-table-body');
|
1252 |
+
if (!tableBody) {
|
1253 |
+
throw new Error('Table body element not found');
|
1254 |
+
}
|
1255 |
+
|
1256 |
+
// Clear existing rows
|
1257 |
tableBody.innerHTML = '';
|
1258 |
|
1259 |
+
// Sort student folders for consistent display
|
1260 |
+
const sortedStudents = Object.keys(results).sort();
|
|
|
|
|
|
|
|
|
|
|
|
|
1261 |
|
1262 |
+
for (const student of sortedStudents) {
|
1263 |
+
const scores = results[student];
|
1264 |
+
const row = document.createElement('tr');
|
1265 |
+
|
1266 |
+
// Add student name cell
|
1267 |
+
const studentCell = document.createElement('td');
|
1268 |
+
studentCell.textContent = student;
|
1269 |
+
row.appendChild(studentCell);
|
1270 |
+
|
1271 |
+
// Add scores cell
|
1272 |
+
const scoresCell = document.createElement('td');
|
1273 |
+
const scoresList = document.createElement('ul');
|
1274 |
+
scoresList.className = 'list-unstyled mb-0';
|
1275 |
+
|
1276 |
+
// Sort filenames for consistent display
|
1277 |
+
const sortedFiles = Object.keys(scores).sort();
|
1278 |
+
|
1279 |
+
for (const filename of sortedFiles) {
|
1280 |
+
const score = scores[filename];
|
1281 |
+
const scoreItem = document.createElement('li');
|
1282 |
+
scoreItem.textContent = `${filename}: ${score}`;
|
1283 |
+
scoresList.appendChild(scoreItem);
|
1284 |
+
}
|
1285 |
+
|
1286 |
+
scoresCell.appendChild(scoresList);
|
1287 |
+
row.appendChild(scoresCell);
|
1288 |
+
|
1289 |
+
// Calculate and add average score cell
|
1290 |
+
const scoreValues = Object.values(scores);
|
1291 |
+
const average = scoreValues.length > 0
|
1292 |
+
? (scoreValues.reduce((a, b) => a + b, 0) / scoreValues.length).toFixed(2)
|
1293 |
+
: 'N/A';
|
1294 |
+
|
1295 |
+
const averageCell = document.createElement('td');
|
1296 |
+
averageCell.textContent = average;
|
1297 |
+
row.appendChild(averageCell);
|
1298 |
+
|
1299 |
+
tableBody.appendChild(row);
|
1300 |
+
}
|
1301 |
+
|
1302 |
+
// Show the results table
|
1303 |
+
const resultsSection = document.getElementById('results-section');
|
1304 |
+
if (resultsSection) {
|
1305 |
+
resultsSection.style.display = 'block';
|
1306 |
+
}
|
1307 |
}
|
1308 |
</script>
|
1309 |
</body>
|