Spaces:
Running
Running
Commit
·
edca77e
1
Parent(s):
5c56bc9
feat: Add core medical document validation module with text/image extraction and initial static UI page.
Browse files- app/static/index.html +868 -577
- app/validator.py +84 -17
app/static/index.html
CHANGED
|
@@ -379,112 +379,190 @@
|
|
| 379 |
}
|
| 380 |
}
|
| 381 |
|
|
|
|
| 382 |
.results {
|
| 383 |
display: none;
|
| 384 |
-
margin-top:
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
}
|
| 389 |
|
| 390 |
.status {
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
|
|
|
|
|
|
|
|
|
| 396 |
}
|
| 397 |
|
| 398 |
.status.pass {
|
| 399 |
-
background: #
|
| 400 |
-
color: #
|
| 401 |
-
|
| 402 |
}
|
| 403 |
|
| 404 |
.status.fail {
|
| 405 |
-
background: #
|
| 406 |
-
color: #
|
| 407 |
-
|
| 408 |
}
|
| 409 |
|
| 410 |
-
.
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
}
|
| 415 |
|
| 416 |
-
.elements-
|
|
|
|
|
|
|
|
|
|
| 417 |
list-style: none;
|
|
|
|
| 418 |
}
|
| 419 |
|
| 420 |
.element-item {
|
| 421 |
background: white;
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
}
|
| 427 |
|
| 428 |
-
.element-item
|
| 429 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 430 |
}
|
| 431 |
|
| 432 |
-
.element-item.missing {
|
| 433 |
-
|
| 434 |
}
|
| 435 |
|
| 436 |
-
.element-item.optional {
|
| 437 |
-
|
| 438 |
}
|
| 439 |
|
| 440 |
.element-header {
|
| 441 |
display: flex;
|
| 442 |
justify-content: space-between;
|
| 443 |
-
align-items:
|
| 444 |
-
margin-bottom:
|
| 445 |
}
|
| 446 |
|
| 447 |
.element-label {
|
| 448 |
-
font-weight:
|
| 449 |
-
|
|
|
|
|
|
|
|
|
|
| 450 |
}
|
| 451 |
|
| 452 |
.element-badge {
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
|
|
|
|
|
|
|
|
|
| 457 |
}
|
| 458 |
|
| 459 |
.badge-present {
|
| 460 |
-
background: #
|
| 461 |
-
color: #
|
|
|
|
| 462 |
}
|
| 463 |
|
| 464 |
.badge-missing {
|
| 465 |
-
background: #
|
| 466 |
-
color: #
|
|
|
|
| 467 |
}
|
| 468 |
|
| 469 |
.badge-optional {
|
| 470 |
-
background: #
|
| 471 |
-
color: #
|
|
|
|
| 472 |
}
|
| 473 |
|
| 474 |
.element-reason {
|
| 475 |
-
color:
|
| 476 |
font-size: 14px;
|
| 477 |
-
margin-top:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
}
|
| 479 |
|
| 480 |
.error {
|
| 481 |
display: none;
|
| 482 |
-
background: #
|
| 483 |
-
color: #
|
| 484 |
-
padding:
|
| 485 |
-
border-radius:
|
| 486 |
-
margin-top:
|
| 487 |
-
border: 1px solid #
|
|
|
|
|
|
|
| 488 |
}
|
| 489 |
|
| 490 |
.templates-loading {
|
|
@@ -741,6 +819,41 @@
|
|
| 741 |
background: #fff3cd;
|
| 742 |
color: #856404;
|
| 743 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 744 |
</style>
|
| 745 |
</head>
|
| 746 |
|
|
@@ -800,252 +913,257 @@
|
|
| 800 |
<p>Upload a document and select a template to validate against</p>
|
| 801 |
</div>
|
| 802 |
|
| 803 |
-
<!--
|
| 804 |
-
<div
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 809 |
</div>
|
| 810 |
-
<select id="currentProject" style="flex: 1; min-width: 200px;">
|
| 811 |
-
<option value="">No Project (Not Saved)</option>
|
| 812 |
-
</select>
|
| 813 |
-
<button type="button" class="btn btn-secondary" id="createProjectBtn">+ New</button>
|
| 814 |
-
<button type="button" class="btn btn-secondary" id="viewProjectsBtn">View All</button>
|
| 815 |
</div>
|
| 816 |
-
</div>
|
| 817 |
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 832 |
</div>
|
| 833 |
</div>
|
| 834 |
-
<div id="sharepointAuthSection">
|
| 835 |
-
<button type="button" class="btn-secondary" id="connectSharePointBtn"
|
| 836 |
-
style="border: 1px solid #0078d4; color: #0078d4; background: white;">
|
| 837 |
-
🔗 Connect Account
|
| 838 |
-
</button>
|
| 839 |
-
</div>
|
| 840 |
-
<div id="sharepointActionsSection" style="display: none;">
|
| 841 |
-
<span id="sharepointStatus"
|
| 842 |
-
style="font-size: 12px; color: #28a745; font-weight: 600; margin-right: 10px;">✓ Connected</span>
|
| 843 |
-
<button type="button" class="btn-secondary" id="browseSharePointBtn"
|
| 844 |
-
style="background: #0078d4; color: white; border: none;">
|
| 845 |
-
📂 Browse Files
|
| 846 |
-
</button>
|
| 847 |
-
<button type="button" class="btn-secondary" id="logoutSharePointBtn"
|
| 848 |
-
style="background: #eee; border: 1px solid #ccc; font-size: 12px; padding: 5px 10px;">
|
| 849 |
-
Disconnect
|
| 850 |
-
</button>
|
| 851 |
-
</div>
|
| 852 |
-
</div>
|
| 853 |
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
<div
|
| 860 |
-
style="padding: 20px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center;">
|
| 861 |
-
<h2 style="margin: 0; font-size: 20px;">📂 Select Documents</h2>
|
| 862 |
-
<button type="button" id="closeSharePointModal"
|
| 863 |
-
style="background: none; border: none; font-size: 24px; cursor: pointer;">×</button>
|
| 864 |
</div>
|
| 865 |
|
| 866 |
-
<
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
|
|
|
| 873 |
|
| 874 |
-
|
| 875 |
-
|
| 876 |
-
|
| 877 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 878 |
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
|
| 883 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 884 |
</div>
|
| 885 |
-
</div>
|
| 886 |
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
<div
|
| 891 |
-
style="background: white; padding: 30px; border-radius: 8px; max-width: 500px; width: 90%; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
|
| 892 |
-
<h2 style="margin-top: 0;">Create New Project</h2>
|
| 893 |
-
<div class="form-group">
|
| 894 |
-
<label for="projectName">Project Name: *</label>
|
| 895 |
-
<input type="text" id="projectName" placeholder="e.g., Cardiology Conference Q1 2025"
|
| 896 |
-
style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px;">
|
| 897 |
-
</div>
|
| 898 |
-
<div class="form-group">
|
| 899 |
-
<label for="projectDescription">Description (Optional):</label>
|
| 900 |
-
<textarea id="projectDescription" rows="3" placeholder="Brief description of this project..."
|
| 901 |
-
style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; resize: vertical;"></textarea>
|
| 902 |
-
</div>
|
| 903 |
-
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;">
|
| 904 |
-
<button type="button" class="btn-secondary" id="cancelProjectBtn">Cancel</button>
|
| 905 |
-
<button type="button" class="btn" id="saveProjectBtn">Create Project</button>
|
| 906 |
-
</div>
|
| 907 |
</div>
|
| 908 |
-
</div>
|
| 909 |
|
| 910 |
-
|
| 911 |
-
|
| 912 |
-
<div class="
|
| 913 |
-
<div class="
|
| 914 |
-
<
|
|
|
|
| 915 |
</div>
|
|
|
|
| 916 |
|
| 917 |
-
<form id="validationForm">
|
| 918 |
-
<div class="form-group">
|
| 919 |
-
<label for="templateSelect">Select Template <span class="optional">(Required for template
|
| 920 |
-
validation)</span></label>
|
| 921 |
-
<select id="templateSelect" name="template">
|
| 922 |
-
<option value="">-- Select a template --</option>
|
| 923 |
-
</select>
|
| 924 |
-
</div>
|
| 925 |
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 936 |
</div>
|
| 937 |
-
<div class="file-info" id="fileInfo" style="display: none;"></div>
|
| 938 |
-
</div>
|
| 939 |
|
| 940 |
-
|
| 941 |
-
|
| 942 |
-
|
| 943 |
-
|
| 944 |
-
<div style="text-align: right; font-size: 12px; color: var(--text-muted); margin-top: 4px;">
|
| 945 |
-
<span id="charCount">0</span>/500 characters
|
| 946 |
</div>
|
| 947 |
</div>
|
| 948 |
|
| 949 |
-
<
|
| 950 |
-
|
| 951 |
-
|
| 952 |
-
|
| 953 |
-
|
| 954 |
-
|
| 955 |
-
|
| 956 |
-
|
| 957 |
-
|
| 958 |
-
|
| 959 |
-
|
| 960 |
-
|
| 961 |
-
</
|
|
|
|
|
|
|
|
|
|
| 962 |
</svg>
|
| 963 |
-
|
| 964 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 965 |
</div>
|
| 966 |
-
|
| 967 |
-
💡 Use "Quality Check Only" for grammar and spelling without template validation
|
| 968 |
-
</p>
|
| 969 |
-
</form>
|
| 970 |
-
</div>
|
| 971 |
|
| 972 |
-
|
| 973 |
-
<
|
| 974 |
-
|
| 975 |
-
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
<div class="debug-info" id="debugInfo" style="display: none;"></div>
|
| 979 |
</div>
|
| 980 |
|
| 981 |
-
<!--
|
| 982 |
-
<div class="
|
| 983 |
-
|
| 984 |
-
<
|
| 985 |
-
|
| 986 |
-
|
| 987 |
-
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
<div class="file-info" id="compareFileInfo1"></div>
|
| 994 |
-
</div>
|
| 995 |
|
| 996 |
-
|
| 997 |
-
|
| 998 |
-
<
|
| 999 |
-
<
|
|
|
|
| 1000 |
</div>
|
| 1001 |
-
</div>
|
| 1002 |
-
|
| 1003 |
-
<button type="button" class="btn" id="compareBtn" style="width: 100%;">
|
| 1004 |
-
🔍 Compare Documents
|
| 1005 |
-
</button>
|
| 1006 |
-
</div>
|
| 1007 |
|
| 1008 |
-
|
| 1009 |
-
|
| 1010 |
-
|
| 1011 |
-
|
| 1012 |
-
|
| 1013 |
-
|
| 1014 |
-
|
| 1015 |
-
|
| 1016 |
-
|
| 1017 |
-
|
| 1018 |
-
|
| 1019 |
-
<label for="excelFile">1️⃣ Upload Excel File with Names:</label>
|
| 1020 |
-
<input type="file" id="excelFile" accept=".xlsx">
|
| 1021 |
-
<div class="file-info" id="excelFileInfo"></div>
|
| 1022 |
-
</div>
|
| 1023 |
|
| 1024 |
-
|
| 1025 |
-
|
| 1026 |
-
|
| 1027 |
-
|
| 1028 |
-
<
|
| 1029 |
-
|
| 1030 |
-
|
| 1031 |
-
|
| 1032 |
</div>
|
| 1033 |
-
</div>
|
| 1034 |
|
| 1035 |
-
|
| 1036 |
-
|
| 1037 |
-
|
| 1038 |
-
|
| 1039 |
-
<div style=" margin-top: 8px;">
|
| 1040 |
-
<span style="font-weight: 600; color: #007bff;" id="certCount">0</span>
|
| 1041 |
-
<span style="color: #666;">/150 files selected</span>
|
| 1042 |
</div>
|
| 1043 |
-
</div>
|
| 1044 |
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
|
| 1048 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1049 |
</div>
|
| 1050 |
|
| 1051 |
<div class="loading" id="loading">
|
|
@@ -1055,31 +1173,60 @@
|
|
| 1055 |
|
| 1056 |
<div class="error" id="error"></div>
|
| 1057 |
|
| 1058 |
-
<div class="results" id="results">
|
| 1059 |
-
<div class="status" id="status"></div>
|
| 1060 |
-
<div class="summary" id="summary"></div>
|
| 1061 |
-
<ul class="elements-list" id="elementsList"></ul>
|
| 1062 |
-
</div>
|
| 1063 |
-
|
| 1064 |
-
<!-- Comparison Results -->
|
| 1065 |
-
<div class="results" id="comparisonResults" style="display: none;">
|
| 1066 |
-
<h2 style="margin-bottom: 20px;">📊 Comparison Results</h2>
|
| 1067 |
-
<div id="comparisonSummary" style="margin-bottom: 20px;"></div>
|
| 1068 |
-
<div id="comparisonDetails"></div>
|
| 1069 |
-
</div>
|
| 1070 |
|
| 1071 |
-
<!-- Bulk Validation Results -->
|
| 1072 |
-
<div class="results" id="bulkResults" style="display: none;">
|
| 1073 |
-
<h2 style="margin-bottom: 20px;">📊 Bulk Validation Results</h2>
|
| 1074 |
-
<div id="bulkSummary" style="margin-bottom: 20px;"></div>
|
| 1075 |
-
<button type="button" class="btn-secondary" id="downloadCSVBtn" style="margin-bottom: 20px;">
|
| 1076 |
-
📥 Download CSV Report
|
| 1077 |
-
</button>
|
| 1078 |
-
<div id="bulkDetails"></div>
|
| 1079 |
-
</div>
|
| 1080 |
</main>
|
| 1081 |
|
| 1082 |
<script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1083 |
// Load templates on page load
|
| 1084 |
async function loadTemplates() {
|
| 1085 |
try {
|
|
@@ -1173,29 +1320,44 @@
|
|
| 1173 |
document.getElementById('compareBtn').addEventListener('click', async function () {
|
| 1174 |
const file1 = document.getElementById('compareFile1').files[0];
|
| 1175 |
const file2 = document.getElementById('compareFile2').files[0];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1176 |
|
| 1177 |
if (!file1 || !file2) {
|
| 1178 |
-
|
|
|
|
| 1179 |
return;
|
| 1180 |
}
|
| 1181 |
|
| 1182 |
// Hide previous results
|
| 1183 |
document.getElementById('results').style.display = 'none';
|
| 1184 |
document.getElementById('comparisonResults').style.display = 'none';
|
| 1185 |
-
|
| 1186 |
-
|
| 1187 |
-
|
|
|
|
|
|
|
| 1188 |
|
| 1189 |
try {
|
| 1190 |
const formData = new FormData();
|
| 1191 |
formData.append('file1', file1);
|
| 1192 |
formData.append('file2', file2);
|
| 1193 |
|
|
|
|
|
|
|
|
|
|
| 1194 |
const response = await fetch('/compare', {
|
| 1195 |
method: 'POST',
|
| 1196 |
body: formData
|
| 1197 |
});
|
| 1198 |
|
|
|
|
|
|
|
| 1199 |
const data = await response.json();
|
| 1200 |
|
| 1201 |
if (!response.ok) {
|
|
@@ -1204,9 +1366,11 @@
|
|
| 1204 |
|
| 1205 |
displayComparisonResults(data);
|
| 1206 |
} catch (error) {
|
| 1207 |
-
|
|
|
|
| 1208 |
} finally {
|
| 1209 |
-
|
|
|
|
| 1210 |
}
|
| 1211 |
});
|
| 1212 |
|
|
@@ -1342,43 +1506,49 @@
|
|
| 1342 |
const summaryDiv = document.getElementById('summary');
|
| 1343 |
const elementsList = document.getElementById('elementsList');
|
| 1344 |
|
| 1345 |
-
//
|
| 1346 |
-
|
| 1347 |
statusDiv.className = `status ${data.status.toLowerCase()}`;
|
| 1348 |
|
| 1349 |
-
|
| 1350 |
-
|
|
|
|
| 1351 |
|
| 1352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1353 |
elementsList.innerHTML = '';
|
|
|
|
|
|
|
| 1354 |
data.elements_report.forEach(element => {
|
| 1355 |
const li = document.createElement('li');
|
| 1356 |
li.className = `element-item ${element.is_present ? 'present' : 'missing'} ${!element.required ? 'optional' : ''}`;
|
| 1357 |
|
| 1358 |
-
|
| 1359 |
-
|
| 1360 |
-
|
| 1361 |
-
const label = document.createElement('span');
|
| 1362 |
-
label.className = 'element-label';
|
| 1363 |
-
label.textContent = element.label;
|
| 1364 |
-
|
| 1365 |
-
const badge = document.createElement('span');
|
| 1366 |
-
badge.className = `element-badge ${element.is_present ? 'badge-present' : 'badge-missing'} ${!element.required ? 'badge-optional' : ''}`;
|
| 1367 |
if (!element.required) {
|
| 1368 |
-
|
| 1369 |
-
|
| 1370 |
-
badge.textContent = element.is_present ? 'Present' : 'Missing';
|
| 1371 |
}
|
| 1372 |
|
| 1373 |
-
|
| 1374 |
-
|
| 1375 |
-
|
| 1376 |
-
|
| 1377 |
-
|
| 1378 |
-
|
| 1379 |
-
|
| 1380 |
-
li.appendChild(header);
|
| 1381 |
-
li.appendChild(reason);
|
| 1382 |
elementsList.appendChild(li);
|
| 1383 |
});
|
| 1384 |
|
|
@@ -1397,7 +1567,11 @@
|
|
| 1397 |
}
|
| 1398 |
|
| 1399 |
function displaySpellCheck(spellCheck) {
|
| 1400 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1401 |
|
| 1402 |
// Create spell check section
|
| 1403 |
const spellSection = document.createElement('div');
|
|
@@ -1405,137 +1579,201 @@
|
|
| 1405 |
|
| 1406 |
const header = document.createElement('div');
|
| 1407 |
header.className = 'spell-check-header';
|
| 1408 |
-
header.innerHTML =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1409 |
spellSection.appendChild(header);
|
| 1410 |
|
| 1411 |
if (spellCheck.total_errors === 0) {
|
| 1412 |
const noErrors = document.createElement('div');
|
| 1413 |
-
noErrors.className = '
|
| 1414 |
-
noErrors.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1415 |
spellSection.appendChild(noErrors);
|
| 1416 |
} else {
|
|
|
|
|
|
|
|
|
|
| 1417 |
spellCheck.errors.forEach(error => {
|
| 1418 |
const errorItem = document.createElement('div');
|
| 1419 |
-
errorItem.className = '
|
|
|
|
| 1420 |
|
| 1421 |
const wordDiv = document.createElement('div');
|
| 1422 |
-
wordDiv.
|
| 1423 |
-
wordDiv.innerHTML =
|
| 1424 |
errorItem.appendChild(wordDiv);
|
| 1425 |
|
| 1426 |
if (error.context) {
|
| 1427 |
const contextDiv = document.createElement('div');
|
| 1428 |
-
contextDiv.className = '
|
|
|
|
|
|
|
|
|
|
| 1429 |
contextDiv.textContent = `Context: "${error.context}"`;
|
| 1430 |
errorItem.appendChild(contextDiv);
|
| 1431 |
}
|
| 1432 |
|
| 1433 |
if (error.suggestions && error.suggestions.length > 0) {
|
| 1434 |
-
const suggestionsLabel = document.createElement('div');
|
| 1435 |
-
suggestionsLabel.textContent = 'Suggestions:';
|
| 1436 |
-
suggestionsLabel.style.fontSize = '14px';
|
| 1437 |
-
suggestionsLabel.style.marginTop = '8px';
|
| 1438 |
-
suggestionsLabel.style.marginBottom = '6px';
|
| 1439 |
-
suggestionsLabel.style.fontWeight = '600';
|
| 1440 |
-
errorItem.appendChild(suggestionsLabel);
|
| 1441 |
-
|
| 1442 |
const suggestionsDiv = document.createElement('div');
|
| 1443 |
-
suggestionsDiv.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1444 |
error.suggestions.forEach(suggestion => {
|
| 1445 |
-
const
|
| 1446 |
-
|
| 1447 |
-
|
| 1448 |
-
suggestionsDiv.appendChild(
|
| 1449 |
});
|
| 1450 |
errorItem.appendChild(suggestionsDiv);
|
| 1451 |
}
|
| 1452 |
|
| 1453 |
-
|
| 1454 |
});
|
|
|
|
| 1455 |
}
|
| 1456 |
|
| 1457 |
-
|
| 1458 |
}
|
| 1459 |
|
| 1460 |
function displayLinkReport(linkReport) {
|
| 1461 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1462 |
|
| 1463 |
// Create link results section
|
| 1464 |
const linkSection = document.createElement('div');
|
| 1465 |
linkSection.className = 'link-validation-section';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1466 |
|
| 1467 |
const header = document.createElement('div');
|
| 1468 |
header.className = 'link-validation-header';
|
| 1469 |
-
header.innerHTML =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1470 |
linkSection.appendChild(header);
|
| 1471 |
|
| 1472 |
if (linkReport.length === 0) {
|
| 1473 |
const noLinks = document.createElement('div');
|
| 1474 |
noLinks.style.padding = '10px';
|
| 1475 |
-
noLinks.style.color = '
|
| 1476 |
noLinks.style.fontStyle = 'italic';
|
| 1477 |
noLinks.textContent = 'No links found in document.';
|
| 1478 |
linkSection.appendChild(noLinks);
|
| 1479 |
} else {
|
| 1480 |
const list = document.createElement('ul');
|
| 1481 |
list.className = 'link-list';
|
|
|
|
| 1482 |
|
| 1483 |
linkReport.forEach(link => {
|
| 1484 |
const item = document.createElement('li');
|
| 1485 |
-
let
|
| 1486 |
-
|
| 1487 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1488 |
|
| 1489 |
-
item.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1490 |
|
| 1491 |
const leftDiv = document.createElement('div');
|
| 1492 |
leftDiv.style.flex = '1';
|
| 1493 |
leftDiv.style.marginRight = '10px';
|
| 1494 |
leftDiv.style.overflow = 'hidden';
|
| 1495 |
-
|
| 1496 |
-
|
| 1497 |
-
|
| 1498 |
-
|
| 1499 |
-
|
| 1500 |
-
|
| 1501 |
-
|
| 1502 |
-
|
| 1503 |
-
|
| 1504 |
-
|
| 1505 |
-
|
| 1506 |
-
|
| 1507 |
-
|
| 1508 |
-
|
| 1509 |
-
|
|
|
|
|
|
|
| 1510 |
|
| 1511 |
item.appendChild(leftDiv);
|
| 1512 |
-
item.appendChild(
|
| 1513 |
list.appendChild(item);
|
| 1514 |
});
|
| 1515 |
linkSection.appendChild(list);
|
| 1516 |
}
|
| 1517 |
|
| 1518 |
-
|
| 1519 |
}
|
| 1520 |
|
| 1521 |
function displaySpellingOnlyResults(data) {
|
| 1522 |
const resultsDiv = document.getElementById('results');
|
| 1523 |
const statusDiv = document.getElementById('status');
|
| 1524 |
const summaryDiv = document.getElementById('summary');
|
| 1525 |
-
const elementsList = document.getElementById('elementsList');
|
| 1526 |
|
| 1527 |
-
//
|
| 1528 |
const hasErrors = data.spell_check && data.spell_check.total_errors > 0;
|
| 1529 |
-
statusDiv.textContent = hasErrors ? '⚠️ Issues Found' : '✓ Text Quality OK';
|
| 1530 |
statusDiv.className = `status ${hasErrors ? 'fail' : 'pass'}`;
|
| 1531 |
|
| 1532 |
-
|
| 1533 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1534 |
|
| 1535 |
-
//
|
|
|
|
| 1536 |
elementsList.innerHTML = '';
|
|
|
|
| 1537 |
|
| 1538 |
-
// Display spell check results
|
| 1539 |
if (data.spell_check) {
|
| 1540 |
displaySpellCheck(data.spell_check);
|
| 1541 |
}
|
|
@@ -1545,81 +1783,85 @@
|
|
| 1545 |
}
|
| 1546 |
|
| 1547 |
// Debug: Extract images
|
| 1548 |
-
document.getElementById('debugBtn')
|
| 1549 |
-
|
| 1550 |
-
|
| 1551 |
-
|
| 1552 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1553 |
|
| 1554 |
-
|
| 1555 |
-
|
| 1556 |
-
|
| 1557 |
-
|
| 1558 |
|
| 1559 |
-
|
| 1560 |
-
|
| 1561 |
-
return;
|
| 1562 |
-
}
|
| 1563 |
|
| 1564 |
-
|
| 1565 |
-
|
|
|
|
| 1566 |
|
| 1567 |
-
|
| 1568 |
-
|
| 1569 |
-
|
|
|
|
| 1570 |
|
| 1571 |
-
|
| 1572 |
-
method: 'POST',
|
| 1573 |
-
body: formData
|
| 1574 |
-
});
|
| 1575 |
|
| 1576 |
-
|
|
|
|
|
|
|
| 1577 |
|
| 1578 |
-
|
| 1579 |
-
|
| 1580 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1581 |
|
| 1582 |
-
|
| 1583 |
-
|
| 1584 |
-
|
| 1585 |
-
|
| 1586 |
-
|
| 1587 |
-
|
| 1588 |
-
|
| 1589 |
-
|
| 1590 |
-
|
| 1591 |
-
|
| 1592 |
-
|
| 1593 |
-
|
| 1594 |
-
}
|
| 1595 |
-
|
| 1596 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1597 |
|
| 1598 |
-
|
| 1599 |
-
|
| 1600 |
-
|
| 1601 |
-
output += `\n${idx + 1}. ${img.id}\n`;
|
| 1602 |
-
output += ` Path: ${img.file_path}\n`;
|
| 1603 |
-
output += ` Exists: ${img.file_exists ? 'Yes' : 'No'}\n`;
|
| 1604 |
-
output += ` Size: ${(img.file_size_bytes / 1024).toFixed(2)} KB\n`;
|
| 1605 |
-
output += ` Dimensions: ${img.dimensions}\n`;
|
| 1606 |
-
output += ` Mode: ${img.image_mode}\n`;
|
| 1607 |
-
output += ` Role: ${img.role_hint}\n`;
|
| 1608 |
-
output += ` Type: ${img.element_type}\n`;
|
| 1609 |
-
});
|
| 1610 |
-
} else {
|
| 1611 |
-
output += '\n⚠️ No images were extracted from the document.\n';
|
| 1612 |
-
output += 'This could mean:\n';
|
| 1613 |
-
output += ' - The document has no embedded images\n';
|
| 1614 |
-
output += ' - Images are in a format not supported\n';
|
| 1615 |
-
output += ' - Images are embedded as external links\n';
|
| 1616 |
}
|
|
|
|
|
|
|
| 1617 |
|
| 1618 |
-
debugInfo.innerHTML = '<pre>' + output + '</pre>';
|
| 1619 |
-
} catch (error) {
|
| 1620 |
-
debugInfo.innerHTML = '<pre style="color: red;">Error: ' + error.message + '</pre>';
|
| 1621 |
-
}
|
| 1622 |
-
});
|
| 1623 |
|
| 1624 |
// Function to display comparison results
|
| 1625 |
function displayComparisonResults(data) {
|
|
@@ -1921,52 +2163,65 @@
|
|
| 1921 |
}
|
| 1922 |
|
| 1923 |
// Create project modal handlers
|
| 1924 |
-
|
| 1925 |
-
|
| 1926 |
-
|
| 1927 |
-
|
| 1928 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1929 |
|
| 1930 |
-
document.getElementById('cancelProjectBtn')
|
| 1931 |
-
|
| 1932 |
-
|
|
|
|
|
|
|
|
|
|
| 1933 |
|
| 1934 |
-
document.getElementById('saveProjectBtn')
|
| 1935 |
-
|
| 1936 |
-
|
|
|
|
|
|
|
| 1937 |
|
| 1938 |
-
|
| 1939 |
-
|
| 1940 |
-
|
| 1941 |
-
|
| 1942 |
|
| 1943 |
-
|
| 1944 |
-
|
| 1945 |
-
|
| 1946 |
-
|
| 1947 |
-
|
| 1948 |
-
|
| 1949 |
|
| 1950 |
-
|
| 1951 |
-
|
| 1952 |
-
|
| 1953 |
-
|
| 1954 |
|
| 1955 |
-
|
| 1956 |
-
|
| 1957 |
-
|
| 1958 |
-
|
| 1959 |
-
|
| 1960 |
-
|
| 1961 |
-
|
| 1962 |
-
|
| 1963 |
-
|
|
|
|
| 1964 |
|
| 1965 |
// View all projects
|
| 1966 |
-
document.getElementById('viewProjectsBtn')
|
| 1967 |
-
|
| 1968 |
-
|
| 1969 |
-
|
|
|
|
|
|
|
|
|
|
| 1970 |
|
| 1971 |
|
| 1972 |
// ==================== SHAREPOINT INTEGRATION ====================
|
|
@@ -1978,89 +2233,152 @@
|
|
| 1978 |
breadcrumbs: [],
|
| 1979 |
selectedFiles: new Set()
|
| 1980 |
};
|
| 1981 |
-
|
| 1982 |
// Initialize UI based on auth state
|
| 1983 |
function updateSharePointUI() {
|
| 1984 |
-
|
| 1985 |
-
|
| 1986 |
-
|
| 1987 |
-
|
| 1988 |
-
|
| 1989 |
-
|
| 1990 |
-
|
| 1991 |
}
|
| 1992 |
updateSharePointUI();
|
| 1993 |
|
| 1994 |
// Connect Button Handler
|
| 1995 |
-
document.getElementById('connectSharePointBtn')
|
| 1996 |
-
|
| 1997 |
-
|
| 1998 |
-
|
| 1999 |
-
|
| 2000 |
-
// Open popup
|
| 2001 |
const width = 600;
|
| 2002 |
const height = 700;
|
| 2003 |
const left = (window.screen.width - width) / 2;
|
| 2004 |
const top = (window.screen.height - height) / 2;
|
| 2005 |
|
| 2006 |
-
window
|
| 2007 |
-
|
| 2008 |
-
'
|
|
|
|
| 2009 |
`width=${width},height=${height},top=${top},left=${left}`
|
| 2010 |
);
|
| 2011 |
-
} catch (error) {
|
| 2012 |
-
showError('Failed to start login: ' + error.message);
|
| 2013 |
-
}
|
| 2014 |
-
});
|
| 2015 |
|
| 2016 |
-
|
| 2017 |
-
|
| 2018 |
-
|
| 2019 |
-
|
| 2020 |
-
|
| 2021 |
-
|
| 2022 |
-
|
| 2023 |
-
|
| 2024 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2025 |
|
| 2026 |
// Logout
|
| 2027 |
-
document.getElementById('logoutSharePointBtn')
|
| 2028 |
-
|
| 2029 |
-
|
| 2030 |
-
|
| 2031 |
-
|
|
|
|
|
|
|
| 2032 |
|
| 2033 |
-
// Browse
|
| 2034 |
-
document.getElementById('browseSharePointBtn')
|
| 2035 |
-
|
| 2036 |
-
|
| 2037 |
-
|
|
|
|
|
|
|
|
|
|
| 2038 |
|
| 2039 |
-
|
| 2040 |
-
|
| 2041 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2042 |
|
| 2043 |
-
//
|
| 2044 |
-
|
| 2045 |
-
|
| 2046 |
-
|
| 2047 |
-
|
| 2048 |
-
|
| 2049 |
-
|
| 2050 |
-
|
| 2051 |
-
|
| 2052 |
-
|
| 2053 |
-
|
| 2054 |
-
|
| 2055 |
-
|
| 2056 |
-
|
| 2057 |
-
|
| 2058 |
-
|
| 2059 |
-
|
| 2060 |
-
|
| 2061 |
-
|
| 2062 |
-
|
| 2063 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2064 |
}
|
| 2065 |
|
| 2066 |
// Load Items (Folder level)
|
|
@@ -2203,48 +2521,21 @@
|
|
| 2203 |
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
| 2204 |
}
|
| 2205 |
|
| 2206 |
-
//
|
| 2207 |
-
document.getElementById('
|
| 2208 |
-
|
| 2209 |
-
|
| 2210 |
-
|
| 2211 |
-
|
| 2212 |
-
|
| 2213 |
-
|
| 2214 |
-
|
| 2215 |
-
|
| 2216 |
-
|
| 2217 |
-
|
| 2218 |
-
|
| 2219 |
-
|
| 2220 |
-
body: JSON.stringify({
|
| 2221 |
-
drive_id: spState.currentDriveId,
|
| 2222 |
-
file_ids: fileIds,
|
| 2223 |
-
token: spState.token,
|
| 2224 |
-
project_id: projectId ? parseInt(projectId) : null
|
| 2225 |
-
})
|
| 2226 |
-
});
|
| 2227 |
-
|
| 2228 |
-
if (!response.ok) throw new Error('Import failed');
|
| 2229 |
-
|
| 2230 |
-
const result = await response.json();
|
| 2231 |
-
document.getElementById('sharePointModal').style.display = 'none';
|
| 2232 |
-
spState.selectedFiles.clear();
|
| 2233 |
-
updateSelectionCount();
|
| 2234 |
-
|
| 2235 |
-
// Show success or redirect to results
|
| 2236 |
-
showStatus('Successfully imported files! Check validation results below.', 'pass');
|
| 2237 |
-
|
| 2238 |
-
// Trigger refresh of validations (if implemented) or just alert
|
| 2239 |
-
alert('Files imported successfully! (Validation logic to be connected fully in next step)');
|
| 2240 |
|
| 2241 |
-
} catch (error) {
|
| 2242 |
-
showError('Import failed: ' + error.message);
|
| 2243 |
-
} finally {
|
| 2244 |
-
btn.disabled = false;
|
| 2245 |
-
btn.textContent = 'Import & Validate';
|
| 2246 |
-
}
|
| 2247 |
-
});
|
| 2248 |
|
| 2249 |
// Load templates when page loads
|
| 2250 |
loadProjects();
|
|
|
|
| 379 |
}
|
| 380 |
}
|
| 381 |
|
| 382 |
+
/* Results Section Refined */
|
| 383 |
.results {
|
| 384 |
display: none;
|
| 385 |
+
margin-top: 32px;
|
| 386 |
+
animation: fadeIn 0.4s ease-out;
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
@keyframes fadeIn {
|
| 390 |
+
from {
|
| 391 |
+
opacity: 0;
|
| 392 |
+
transform: translateY(10px);
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
to {
|
| 396 |
+
opacity: 1;
|
| 397 |
+
transform: translateY(0);
|
| 398 |
+
}
|
| 399 |
}
|
| 400 |
|
| 401 |
.status {
|
| 402 |
+
display: flex;
|
| 403 |
+
align-items: center;
|
| 404 |
+
gap: 20px;
|
| 405 |
+
padding: 24px;
|
| 406 |
+
border-radius: var(--radius-lg);
|
| 407 |
+
margin-bottom: 32px;
|
| 408 |
+
border: 1px solid transparent;
|
| 409 |
+
box-shadow: var(--shadow-sm);
|
| 410 |
}
|
| 411 |
|
| 412 |
.status.pass {
|
| 413 |
+
background: #ECFDF5;
|
| 414 |
+
border-color: #A7F3D0;
|
| 415 |
+
color: #065F46;
|
| 416 |
}
|
| 417 |
|
| 418 |
.status.fail {
|
| 419 |
+
background: #FEF2F2;
|
| 420 |
+
border-color: #FECACA;
|
| 421 |
+
color: #991B1B;
|
| 422 |
}
|
| 423 |
|
| 424 |
+
.status-icon {
|
| 425 |
+
width: 56px;
|
| 426 |
+
height: 56px;
|
| 427 |
+
border-radius: 50%;
|
| 428 |
+
display: flex;
|
| 429 |
+
align-items: center;
|
| 430 |
+
justify-content: center;
|
| 431 |
+
background: rgba(255, 255, 255, 0.6);
|
| 432 |
+
font-size: 28px;
|
| 433 |
+
flex-shrink: 0;
|
| 434 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
.status-content {
|
| 438 |
+
flex: 1;
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
.status-content h3 {
|
| 442 |
+
font-size: 20px;
|
| 443 |
+
font-weight: 700;
|
| 444 |
+
margin-bottom: 6px;
|
| 445 |
+
letter-spacing: -0.01em;
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
.status-content p {
|
| 449 |
+
font-size: 15px;
|
| 450 |
+
opacity: 0.9;
|
| 451 |
+
line-height: 1.5;
|
| 452 |
}
|
| 453 |
|
| 454 |
+
.elements-grid {
|
| 455 |
+
display: grid;
|
| 456 |
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
| 457 |
+
gap: 20px;
|
| 458 |
list-style: none;
|
| 459 |
+
margin-bottom: 32px;
|
| 460 |
}
|
| 461 |
|
| 462 |
.element-item {
|
| 463 |
background: white;
|
| 464 |
+
border: 1px solid var(--border);
|
| 465 |
+
border-radius: var(--radius-md);
|
| 466 |
+
padding: 20px;
|
| 467 |
+
box-shadow: var(--shadow-sm);
|
| 468 |
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
| 469 |
+
position: relative;
|
| 470 |
+
overflow: hidden;
|
| 471 |
+
display: flex;
|
| 472 |
+
flex-direction: column;
|
| 473 |
+
height: 100%;
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
.element-item:hover {
|
| 477 |
+
transform: translateY(-4px);
|
| 478 |
+
box-shadow: var(--shadow-lg);
|
| 479 |
+
border-color: var(--primary-light);
|
| 480 |
}
|
| 481 |
|
| 482 |
+
.element-item::before {
|
| 483 |
+
content: '';
|
| 484 |
+
position: absolute;
|
| 485 |
+
left: 0;
|
| 486 |
+
top: 0;
|
| 487 |
+
bottom: 0;
|
| 488 |
+
width: 6px;
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
+
.element-item.present::before {
|
| 492 |
+
background: var(--success);
|
| 493 |
}
|
| 494 |
|
| 495 |
+
.element-item.missing::before {
|
| 496 |
+
background: var(--error);
|
| 497 |
}
|
| 498 |
|
| 499 |
+
.element-item.optional::before {
|
| 500 |
+
background: var(--warning);
|
| 501 |
}
|
| 502 |
|
| 503 |
.element-header {
|
| 504 |
display: flex;
|
| 505 |
justify-content: space-between;
|
| 506 |
+
align-items: flex-start;
|
| 507 |
+
margin-bottom: 16px;
|
| 508 |
}
|
| 509 |
|
| 510 |
.element-label {
|
| 511 |
+
font-weight: 700;
|
| 512 |
+
font-size: 16px;
|
| 513 |
+
color: var(--text-primary);
|
| 514 |
+
padding-left: 12px;
|
| 515 |
+
line-height: 1.3;
|
| 516 |
}
|
| 517 |
|
| 518 |
.element-badge {
|
| 519 |
+
font-size: 11px;
|
| 520 |
+
font-weight: 700;
|
| 521 |
+
text-transform: uppercase;
|
| 522 |
+
padding: 6px 12px;
|
| 523 |
+
border-radius: 20px;
|
| 524 |
+
letter-spacing: 0.5px;
|
| 525 |
+
flex-shrink: 0;
|
| 526 |
}
|
| 527 |
|
| 528 |
.badge-present {
|
| 529 |
+
background: #E6FFFA;
|
| 530 |
+
color: #047481;
|
| 531 |
+
border: 1px solid #B2F5EA;
|
| 532 |
}
|
| 533 |
|
| 534 |
.badge-missing {
|
| 535 |
+
background: #FFF5F5;
|
| 536 |
+
color: #C53030;
|
| 537 |
+
border: 1px solid #FED7D7;
|
| 538 |
}
|
| 539 |
|
| 540 |
.badge-optional {
|
| 541 |
+
background: #FFFFF0;
|
| 542 |
+
color: #B7791F;
|
| 543 |
+
border: 1px solid #FEFCBF;
|
| 544 |
}
|
| 545 |
|
| 546 |
.element-reason {
|
| 547 |
+
color: var(--text-secondary);
|
| 548 |
font-size: 14px;
|
| 549 |
+
margin-top: auto;
|
| 550 |
+
line-height: 1.5;
|
| 551 |
+
padding-left: 12px;
|
| 552 |
+
border-top: 1px solid #F3F4F6;
|
| 553 |
+
padding-top: 12px;
|
| 554 |
}
|
| 555 |
|
| 556 |
.error {
|
| 557 |
display: none;
|
| 558 |
+
background: #FEF2F2;
|
| 559 |
+
color: #991B1B;
|
| 560 |
+
padding: 20px;
|
| 561 |
+
border-radius: var(--radius-md);
|
| 562 |
+
margin-top: 24px;
|
| 563 |
+
border: 1px solid #FECACA;
|
| 564 |
+
font-weight: 500;
|
| 565 |
+
text-align: center;
|
| 566 |
}
|
| 567 |
|
| 568 |
.templates-loading {
|
|
|
|
| 819 |
background: #fff3cd;
|
| 820 |
color: #856404;
|
| 821 |
}
|
| 822 |
+
|
| 823 |
+
/* Loading animations */
|
| 824 |
+
@keyframes rotate {
|
| 825 |
+
100% {
|
| 826 |
+
transform: rotate(360deg);
|
| 827 |
+
}
|
| 828 |
+
}
|
| 829 |
+
|
| 830 |
+
@keyframes dash {
|
| 831 |
+
0% {
|
| 832 |
+
stroke-dashoffset: 80;
|
| 833 |
+
}
|
| 834 |
+
|
| 835 |
+
50% {
|
| 836 |
+
stroke-dashoffset: 20;
|
| 837 |
+
}
|
| 838 |
+
|
| 839 |
+
100% {
|
| 840 |
+
stroke-dashoffset: 80;
|
| 841 |
+
}
|
| 842 |
+
}
|
| 843 |
+
|
| 844 |
+
@keyframes progress {
|
| 845 |
+
0% {
|
| 846 |
+
width: 5%;
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
50% {
|
| 850 |
+
width: 70%;
|
| 851 |
+
}
|
| 852 |
+
|
| 853 |
+
100% {
|
| 854 |
+
width: 95%;
|
| 855 |
+
}
|
| 856 |
+
}
|
| 857 |
</style>
|
| 858 |
</head>
|
| 859 |
|
|
|
|
| 913 |
<p>Upload a document and select a template to validate against</p>
|
| 914 |
</div>
|
| 915 |
|
| 916 |
+
<!-- VALIDATE PAGE -->
|
| 917 |
+
<div id="validatePage" class="page-section">
|
| 918 |
+
<!-- Project Selector Card -->
|
| 919 |
+
<div class="card" style="padding: 16px;">
|
| 920 |
+
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap;">
|
| 921 |
+
<div style="display: flex; align-items: center; gap: 8px;">
|
| 922 |
+
<span style="font-size: 20px;">📂</span>
|
| 923 |
+
<label style="font-weight: 600; margin: 0; white-space: nowrap;">Project:</label>
|
| 924 |
+
</div>
|
| 925 |
+
<select id="currentProject" style="flex: 1; min-width: 200px;">
|
| 926 |
+
<option value="">No Project (Not Saved)</option>
|
| 927 |
+
</select>
|
| 928 |
+
<button type="button" class="btn btn-secondary" id="createProjectBtn">+ New</button>
|
| 929 |
+
<button type="button" class="btn btn-secondary" id="viewProjectsBtn">View All</button>
|
| 930 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 931 |
</div>
|
|
|
|
| 932 |
|
| 933 |
+
<!-- SharePoint Integration -->
|
| 934 |
+
<div
|
| 935 |
+
style="background: #f3f6f9; padding: 15px; border-radius: 8px; margin-bottom: 30px; border-left: 4px solid #0078d4; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px;">
|
| 936 |
+
<div style="display: flex; align-items: center; gap: 10px;">
|
| 937 |
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
| 938 |
+
<path d="M12.5 4H19.5C20.6046 4 21.5 4.89543 21.5 6V20C21.5 21.1046 20.6046 22 19.5 22H12.5V4Z"
|
| 939 |
+
fill="#0078D4" />
|
| 940 |
+
<path d="M2.5 6C2.5 4.89543 3.39543 4 4.5 4H11.5V22H4.5C3.39543 22 2.5 21.1046 2.5 20V6Z"
|
| 941 |
+
fill="#50E6FF" fill-opacity="0.3" />
|
| 942 |
+
<path d="M11.5 4V13H7.5V7H11.5V4Z" fill="#0078D4" fill-opacity="0.5" />
|
| 943 |
+
</svg>
|
| 944 |
+
<div>
|
| 945 |
+
<strong style="display: block; color: #333;">Microsoft SharePoint / OneDrive</strong>
|
| 946 |
+
<span style="font-size: 12px; color: #666;">Import documents directly from cloud</span>
|
| 947 |
+
</div>
|
| 948 |
+
</div>
|
| 949 |
+
<div id="sharepointAuthSection">
|
| 950 |
+
<button type="button" class="btn-secondary" id="connectSharePointBtn"
|
| 951 |
+
style="border: 1px solid #0078d4; color: #0078d4; background: white;">
|
| 952 |
+
🔗 Connect Account
|
| 953 |
+
</button>
|
| 954 |
+
</div>
|
| 955 |
+
<div id="sharepointActionsSection" style="display: none;">
|
| 956 |
+
<span id="sharepointStatus"
|
| 957 |
+
style="font-size: 12px; color: #28a745; font-weight: 600; margin-right: 10px;">✓
|
| 958 |
+
Connected</span>
|
| 959 |
+
<button type="button" class="btn-secondary" id="browseSharePointBtn"
|
| 960 |
+
style="background: #0078d4; color: white; border: none;">
|
| 961 |
+
📂 Browse Files
|
| 962 |
+
</button>
|
| 963 |
+
<button type="button" class="btn-secondary" id="logoutSharePointBtn"
|
| 964 |
+
style="background: #eee; border: 1px solid #ccc; font-size: 12px; padding: 5px 10px;">
|
| 965 |
+
Disconnect
|
| 966 |
+
</button>
|
| 967 |
</div>
|
| 968 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 969 |
|
| 970 |
+
<!-- Validation Form Card -->
|
| 971 |
+
<div class="card" id="validateSection">
|
| 972 |
+
<div class="card-header">
|
| 973 |
+
<div class="card-icon accent">✓</div>
|
| 974 |
+
<h2>Validate Document</h2>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 975 |
</div>
|
| 976 |
|
| 977 |
+
<form id="validationForm">
|
| 978 |
+
<div class="form-group">
|
| 979 |
+
<label for="templateSelect">Select Template <span class="optional">(Required for template
|
| 980 |
+
validation)</span></label>
|
| 981 |
+
<select id="templateSelect" name="template">
|
| 982 |
+
<option value="">-- Select a template --</option>
|
| 983 |
+
</select>
|
| 984 |
+
</div>
|
| 985 |
|
| 986 |
+
<div class="form-group">
|
| 987 |
+
<label for="fileInput">Upload Document</label>
|
| 988 |
+
<div class="file-upload-wrapper" id="dropZone">
|
| 989 |
+
<div class="file-upload-icon">📄</div>
|
| 990 |
+
<div class="file-upload-text">
|
| 991 |
+
<strong>Click to upload</strong> or drag and drop<br>
|
| 992 |
+
PDF, DOCX, or PPTX files
|
| 993 |
+
</div>
|
| 994 |
+
<input type="file" id="fileInput" name="file" accept=".pdf,.docx,.pptx" required
|
| 995 |
+
style="display: none;">
|
| 996 |
+
</div>
|
| 997 |
+
<div class="file-info" id="fileInfo" style="display: none;"></div>
|
| 998 |
+
</div>
|
| 999 |
|
| 1000 |
+
<div class="form-group">
|
| 1001 |
+
<label for="customPrompt">Custom Instructions <span class="optional">(Optional)</span></label>
|
| 1002 |
+
<textarea id="customPrompt" name="customPrompt" rows="3" maxlength="500"
|
| 1003 |
+
placeholder="e.g., 'Focus on date format validation' or 'Pay special attention to logo placement'..."></textarea>
|
| 1004 |
+
<div style="text-align: right; font-size: 12px; color: var(--text-muted); margin-top: 4px;">
|
| 1005 |
+
<span id="charCount">0</span>/500 characters
|
| 1006 |
+
</div>
|
| 1007 |
+
</div>
|
| 1008 |
+
|
| 1009 |
+
<div class="button-group">
|
| 1010 |
+
<button type="button" class="btn btn-primary btn-lg" id="validateBtn">
|
| 1011 |
+
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 1012 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
| 1013 |
+
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
| 1014 |
+
</svg>
|
| 1015 |
+
Validate Document
|
| 1016 |
+
</button>
|
| 1017 |
+
<button type="button" class="btn btn-secondary btn-lg" id="spellingOnlyBtn">
|
| 1018 |
+
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 1019 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
| 1020 |
+
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z">
|
| 1021 |
+
</path>
|
| 1022 |
+
</svg>
|
| 1023 |
+
Quality Check Only
|
| 1024 |
+
</button>
|
| 1025 |
+
</div>
|
| 1026 |
+
<p style="font-size: 13px; color: var(--text-muted); margin-top: 12px; text-align: center;">
|
| 1027 |
+
💡 Use "Quality Check Only" for grammar and spelling without template validation
|
| 1028 |
+
</p>
|
| 1029 |
+
</form>
|
| 1030 |
</div>
|
|
|
|
| 1031 |
|
| 1032 |
+
<div class="loading" id="loading">
|
| 1033 |
+
<div class="spinner"></div>
|
| 1034 |
+
<p>Validating document...</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1035 |
</div>
|
|
|
|
| 1036 |
|
| 1037 |
+
<div class="error" id="error"></div>
|
| 1038 |
+
|
| 1039 |
+
<div class="results" id="results">
|
| 1040 |
+
<div class="status" id="status"></div>
|
| 1041 |
+
<div class="summary" id="summary"></div>
|
| 1042 |
+
<ul class="elements-list" id="elementsList"></ul>
|
| 1043 |
</div>
|
| 1044 |
+
</div>
|
| 1045 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1046 |
|
| 1047 |
+
|
| 1048 |
+
<!-- COMPARE PAGE -->
|
| 1049 |
+
<div id="comparePage" class="page-section" style="display: none;">
|
| 1050 |
+
<!-- Document Comparison Section -->
|
| 1051 |
+
<div class="comparison-section"
|
| 1052 |
+
style="background: #f8f9fa; padding: 25px; border-radius: 8px; margin-top: 30px;">
|
| 1053 |
+
<h3 style="margin-bottom: 15px; font-size: 20px; color: #333;">🔄 Compare Documents</h3>
|
| 1054 |
+
<p style="color: #666; font-size: 14px; margin-bottom: 20px;">
|
| 1055 |
+
Upload two versions of a document to see what changed (e.g., before and after edits).
|
| 1056 |
+
</p>
|
| 1057 |
+
|
| 1058 |
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;">
|
| 1059 |
+
<div class="form-group">
|
| 1060 |
+
<label for="compareFile1">📄 Original Document:</label>
|
| 1061 |
+
<input type="file" id="compareFile1" accept=".pdf,.docx,.pptx">
|
| 1062 |
+
<div class="file-info" id="compareFileInfo1"></div>
|
| 1063 |
</div>
|
|
|
|
|
|
|
| 1064 |
|
| 1065 |
+
<div class="form-group">
|
| 1066 |
+
<label for="compareFile2">📝 Modified Document:</label>
|
| 1067 |
+
<input type="file" id="compareFile2" accept=".pdf,.docx,.pptx">
|
| 1068 |
+
<div class="file-info" id="compareFileInfo2"></div>
|
|
|
|
|
|
|
| 1069 |
</div>
|
| 1070 |
</div>
|
| 1071 |
|
| 1072 |
+
<button type="button" class="btn" id="compareBtn" style="width: 100%;">
|
| 1073 |
+
🔍 Compare Documents
|
| 1074 |
+
</button>
|
| 1075 |
+
|
| 1076 |
+
<div class="error" id="compareError" style="margin-top: 15px;"></div>
|
| 1077 |
+
|
| 1078 |
+
<!-- Loading Indicator -->
|
| 1079 |
+
<div id="compareLoading"
|
| 1080 |
+
style="display: none; margin-top: 20px; text-align: center; padding: 40px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; color: white;">
|
| 1081 |
+
<div style="margin-bottom: 20px;">
|
| 1082 |
+
<svg width="60" height="60" viewBox="0 0 50 50" style="animation: rotate 2s linear infinite;">
|
| 1083 |
+
<circle cx="25" cy="25" r="20" fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="4">
|
| 1084 |
+
</circle>
|
| 1085 |
+
<circle cx="25" cy="25" r="20" fill="none" stroke="white" stroke-width="4"
|
| 1086 |
+
stroke-dasharray="80" stroke-dashoffset="60" stroke-linecap="round"
|
| 1087 |
+
style="animation: dash 1.5s ease-in-out infinite;"></circle>
|
| 1088 |
</svg>
|
| 1089 |
+
</div>
|
| 1090 |
+
<h3 style="margin: 0 0 10px 0; font-size: 18px; font-weight: 600;">Comparing Documents...</h3>
|
| 1091 |
+
<p id="compareLoadingText" style="margin: 0; opacity: 0.9; font-size: 14px;">Extracting and
|
| 1092 |
+
analyzing content</p>
|
| 1093 |
+
<div
|
| 1094 |
+
style="margin-top: 15px; background: rgba(255,255,255,0.2); border-radius: 8px; height: 6px; overflow: hidden;">
|
| 1095 |
+
<div id="compareProgressBar"
|
| 1096 |
+
style="height: 100%; background: white; width: 0%; transition: width 0.5s ease; animation: progress 3s ease-in-out infinite;">
|
| 1097 |
+
</div>
|
| 1098 |
+
</div>
|
| 1099 |
</div>
|
| 1100 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1101 |
|
| 1102 |
+
<!-- Comparison Results inside Compare Page -->
|
| 1103 |
+
<div class="results" id="comparisonResults" style="display: none;">
|
| 1104 |
+
<h2 style="margin-bottom: 20px;">📊 Comparison Results</h2>
|
| 1105 |
+
<div id="comparisonSummary" style="margin-bottom: 20px;"></div>
|
| 1106 |
+
<div id="comparisonDetails"></div>
|
| 1107 |
+
</div>
|
|
|
|
| 1108 |
</div>
|
| 1109 |
|
| 1110 |
+
<!-- BULK PAGE -->
|
| 1111 |
+
<div id="bulkPage" class="page-section" style="display: none;">
|
| 1112 |
+
<!-- Bulk Certificate Validation Section -->
|
| 1113 |
+
<div class="bulk-validation-section"
|
| 1114 |
+
style="background: #f0f8ff; padding: 25px; border-radius: 8px; margin-top: 30px;">
|
| 1115 |
+
<h3 style="margin-bottom: 15px; font-size: 20px; color: #333;">📋 Bulk Certificate Validation
|
| 1116 |
+
</h3>
|
| 1117 |
+
<p style="color: #666; font-size: 14px; margin-bottom: 20px;">
|
| 1118 |
+
Upload an Excel list of names and multiple certificates to verify all attendees received
|
| 1119 |
+
their
|
| 1120 |
+
certificates.
|
| 1121 |
+
</p>
|
|
|
|
|
|
|
| 1122 |
|
| 1123 |
+
<!-- Step 1: Excel Upload -->
|
| 1124 |
+
<div class="form-group" style="margin-bottom: 20px;">
|
| 1125 |
+
<label for="excelFile">1️⃣ Upload Excel File with Names:</label>
|
| 1126 |
+
<input type="file" id="excelFile" accept=".xlsx">
|
| 1127 |
+
<div class="file-info" id="excelFileInfo"></div>
|
| 1128 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1129 |
|
| 1130 |
+
<!-- Step 2: Column Selection -->
|
| 1131 |
+
<div class="form-group" style="margin-bottom: 20px; display: none;" id="columnSelectorGroup">
|
| 1132 |
+
<label for="nameColumn">2️⃣ Select Column Containing Names:</label>
|
| 1133 |
+
<select id="nameColumn"
|
| 1134 |
+
style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px;">
|
| 1135 |
+
<option value="">Loading columns...</option>
|
| 1136 |
+
</select>
|
| 1137 |
+
<div style="margin-top: 8px; color: #666; font-size: 13px;">
|
| 1138 |
+
Preview: <span id="namePreview" style="font-weight: 500;"></span>
|
| 1139 |
+
</div>
|
| 1140 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1141 |
|
| 1142 |
+
<!-- Step 3: Certificates Upload -->
|
| 1143 |
+
<div class="form-group" style="margin-bottom: 20px;">
|
| 1144 |
+
<label for="certificateFiles">3️⃣ Upload Certificates (Max 150):</label>
|
| 1145 |
+
<input type="file" id="certificateFiles" multiple accept=".pdf,.pptx">
|
| 1146 |
+
<div style=" margin-top: 8px;">
|
| 1147 |
+
<span style="font-weight: 600; color: #007bff;" id="certCount">0</span>
|
| 1148 |
+
<span style="color: #666;">/150 files selected</span>
|
| 1149 |
+
</div>
|
| 1150 |
</div>
|
|
|
|
| 1151 |
|
| 1152 |
+
<!-- Step 4: Validate Button -->
|
| 1153 |
+
<button type="button" class="btn" id="bulkValidateBtn" style="width: 100%;" disabled>
|
| 1154 |
+
✅ Validate All Certificates
|
| 1155 |
+
</button>
|
|
|
|
|
|
|
|
|
|
| 1156 |
</div>
|
|
|
|
| 1157 |
|
| 1158 |
+
<!-- Bulk Validation Results inside Bulk Page -->
|
| 1159 |
+
<div class="results" id="bulkResults" style="display: none;">
|
| 1160 |
+
<h2 style="margin-bottom: 20px;">📊 Bulk Validation Results</h2>
|
| 1161 |
+
<div id="bulkSummary" style="margin-bottom: 20px;"></div>
|
| 1162 |
+
<button type="button" class="btn-secondary" id="downloadCSVBtn" style="margin-bottom: 20px;">
|
| 1163 |
+
📥 Download CSV Report
|
| 1164 |
+
</button>
|
| 1165 |
+
<div id="bulkDetails"></div>
|
| 1166 |
+
</div>
|
| 1167 |
</div>
|
| 1168 |
|
| 1169 |
<div class="loading" id="loading">
|
|
|
|
| 1173 |
|
| 1174 |
<div class="error" id="error"></div>
|
| 1175 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1176 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1177 |
</main>
|
| 1178 |
|
| 1179 |
<script>
|
| 1180 |
+
// Tab Navigation Logic
|
| 1181 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 1182 |
+
const navItems = document.querySelectorAll('.nav-item[data-tab]');
|
| 1183 |
+
const pages = {
|
| 1184 |
+
'validate': document.getElementById('validatePage'),
|
| 1185 |
+
'compare': document.getElementById('comparePage'),
|
| 1186 |
+
'bulk': document.getElementById('bulkPage')
|
| 1187 |
+
};
|
| 1188 |
+
const pageHeaderTitle = document.querySelector('.page-header h1');
|
| 1189 |
+
const pageHeaderDesc = document.querySelector('.page-header p');
|
| 1190 |
+
|
| 1191 |
+
const pageInfo = {
|
| 1192 |
+
'validate': { title: 'Document Validation', desc: 'Upload a document and select a template to validate against' },
|
| 1193 |
+
'compare': { title: 'Compare Documents', desc: 'Upload two versions of a document to see differences' },
|
| 1194 |
+
'bulk': { title: 'Bulk Certificate Validation', desc: 'Validate multiple certificates against an Excel list' }
|
| 1195 |
+
};
|
| 1196 |
+
|
| 1197 |
+
navItems.forEach(item => {
|
| 1198 |
+
item.addEventListener('click', () => {
|
| 1199 |
+
const tabName = item.getAttribute('data-tab');
|
| 1200 |
+
|
| 1201 |
+
// Update Sidebar
|
| 1202 |
+
navItems.forEach(nav => nav.classList.remove('active'));
|
| 1203 |
+
item.classList.add('active');
|
| 1204 |
+
|
| 1205 |
+
// Update Pages
|
| 1206 |
+
Object.values(pages).forEach(page => {
|
| 1207 |
+
if (page) page.style.display = 'none';
|
| 1208 |
+
});
|
| 1209 |
+
if (pages[tabName]) {
|
| 1210 |
+
pages[tabName].style.display = 'block';
|
| 1211 |
+
|
| 1212 |
+
// Update Header
|
| 1213 |
+
if (pageInfo[tabName]) {
|
| 1214 |
+
pageHeaderTitle.textContent = pageInfo[tabName].title;
|
| 1215 |
+
pageHeaderDesc.textContent = pageInfo[tabName].desc;
|
| 1216 |
+
}
|
| 1217 |
+
}
|
| 1218 |
+
|
| 1219 |
+
// Clear and hide global error on tab switch
|
| 1220 |
+
const errorDiv = document.getElementById('error');
|
| 1221 |
+
errorDiv.style.display = 'none';
|
| 1222 |
+
errorDiv.textContent = '';
|
| 1223 |
+
});
|
| 1224 |
+
});
|
| 1225 |
+
|
| 1226 |
+
// Initial load of templates
|
| 1227 |
+
loadTemplates();
|
| 1228 |
+
});
|
| 1229 |
+
|
| 1230 |
// Load templates on page load
|
| 1231 |
async function loadTemplates() {
|
| 1232 |
try {
|
|
|
|
| 1320 |
document.getElementById('compareBtn').addEventListener('click', async function () {
|
| 1321 |
const file1 = document.getElementById('compareFile1').files[0];
|
| 1322 |
const file2 = document.getElementById('compareFile2').files[0];
|
| 1323 |
+
const compareError = document.getElementById('compareError');
|
| 1324 |
+
const compareLoading = document.getElementById('compareLoading');
|
| 1325 |
+
const loadingText = document.getElementById('compareLoadingText');
|
| 1326 |
+
|
| 1327 |
+
// Clear previous errors
|
| 1328 |
+
compareError.style.display = 'none';
|
| 1329 |
+
compareError.textContent = '';
|
| 1330 |
|
| 1331 |
if (!file1 || !file2) {
|
| 1332 |
+
compareError.textContent = 'Please select both documents to compare';
|
| 1333 |
+
compareError.style.display = 'block';
|
| 1334 |
return;
|
| 1335 |
}
|
| 1336 |
|
| 1337 |
// Hide previous results
|
| 1338 |
document.getElementById('results').style.display = 'none';
|
| 1339 |
document.getElementById('comparisonResults').style.display = 'none';
|
| 1340 |
+
|
| 1341 |
+
// Show loading indicator
|
| 1342 |
+
compareLoading.style.display = 'block';
|
| 1343 |
+
loadingText.textContent = 'Extracting text from documents...';
|
| 1344 |
+
this.disabled = true;
|
| 1345 |
|
| 1346 |
try {
|
| 1347 |
const formData = new FormData();
|
| 1348 |
formData.append('file1', file1);
|
| 1349 |
formData.append('file2', file2);
|
| 1350 |
|
| 1351 |
+
// Update status
|
| 1352 |
+
loadingText.textContent = 'Analyzing differences with AI...';
|
| 1353 |
+
|
| 1354 |
const response = await fetch('/compare', {
|
| 1355 |
method: 'POST',
|
| 1356 |
body: formData
|
| 1357 |
});
|
| 1358 |
|
| 1359 |
+
loadingText.textContent = 'Processing results...';
|
| 1360 |
+
|
| 1361 |
const data = await response.json();
|
| 1362 |
|
| 1363 |
if (!response.ok) {
|
|
|
|
| 1366 |
|
| 1367 |
displayComparisonResults(data);
|
| 1368 |
} catch (error) {
|
| 1369 |
+
compareError.textContent = error.message || 'An error occurred during comparison';
|
| 1370 |
+
compareError.style.display = 'block';
|
| 1371 |
} finally {
|
| 1372 |
+
compareLoading.style.display = 'none';
|
| 1373 |
+
this.disabled = false;
|
| 1374 |
}
|
| 1375 |
});
|
| 1376 |
|
|
|
|
| 1506 |
const summaryDiv = document.getElementById('summary');
|
| 1507 |
const elementsList = document.getElementById('elementsList');
|
| 1508 |
|
| 1509 |
+
// 1. Status Section
|
| 1510 |
+
const isPass = data.status === 'PASS';
|
| 1511 |
statusDiv.className = `status ${data.status.toLowerCase()}`;
|
| 1512 |
|
| 1513 |
+
const iconSvg = isPass
|
| 1514 |
+
? '<svg class="status-icon-svg" width="32" height="32" fill="none" stroke="#059669" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"/></svg>'
|
| 1515 |
+
: '<svg class="status-icon-svg" width="32" height="32" fill="none" stroke="#DC2626" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M6 18L18 6M6 6l12 12"/></svg>';
|
| 1516 |
|
| 1517 |
+
statusDiv.innerHTML = `
|
| 1518 |
+
<div class="status-icon">
|
| 1519 |
+
${iconSvg}
|
| 1520 |
+
</div>
|
| 1521 |
+
<div class="status-content">
|
| 1522 |
+
<h3>${isPass ? 'Validation Passed' : 'Validation Failed'}</h3>
|
| 1523 |
+
<p>${data.summary || 'Validation run completed.'}</p>
|
| 1524 |
+
</div>
|
| 1525 |
+
`;
|
| 1526 |
+
|
| 1527 |
+
// Hide separate summary div
|
| 1528 |
+
summaryDiv.style.display = 'none';
|
| 1529 |
+
|
| 1530 |
+
// 2. Elements Grid
|
| 1531 |
elementsList.innerHTML = '';
|
| 1532 |
+
elementsList.className = 'elements-grid'; // Ensure grid class is used
|
| 1533 |
+
|
| 1534 |
data.elements_report.forEach(element => {
|
| 1535 |
const li = document.createElement('li');
|
| 1536 |
li.className = `element-item ${element.is_present ? 'present' : 'missing'} ${!element.required ? 'optional' : ''}`;
|
| 1537 |
|
| 1538 |
+
let badgeClass = element.is_present ? 'badge-present' : 'badge-missing';
|
| 1539 |
+
let badgeText = element.is_present ? 'PRESENT' : 'MISSING';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1540 |
if (!element.required) {
|
| 1541 |
+
badgeClass = 'badge-optional';
|
| 1542 |
+
badgeText = 'OPTIONAL';
|
|
|
|
| 1543 |
}
|
| 1544 |
|
| 1545 |
+
li.innerHTML = `
|
| 1546 |
+
<div class="element-header">
|
| 1547 |
+
<span class="element-label">${element.label}</span>
|
| 1548 |
+
<span class="element-badge ${badgeClass}">${badgeText}</span>
|
| 1549 |
+
</div>
|
| 1550 |
+
<div class="element-reason">${element.reason}</div>
|
| 1551 |
+
`;
|
|
|
|
|
|
|
| 1552 |
elementsList.appendChild(li);
|
| 1553 |
});
|
| 1554 |
|
|
|
|
| 1567 |
}
|
| 1568 |
|
| 1569 |
function displaySpellCheck(spellCheck) {
|
| 1570 |
+
const resultsDiv = document.getElementById('results');
|
| 1571 |
+
|
| 1572 |
+
// Remove existing spell check section
|
| 1573 |
+
const existing = resultsDiv.querySelectorAll('.spell-check-section');
|
| 1574 |
+
existing.forEach(el => el.remove());
|
| 1575 |
|
| 1576 |
// Create spell check section
|
| 1577 |
const spellSection = document.createElement('div');
|
|
|
|
| 1579 |
|
| 1580 |
const header = document.createElement('div');
|
| 1581 |
header.className = 'spell-check-header';
|
| 1582 |
+
header.innerHTML = `
|
| 1583 |
+
<div style="display:flex; align-items:center; gap:12px;">
|
| 1584 |
+
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
|
| 1585 |
+
Quality & Spelling Check
|
| 1586 |
+
</div>
|
| 1587 |
+
<span style="font-size: 14px; color: var(--text-secondary); font-weight: normal;">${spellCheck.summary}</span>
|
| 1588 |
+
`;
|
| 1589 |
spellSection.appendChild(header);
|
| 1590 |
|
| 1591 |
if (spellCheck.total_errors === 0) {
|
| 1592 |
const noErrors = document.createElement('div');
|
| 1593 |
+
noErrors.className = 'status pass';
|
| 1594 |
+
noErrors.style.marginBottom = '0';
|
| 1595 |
+
noErrors.style.display = 'flex';
|
| 1596 |
+
noErrors.innerHTML = `
|
| 1597 |
+
<div style="font-size: 20px;">✓</div>
|
| 1598 |
+
<div>No quality or spelling errors found!</div>
|
| 1599 |
+
`;
|
| 1600 |
spellSection.appendChild(noErrors);
|
| 1601 |
} else {
|
| 1602 |
+
const grid = document.createElement('div');
|
| 1603 |
+
grid.className = 'spell-errors-grid';
|
| 1604 |
+
|
| 1605 |
spellCheck.errors.forEach(error => {
|
| 1606 |
const errorItem = document.createElement('div');
|
| 1607 |
+
errorItem.className = 'element-item'; // Reuse card style
|
| 1608 |
+
errorItem.style.borderLeft = '4px solid var(--warning)'; // Distinctive
|
| 1609 |
|
| 1610 |
const wordDiv = document.createElement('div');
|
| 1611 |
+
wordDiv.style.marginBottom = '8px';
|
| 1612 |
+
wordDiv.innerHTML = `<span style="font-weight:700; font-size:16px; color:#1F2937;">"${error.word}"</span> <span class="element-badge badge-missing" style="font-size:10px; margin-left:8px;">${error.error_type}</span>`;
|
| 1613 |
errorItem.appendChild(wordDiv);
|
| 1614 |
|
| 1615 |
if (error.context) {
|
| 1616 |
const contextDiv = document.createElement('div');
|
| 1617 |
+
contextDiv.className = 'element-reason';
|
| 1618 |
+
contextDiv.style.borderTop = 'none';
|
| 1619 |
+
contextDiv.style.paddingLeft = '0';
|
| 1620 |
+
contextDiv.style.fontStyle = 'italic';
|
| 1621 |
contextDiv.textContent = `Context: "${error.context}"`;
|
| 1622 |
errorItem.appendChild(contextDiv);
|
| 1623 |
}
|
| 1624 |
|
| 1625 |
if (error.suggestions && error.suggestions.length > 0) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1626 |
const suggestionsDiv = document.createElement('div');
|
| 1627 |
+
suggestionsDiv.style.marginTop = '12px';
|
| 1628 |
+
suggestionsDiv.style.display = 'flex';
|
| 1629 |
+
suggestionsDiv.style.gap = '8px';
|
| 1630 |
+
suggestionsDiv.style.flexWrap = 'wrap';
|
| 1631 |
+
|
| 1632 |
error.suggestions.forEach(suggestion => {
|
| 1633 |
+
const badge = document.createElement('span');
|
| 1634 |
+
badge.className = 'element-badge badge-present';
|
| 1635 |
+
badge.textContent = suggestion;
|
| 1636 |
+
suggestionsDiv.appendChild(badge);
|
| 1637 |
});
|
| 1638 |
errorItem.appendChild(suggestionsDiv);
|
| 1639 |
}
|
| 1640 |
|
| 1641 |
+
grid.appendChild(errorItem);
|
| 1642 |
});
|
| 1643 |
+
spellSection.appendChild(grid);
|
| 1644 |
}
|
| 1645 |
|
| 1646 |
+
resultsDiv.appendChild(spellSection);
|
| 1647 |
}
|
| 1648 |
|
| 1649 |
function displayLinkReport(linkReport) {
|
| 1650 |
+
const resultsDiv = document.getElementById('results');
|
| 1651 |
+
|
| 1652 |
+
// Remove existing
|
| 1653 |
+
const existing = resultsDiv.querySelectorAll('.link-validation-section');
|
| 1654 |
+
existing.forEach(el => el.remove());
|
| 1655 |
|
| 1656 |
// Create link results section
|
| 1657 |
const linkSection = document.createElement('div');
|
| 1658 |
linkSection.className = 'link-validation-section';
|
| 1659 |
+
// Inline styles to match refined look (or could add to CSS block)
|
| 1660 |
+
linkSection.style.background = 'white';
|
| 1661 |
+
linkSection.style.border = '1px solid var(--border)';
|
| 1662 |
+
linkSection.style.borderRadius = 'var(--radius-lg)';
|
| 1663 |
+
linkSection.style.padding = '24px';
|
| 1664 |
+
linkSection.style.marginTop = '32px';
|
| 1665 |
+
linkSection.style.boxShadow = 'var(--shadow-sm)';
|
| 1666 |
|
| 1667 |
const header = document.createElement('div');
|
| 1668 |
header.className = 'link-validation-header';
|
| 1669 |
+
header.innerHTML = `
|
| 1670 |
+
<div style="display:flex; align-items:center; gap:12px; margin-bottom:16px; font-size:18px; font-weight:700; color:var(--text-primary); border-bottom:2px solid var(--bg-main); padding-bottom:16px;">
|
| 1671 |
+
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path></svg>
|
| 1672 |
+
Link Validation
|
| 1673 |
+
<span style="font-size: 14px; color: var(--text-secondary); font-weight: normal; margin-left: auto;">${linkReport.length} link(s) checked</span>
|
| 1674 |
+
</div>
|
| 1675 |
+
`;
|
| 1676 |
linkSection.appendChild(header);
|
| 1677 |
|
| 1678 |
if (linkReport.length === 0) {
|
| 1679 |
const noLinks = document.createElement('div');
|
| 1680 |
noLinks.style.padding = '10px';
|
| 1681 |
+
noLinks.style.color = 'var(--text-secondary)';
|
| 1682 |
noLinks.style.fontStyle = 'italic';
|
| 1683 |
noLinks.textContent = 'No links found in document.';
|
| 1684 |
linkSection.appendChild(noLinks);
|
| 1685 |
} else {
|
| 1686 |
const list = document.createElement('ul');
|
| 1687 |
list.className = 'link-list';
|
| 1688 |
+
list.style.listStyle = 'none';
|
| 1689 |
|
| 1690 |
linkReport.forEach(link => {
|
| 1691 |
const item = document.createElement('li');
|
| 1692 |
+
let statusColor = 'var(--success)';
|
| 1693 |
+
let borderColor = 'var(--success-bg)';
|
| 1694 |
+
let bgColor = '#F0FDF4';
|
| 1695 |
+
|
| 1696 |
+
if (link.status === 'broken') {
|
| 1697 |
+
statusColor = 'var(--error)';
|
| 1698 |
+
borderColor = 'var(--error-bg)';
|
| 1699 |
+
bgColor = '#FEF2F2';
|
| 1700 |
+
}
|
| 1701 |
+
if (link.status === 'warning') {
|
| 1702 |
+
statusColor = 'var(--warning)';
|
| 1703 |
+
borderColor = 'var(--warning-bg)';
|
| 1704 |
+
bgColor = '#FFFBEB';
|
| 1705 |
+
}
|
| 1706 |
|
| 1707 |
+
item.style.display = 'flex';
|
| 1708 |
+
item.style.marginBottom = '10px';
|
| 1709 |
+
item.style.padding = '12px';
|
| 1710 |
+
item.style.background = bgColor;
|
| 1711 |
+
item.style.border = `1px solid ${borderColor}`;
|
| 1712 |
+
item.style.borderRadius = 'var(--radius-md)';
|
| 1713 |
+
item.style.alignItems = 'center';
|
| 1714 |
|
| 1715 |
const leftDiv = document.createElement('div');
|
| 1716 |
leftDiv.style.flex = '1';
|
| 1717 |
leftDiv.style.marginRight = '10px';
|
| 1718 |
leftDiv.style.overflow = 'hidden';
|
| 1719 |
+
leftDiv.style.textOverflow = 'ellipsis';
|
| 1720 |
+
|
| 1721 |
+
const urlLink = document.createElement('a');
|
| 1722 |
+
urlLink.href = link.url;
|
| 1723 |
+
urlLink.target = '_blank';
|
| 1724 |
+
urlLink.textContent = link.url;
|
| 1725 |
+
urlLink.style.color = 'var(--primary)';
|
| 1726 |
+
urlLink.style.fontWeight = '500';
|
| 1727 |
+
urlLink.style.textDecoration = 'none';
|
| 1728 |
+
leftDiv.appendChild(urlLink);
|
| 1729 |
+
|
| 1730 |
+
const statusSpan = document.createElement('span');
|
| 1731 |
+
statusSpan.style.fontWeight = '700';
|
| 1732 |
+
statusSpan.style.color = statusColor;
|
| 1733 |
+
statusSpan.style.textTransform = 'uppercase';
|
| 1734 |
+
statusSpan.style.fontSize = '12px';
|
| 1735 |
+
statusSpan.textContent = link.status;
|
| 1736 |
|
| 1737 |
item.appendChild(leftDiv);
|
| 1738 |
+
item.appendChild(statusSpan);
|
| 1739 |
list.appendChild(item);
|
| 1740 |
});
|
| 1741 |
linkSection.appendChild(list);
|
| 1742 |
}
|
| 1743 |
|
| 1744 |
+
resultsDiv.appendChild(linkSection);
|
| 1745 |
}
|
| 1746 |
|
| 1747 |
function displaySpellingOnlyResults(data) {
|
| 1748 |
const resultsDiv = document.getElementById('results');
|
| 1749 |
const statusDiv = document.getElementById('status');
|
| 1750 |
const summaryDiv = document.getElementById('summary');
|
| 1751 |
+
const elementsList = document.getElementById('elementsList'); // unused but cleared
|
| 1752 |
|
| 1753 |
+
// 1. Status Section
|
| 1754 |
const hasErrors = data.spell_check && data.spell_check.total_errors > 0;
|
|
|
|
| 1755 |
statusDiv.className = `status ${hasErrors ? 'fail' : 'pass'}`;
|
| 1756 |
|
| 1757 |
+
const iconSvg = hasErrors
|
| 1758 |
+
? '<svg class="status-icon-svg" width="32" height="32" fill="none" stroke="#DC2626" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>'
|
| 1759 |
+
: '<svg class="status-icon-svg" width="32" height="32" fill="none" stroke="#059669" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>';
|
| 1760 |
+
|
| 1761 |
+
statusDiv.innerHTML = `
|
| 1762 |
+
<div class="status-icon">
|
| 1763 |
+
${iconSvg}
|
| 1764 |
+
</div>
|
| 1765 |
+
<div class="status-content">
|
| 1766 |
+
<h3>${hasErrors ? 'Quality Issues Found' : 'Text Quality Passed'}</h3>
|
| 1767 |
+
<p>${data.summary || (hasErrors ? 'Issues detected in document text.' : 'No spelling or grammar issues found.')}</p>
|
| 1768 |
+
</div>
|
| 1769 |
+
`;
|
| 1770 |
|
| 1771 |
+
// Hide summary, clear elements
|
| 1772 |
+
summaryDiv.style.display = 'none';
|
| 1773 |
elementsList.innerHTML = '';
|
| 1774 |
+
elementsList.className = 'elements-grid'; // Ensure grid class just in case
|
| 1775 |
|
| 1776 |
+
// Display spell check results if available
|
| 1777 |
if (data.spell_check) {
|
| 1778 |
displaySpellCheck(data.spell_check);
|
| 1779 |
}
|
|
|
|
| 1783 |
}
|
| 1784 |
|
| 1785 |
// Debug: Extract images
|
| 1786 |
+
const debugBtn = document.getElementById('debugBtn');
|
| 1787 |
+
if (debugBtn) {
|
| 1788 |
+
debugBtn.addEventListener('click', async function () {
|
| 1789 |
+
const templateKey = document.getElementById('templateSelect').value;
|
| 1790 |
+
const fileInput = document.getElementById('fileInput');
|
| 1791 |
+
const file = fileInput.files[0];
|
| 1792 |
+
const debugInfo = document.getElementById('debugInfo');
|
| 1793 |
+
|
| 1794 |
+
if (!templateKey) {
|
| 1795 |
+
alert('Please select a template first');
|
| 1796 |
+
return;
|
| 1797 |
+
}
|
| 1798 |
|
| 1799 |
+
if (!file) {
|
| 1800 |
+
alert('Please select a file first');
|
| 1801 |
+
return;
|
| 1802 |
+
}
|
| 1803 |
|
| 1804 |
+
debugInfo.style.display = 'block';
|
| 1805 |
+
debugInfo.innerHTML = '<p>Extracting images...</p>';
|
|
|
|
|
|
|
| 1806 |
|
| 1807 |
+
try {
|
| 1808 |
+
const formData = new FormData();
|
| 1809 |
+
formData.append('file', file);
|
| 1810 |
|
| 1811 |
+
const response = await fetch(`/debug/extract-images?template_key=${encodeURIComponent(templateKey)}`, {
|
| 1812 |
+
method: 'POST',
|
| 1813 |
+
body: formData
|
| 1814 |
+
});
|
| 1815 |
|
| 1816 |
+
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
| 1817 |
|
| 1818 |
+
if (!response.ok) {
|
| 1819 |
+
throw new Error(data.detail || 'Extraction failed');
|
| 1820 |
+
}
|
| 1821 |
|
| 1822 |
+
// Format debug output
|
| 1823 |
+
let output = '=== IMAGE EXTRACTION DEBUG ===\n\n';
|
| 1824 |
+
output += `File: ${data.file_name}\n`;
|
| 1825 |
+
output += `Size: ${(data.file_size_bytes / 1024).toFixed(2)} KB\n`;
|
| 1826 |
+
output += `Text extracted: ${data.text_extracted ? 'Yes' : 'No'} (${data.text_length} chars)\n\n`;
|
| 1827 |
+
output += `Images Found: ${data.images_found}\n`;
|
| 1828 |
+
output += `Template Requires Visual Elements: ${data.template_requires_visual_elements ? 'Yes' : 'No'}\n\n`;
|
| 1829 |
+
|
| 1830 |
+
if (data.template_visual_elements.length > 0) {
|
| 1831 |
+
output += 'Template Visual Elements:\n';
|
| 1832 |
+
data.template_visual_elements.forEach(elem => {
|
| 1833 |
+
output += ` - ${elem.label} (${elem.type}) - Required: ${elem.required}\n`;
|
| 1834 |
+
});
|
| 1835 |
+
output += '\n';
|
| 1836 |
+
}
|
| 1837 |
|
| 1838 |
+
if (data.images.length > 0) {
|
| 1839 |
+
output += 'Extracted Images:\n';
|
| 1840 |
+
data.images.forEach((img, idx) => {
|
| 1841 |
+
output += `\n${idx + 1}. ${img.id}\n`;
|
| 1842 |
+
output += ` Path: ${img.file_path}\n`;
|
| 1843 |
+
output += ` Exists: ${img.file_exists ? 'Yes' : 'No'}\n`;
|
| 1844 |
+
output += ` Size: ${(img.file_size_bytes / 1024).toFixed(2)} KB\n`;
|
| 1845 |
+
output += ` Dimensions: ${img.dimensions}\n`;
|
| 1846 |
+
output += ` Mode: ${img.image_mode}\n`;
|
| 1847 |
+
output += ` Role: ${img.role_hint}\n`;
|
| 1848 |
+
output += ` Type: ${img.element_type}\n`;
|
| 1849 |
+
});
|
| 1850 |
+
} else {
|
| 1851 |
+
output += '\n⚠️ No images were extracted from the document.\n';
|
| 1852 |
+
output += 'This could mean:\n';
|
| 1853 |
+
output += ' - The document has no embedded images\n';
|
| 1854 |
+
output += ' - Images are in a format not supported\n';
|
| 1855 |
+
output += ' - Images are embedded as external links\n';
|
| 1856 |
+
}
|
| 1857 |
|
| 1858 |
+
debugInfo.innerHTML = '<pre>' + output + '</pre>';
|
| 1859 |
+
} catch (error) {
|
| 1860 |
+
debugInfo.innerHTML = '<pre style="color: red;">Error: ' + error.message + '</pre>';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1861 |
}
|
| 1862 |
+
});
|
| 1863 |
+
}
|
| 1864 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1865 |
|
| 1866 |
// Function to display comparison results
|
| 1867 |
function displayComparisonResults(data) {
|
|
|
|
| 2163 |
}
|
| 2164 |
|
| 2165 |
// Create project modal handlers
|
| 2166 |
+
// Create project modal handlers
|
| 2167 |
+
const createProjectBtn = document.getElementById('createProjectBtn');
|
| 2168 |
+
if (createProjectBtn) {
|
| 2169 |
+
createProjectBtn.addEventListener('click', function () {
|
| 2170 |
+
document.getElementById('createProjectModal').style.display = 'flex';
|
| 2171 |
+
document.getElementById('projectName').value = '';
|
| 2172 |
+
document.getElementById('projectDescription').value = '';
|
| 2173 |
+
});
|
| 2174 |
+
}
|
| 2175 |
|
| 2176 |
+
const cancelProjectBtn = document.getElementById('cancelProjectBtn');
|
| 2177 |
+
if (cancelProjectBtn) {
|
| 2178 |
+
cancelProjectBtn.addEventListener('click', function () {
|
| 2179 |
+
document.getElementById('createProjectModal').style.display = 'none';
|
| 2180 |
+
});
|
| 2181 |
+
}
|
| 2182 |
|
| 2183 |
+
const saveProjectBtn = document.getElementById('saveProjectBtn');
|
| 2184 |
+
if (saveProjectBtn) {
|
| 2185 |
+
saveProjectBtn.addEventListener('click', async function () {
|
| 2186 |
+
const name = document.getElementById('projectName').value.trim();
|
| 2187 |
+
const description = document.getElementById('projectDescription').value.trim();
|
| 2188 |
|
| 2189 |
+
if (!name) {
|
| 2190 |
+
showError('Project name is required');
|
| 2191 |
+
return;
|
| 2192 |
+
}
|
| 2193 |
|
| 2194 |
+
try {
|
| 2195 |
+
const response = await fetch('/projects', {
|
| 2196 |
+
method: 'POST',
|
| 2197 |
+
headers: { 'Content-Type': 'application/json' },
|
| 2198 |
+
body: JSON.stringify({ name, description })
|
| 2199 |
+
});
|
| 2200 |
|
| 2201 |
+
if (!response.ok) {
|
| 2202 |
+
const error = await response.json();
|
| 2203 |
+
throw new Error(error.detail || 'Failed to create project');
|
| 2204 |
+
}
|
| 2205 |
|
| 2206 |
+
const project = await response.json();
|
| 2207 |
+
document.getElementById('createProjectModal').style.display = 'none';
|
| 2208 |
+
await loadProjects();
|
| 2209 |
+
document.getElementById('currentProject').value = project.id;
|
| 2210 |
+
showError(''); // clear error
|
| 2211 |
+
} catch (error) {
|
| 2212 |
+
showError(error.message);
|
| 2213 |
+
}
|
| 2214 |
+
});
|
| 2215 |
+
}
|
| 2216 |
|
| 2217 |
// View all projects
|
| 2218 |
+
const viewProjectsBtn = document.getElementById('viewProjectsBtn');
|
| 2219 |
+
if (viewProjectsBtn) {
|
| 2220 |
+
viewProjectsBtn.addEventListener('click', function () {
|
| 2221 |
+
// For now, just alert - can be enhanced later
|
| 2222 |
+
alert('Projects view coming soon! For now, use the dropdown to select projects.');
|
| 2223 |
+
});
|
| 2224 |
+
}
|
| 2225 |
|
| 2226 |
|
| 2227 |
// ==================== SHAREPOINT INTEGRATION ====================
|
|
|
|
| 2233 |
breadcrumbs: [],
|
| 2234 |
selectedFiles: new Set()
|
| 2235 |
};
|
|
|
|
| 2236 |
// Initialize UI based on auth state
|
| 2237 |
function updateSharePointUI() {
|
| 2238 |
+
const connected = localStorage.getItem('sp_connected') === 'true';
|
| 2239 |
+
// FIX: Use correct ID from HTML (sharepointAuthSection, not sharepointConnectSection)
|
| 2240 |
+
const connectDiv = document.getElementById('sharepointAuthSection');
|
| 2241 |
+
const actionsDiv = document.getElementById('sharepointActionsSection');
|
| 2242 |
+
|
| 2243 |
+
if (connectDiv) connectDiv.style.display = connected ? 'none' : 'flex';
|
| 2244 |
+
if (actionsDiv) actionsDiv.style.display = connected ? 'block' : 'none';
|
| 2245 |
}
|
| 2246 |
updateSharePointUI();
|
| 2247 |
|
| 2248 |
// Connect Button Handler
|
| 2249 |
+
const connectSharePointBtn = document.getElementById('connectSharePointBtn');
|
| 2250 |
+
console.log('SharePoint Button found:', !!connectSharePointBtn); // Debug
|
| 2251 |
+
if (connectSharePointBtn) {
|
| 2252 |
+
connectSharePointBtn.addEventListener('click', async () => {
|
| 2253 |
+
console.log('Connect Account button clicked!'); // Debug
|
| 2254 |
+
// Open popup immediately to avoid blocker
|
| 2255 |
const width = 600;
|
| 2256 |
const height = 700;
|
| 2257 |
const left = (window.screen.width - width) / 2;
|
| 2258 |
const top = (window.screen.height - height) / 2;
|
| 2259 |
|
| 2260 |
+
// Use unique name to ensure new window every time
|
| 2261 |
+
const popup = window.open(
|
| 2262 |
+
'about:blank',
|
| 2263 |
+
`SharePointLogin_${Date.now()}`,
|
| 2264 |
`width=${width},height=${height},top=${top},left=${left}`
|
| 2265 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2266 |
|
| 2267 |
+
console.log('Popup result:', popup); // Debug
|
| 2268 |
+
|
| 2269 |
+
if (!popup) {
|
| 2270 |
+
showError('Popup blocked! Please allow popups for this site.');
|
| 2271 |
+
return;
|
| 2272 |
+
}
|
| 2273 |
+
|
| 2274 |
+
// Safer way to set content
|
| 2275 |
+
try {
|
| 2276 |
+
popup.document.body.innerHTML = '<h3>Connecting to Microsoft...</h3><p>Please wait while we redirect you.</p>';
|
| 2277 |
+
} catch (e) {
|
| 2278 |
+
// Ignore modification errors if cross-origin or closed
|
| 2279 |
+
console.warn('Could not set popup content', e);
|
| 2280 |
+
}
|
| 2281 |
+
|
| 2282 |
+
try {
|
| 2283 |
+
const response = await fetch('/auth/sharepoint/login');
|
| 2284 |
+
const data = await response.json();
|
| 2285 |
+
console.log('Auth response:', data); // Debug
|
| 2286 |
+
|
| 2287 |
+
if (response.ok && data.auth_url) {
|
| 2288 |
+
if (!popup.closed) {
|
| 2289 |
+
popup.location.href = data.auth_url;
|
| 2290 |
+
}
|
| 2291 |
+
} else {
|
| 2292 |
+
if (!popup.closed) popup.close();
|
| 2293 |
+
showError('Failed to get login URL');
|
| 2294 |
+
}
|
| 2295 |
+
} catch (error) {
|
| 2296 |
+
console.error('Auth error:', error); // Debug
|
| 2297 |
+
if (!popup.closed) popup.close();
|
| 2298 |
+
showError('Failed to start login: ' + error.message);
|
| 2299 |
+
}
|
| 2300 |
+
});
|
| 2301 |
+
} else {
|
| 2302 |
+
console.error('SharePoint button NOT FOUND in DOM!');
|
| 2303 |
+
}
|
| 2304 |
|
| 2305 |
// Logout
|
| 2306 |
+
const logoutSharePointBtn = document.getElementById('logoutSharePointBtn');
|
| 2307 |
+
if (logoutSharePointBtn) {
|
| 2308 |
+
logoutSharePointBtn.addEventListener('click', () => {
|
| 2309 |
+
localStorage.removeItem('sp_connected');
|
| 2310 |
+
updateSharePointUI();
|
| 2311 |
+
});
|
| 2312 |
+
}
|
| 2313 |
|
| 2314 |
+
// Browse
|
| 2315 |
+
const browseSharePointBtn = document.getElementById('browseSharePointBtn');
|
| 2316 |
+
if (browseSharePointBtn) {
|
| 2317 |
+
browseSharePointBtn.addEventListener('click', async () => {
|
| 2318 |
+
document.getElementById('sharepointModal').style.display = 'flex';
|
| 2319 |
+
await loadSharePointItems();
|
| 2320 |
+
});
|
| 2321 |
+
}
|
| 2322 |
|
| 2323 |
+
// Close Modal
|
| 2324 |
+
const closeSharePointModal = document.getElementById('closeSharePointModal');
|
| 2325 |
+
if (closeSharePointModal) {
|
| 2326 |
+
closeSharePointModal.addEventListener('click', () => {
|
| 2327 |
+
document.getElementById('sharepointModal').style.display = 'none';
|
| 2328 |
+
});
|
| 2329 |
+
}
|
| 2330 |
|
| 2331 |
+
// Back Button
|
| 2332 |
+
const spBackBtn = document.getElementById('spBackBtn');
|
| 2333 |
+
if (spBackBtn) {
|
| 2334 |
+
spBackBtn.addEventListener('click', async () => {
|
| 2335 |
+
if (currentPath.length > 0) {
|
| 2336 |
+
currentPath.pop(); // Remove current folder
|
| 2337 |
+
const parentFolder = currentPath.length > 0 ? currentPath[currentPath.length - 1] : null;
|
| 2338 |
+
await loadSharePointItems(parentFolder ? parentFolder.id : null);
|
| 2339 |
+
}
|
| 2340 |
+
});
|
| 2341 |
+
}
|
| 2342 |
+
|
| 2343 |
+
// Import Button
|
| 2344 |
+
const spImportBtn = document.getElementById('spImportBtn');
|
| 2345 |
+
if (spImportBtn) {
|
| 2346 |
+
spImportBtn.addEventListener('click', async () => {
|
| 2347 |
+
const checkboxes = document.querySelectorAll('.sp-item-checkbox:checked');
|
| 2348 |
+
if (checkboxes.length === 0) {
|
| 2349 |
+
alert('Please select at least one file to import.');
|
| 2350 |
+
return;
|
| 2351 |
+
}
|
| 2352 |
+
|
| 2353 |
+
const fileIds = Array.from(checkboxes).map(cb => cb.value);
|
| 2354 |
+
const btn = document.getElementById('spImportBtn');
|
| 2355 |
+
btn.disabled = true;
|
| 2356 |
+
btn.textContent = 'Importing...';
|
| 2357 |
+
|
| 2358 |
+
try {
|
| 2359 |
+
// This endpoint would handle downloading from Graph API and processing
|
| 2360 |
+
// For now, we simulate success or need to implement the backend logic
|
| 2361 |
+
const response = await fetch('/sharepoint/download-and-validate', {
|
| 2362 |
+
method: 'POST',
|
| 2363 |
+
headers: { 'Content-Type': 'application/json' },
|
| 2364 |
+
body: JSON.stringify({ file_ids: fileIds })
|
| 2365 |
+
});
|
| 2366 |
+
|
| 2367 |
+
if (response.ok) {
|
| 2368 |
+
const result = await response.json();
|
| 2369 |
+
document.getElementById('sharepointModal').style.display = 'none';
|
| 2370 |
+
// Refresh validation results or show success
|
| 2371 |
+
displayResults(result);
|
| 2372 |
+
} else {
|
| 2373 |
+
throw new Error('Import failed');
|
| 2374 |
+
}
|
| 2375 |
+
} catch (e) {
|
| 2376 |
+
alert('Error importing files: ' + e.message);
|
| 2377 |
+
} finally {
|
| 2378 |
+
btn.disabled = false;
|
| 2379 |
+
btn.textContent = 'Import Selected';
|
| 2380 |
+
}
|
| 2381 |
+
});
|
| 2382 |
}
|
| 2383 |
|
| 2384 |
// Load Items (Folder level)
|
|
|
|
| 2521 |
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
| 2522 |
}
|
| 2523 |
|
| 2524 |
+
// App Logout
|
| 2525 |
+
const logoutBtn = document.getElementById('logoutBtn');
|
| 2526 |
+
if (logoutBtn) {
|
| 2527 |
+
logoutBtn.addEventListener('click', async () => {
|
| 2528 |
+
try {
|
| 2529 |
+
// Call backend logout if it exists, or just redirect
|
| 2530 |
+
await fetch('/logout', { method: 'POST' });
|
| 2531 |
+
window.location.href = '/login';
|
| 2532 |
+
} catch (e) {
|
| 2533 |
+
// Fallback
|
| 2534 |
+
window.location.href = '/login';
|
| 2535 |
+
}
|
| 2536 |
+
});
|
| 2537 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2538 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2539 |
|
| 2540 |
// Load templates when page loads
|
| 2541 |
loadProjects();
|
app/validator.py
CHANGED
|
@@ -681,9 +681,38 @@ class Validator:
|
|
| 681 |
"""
|
| 682 |
logger.info(f"Starting comparison: {file1_name} vs {file2_name}")
|
| 683 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 684 |
# Extract text from both documents
|
| 685 |
-
text1 = extract_document_text(file1_content, file1_extension)
|
| 686 |
-
text2 = extract_document_text(file2_content, file2_extension)
|
| 687 |
|
| 688 |
logger.info(f"Extracted text - File 1: {len(text1)} chars, File 2: {len(text2)} chars")
|
| 689 |
|
|
@@ -691,18 +720,25 @@ class Validator:
|
|
| 691 |
raise ValueError("Both documents appear to be empty or contain no extractable text")
|
| 692 |
|
| 693 |
# Build LLM prompt for comparison
|
| 694 |
-
comparison_prompt = f"""You are comparing two versions of a document to identify
|
| 695 |
|
| 696 |
-
DOCUMENT 1 ({file1_name}):
|
| 697 |
-
{text1[:10000]}
|
| 698 |
|
| 699 |
-
DOCUMENT 2 ({file2_name}):
|
| 700 |
{text2[:10000]}
|
| 701 |
|
| 702 |
Please analyze the differences between these two documents and provide:
|
| 703 |
|
| 704 |
1. A natural language summary of the main changes (2-3 sentences)
|
| 705 |
-
2. A detailed list of
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 706 |
|
| 707 |
Format your response as a JSON object with this structure:
|
| 708 |
{{
|
|
@@ -710,19 +746,17 @@ Format your response as a JSON object with this structure:
|
|
| 710 |
"changes": [
|
| 711 |
{{
|
| 712 |
"type": "addition|deletion|modification",
|
| 713 |
-
"section": "
|
| 714 |
"description": "Description of the change"
|
| 715 |
}}
|
| 716 |
]
|
| 717 |
}}
|
| 718 |
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
If the documents are identical, return an empty changes array.
|
| 726 |
"""
|
| 727 |
|
| 728 |
try:
|
|
@@ -744,9 +778,42 @@ If the documents are identical, return an empty changes array.
|
|
| 744 |
response_text = message.content[0].text if message.content else ""
|
| 745 |
logger.info(f"Received comparison response ({len(response_text)} chars)")
|
| 746 |
|
| 747 |
-
# Parse JSON response
|
| 748 |
import json
|
| 749 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 750 |
|
| 751 |
logger.info(f"Comparison complete: {len(comparison_data.get('changes', []))} changes detected")
|
| 752 |
|
|
|
|
| 681 |
"""
|
| 682 |
logger.info(f"Starting comparison: {file1_name} vs {file2_name}")
|
| 683 |
|
| 684 |
+
# Normalize text to handle PDF ligatures and encoding differences
|
| 685 |
+
def normalize_text(text: str) -> str:
|
| 686 |
+
"""Normalize Unicode ligatures and common PDF text artifacts."""
|
| 687 |
+
import unicodedata
|
| 688 |
+
# Common ligature replacements
|
| 689 |
+
ligatures = {
|
| 690 |
+
'fi': 'fi',
|
| 691 |
+
'fl': 'fl',
|
| 692 |
+
'ff': 'ff',
|
| 693 |
+
'ffi': 'ffi',
|
| 694 |
+
'ffl': 'ffl',
|
| 695 |
+
'Ꜳ': 'AA',
|
| 696 |
+
'ꜳ': 'aa',
|
| 697 |
+
'Æ': 'AE',
|
| 698 |
+
'æ': 'ae',
|
| 699 |
+
'Œ': 'OE',
|
| 700 |
+
'œ': 'oe',
|
| 701 |
+
'\u00AD': '', # Soft hyphen
|
| 702 |
+
'\u200B': '', # Zero-width space
|
| 703 |
+
'\u200C': '', # Zero-width non-joiner
|
| 704 |
+
'\u200D': '', # Zero-width joiner
|
| 705 |
+
'\uFEFF': '', # BOM
|
| 706 |
+
}
|
| 707 |
+
for lig, replacement in ligatures.items():
|
| 708 |
+
text = text.replace(lig, replacement)
|
| 709 |
+
# Normalize Unicode to NFC form
|
| 710 |
+
text = unicodedata.normalize('NFC', text)
|
| 711 |
+
return text
|
| 712 |
+
|
| 713 |
# Extract text from both documents
|
| 714 |
+
text1 = normalize_text(extract_document_text(file1_content, file1_extension))
|
| 715 |
+
text2 = normalize_text(extract_document_text(file2_content, file2_extension))
|
| 716 |
|
| 717 |
logger.info(f"Extracted text - File 1: {len(text1)} chars, File 2: {len(text2)} chars")
|
| 718 |
|
|
|
|
| 720 |
raise ValueError("Both documents appear to be empty or contain no extractable text")
|
| 721 |
|
| 722 |
# Build LLM prompt for comparison
|
| 723 |
+
comparison_prompt = f"""You are comparing two versions of a document to identify MEANINGFUL content changes.
|
| 724 |
|
| 725 |
+
DOCUMENT 1 (Original - {file1_name}):
|
| 726 |
+
{text1[:10000]}
|
| 727 |
|
| 728 |
+
DOCUMENT 2 (Modified - {file2_name}):
|
| 729 |
{text2[:10000]}
|
| 730 |
|
| 731 |
Please analyze the differences between these two documents and provide:
|
| 732 |
|
| 733 |
1. A natural language summary of the main changes (2-3 sentences)
|
| 734 |
+
2. A detailed list of UNIQUE, MEANINGFUL changes only
|
| 735 |
+
|
| 736 |
+
IMPORTANT RULES:
|
| 737 |
+
- Do NOT report duplicate changes - each change should appear only once
|
| 738 |
+
- IGNORE font/encoding differences (like ligatures, special characters that look the same)
|
| 739 |
+
- IGNORE minor whitespace or formatting differences
|
| 740 |
+
- Focus on actual CONTENT changes (text additions, deletions, modifications)
|
| 741 |
+
- Group similar changes together instead of listing each instance separately
|
| 742 |
|
| 743 |
Format your response as a JSON object with this structure:
|
| 744 |
{{
|
|
|
|
| 746 |
"changes": [
|
| 747 |
{{
|
| 748 |
"type": "addition|deletion|modification",
|
| 749 |
+
"section": "Section name where change occurred",
|
| 750 |
"description": "Description of the change"
|
| 751 |
}}
|
| 752 |
]
|
| 753 |
}}
|
| 754 |
|
| 755 |
+
If the documents are essentially identical (only minor formatting/encoding differences), return:
|
| 756 |
+
{{
|
| 757 |
+
"summary": "Documents are essentially identical with no meaningful content changes.",
|
| 758 |
+
"changes": []
|
| 759 |
+
}}
|
|
|
|
|
|
|
| 760 |
"""
|
| 761 |
|
| 762 |
try:
|
|
|
|
| 778 |
response_text = message.content[0].text if message.content else ""
|
| 779 |
logger.info(f"Received comparison response ({len(response_text)} chars)")
|
| 780 |
|
| 781 |
+
# Parse JSON response - extract from markdown code blocks if present
|
| 782 |
import json
|
| 783 |
+
import re
|
| 784 |
+
|
| 785 |
+
# Try to extract JSON from markdown code blocks
|
| 786 |
+
json_text = response_text
|
| 787 |
+
|
| 788 |
+
# Check for ```json ... ``` or ``` ... ``` patterns
|
| 789 |
+
code_block_match = re.search(r'```(?:json)?\s*([\s\S]*?)```', response_text)
|
| 790 |
+
if code_block_match:
|
| 791 |
+
json_text = code_block_match.group(1).strip()
|
| 792 |
+
logger.info("Extracted JSON from markdown code block")
|
| 793 |
+
else:
|
| 794 |
+
# Try to find raw JSON object
|
| 795 |
+
json_match = re.search(r'\{[\s\S]*\}', response_text)
|
| 796 |
+
if json_match:
|
| 797 |
+
json_text = json_match.group(0)
|
| 798 |
+
logger.info("Extracted raw JSON object from response")
|
| 799 |
+
|
| 800 |
+
try:
|
| 801 |
+
comparison_data = json.loads(json_text)
|
| 802 |
+
except json.JSONDecodeError as json_err:
|
| 803 |
+
logger.error(f"JSON parsing failed. Raw response: {response_text[:500]}...")
|
| 804 |
+
raise ValueError(f"Failed to parse comparison response as JSON: {str(json_err)}")
|
| 805 |
+
|
| 806 |
+
# Deduplicate changes based on description
|
| 807 |
+
if 'changes' in comparison_data:
|
| 808 |
+
seen_descriptions = set()
|
| 809 |
+
unique_changes = []
|
| 810 |
+
for change in comparison_data['changes']:
|
| 811 |
+
desc = change.get('description', '').lower().strip()
|
| 812 |
+
if desc and desc not in seen_descriptions:
|
| 813 |
+
seen_descriptions.add(desc)
|
| 814 |
+
unique_changes.append(change)
|
| 815 |
+
comparison_data['changes'] = unique_changes
|
| 816 |
+
logger.info(f"After deduplication: {len(unique_changes)} unique changes")
|
| 817 |
|
| 818 |
logger.info(f"Comparison complete: {len(comparison_data.get('changes', []))} changes detected")
|
| 819 |
|