File size: 54,382 Bytes
ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 9eea112 ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 155fe98 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 155fe98 ebcb290 155fe98 ebcb290 155fe98 ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 05f720a ebcb290 155fe98 ebcb290 05f720a 155fe98 9eea112 155fe98 9eea112 05f720a 155fe98 9eea112 155fe98 9eea112 155fe98 9eea112 155fe98 9eea112 155fe98 05f720a 155fe98 05f720a 155fe98 05f720a 155fe98 05f720a 155fe98 05f720a 155fe98 ebcb290 05f720a 155fe98 ebcb290 155fe98 05f720a 155fe98 ebcb290 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 | <!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DeepfakeDet-ViT — XAI & Digital Forensics Suite</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- FontAwesome Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
<!-- html2canvas -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
colors: {
brand: {
50: '#f0fdfa',
500: '#14b8a6',
600: '#0d9488',
900: '#134e4a',
}
}
}
}
}
</script>
<style>
body { font-family: 'Inter', sans-serif; }
.glass-panel {
background: rgba(15, 23, 42, 0.85);
backdrop-filter: blur(12px);
border: 1px solid rgba(51, 65, 85, 0.5);
}
.custom-scrollbar::-webkit-scrollbar { width: 6px; height: 6px; }
.custom-scrollbar::-webkit-scrollbar-track { background: #0f172a; }
.custom-scrollbar::-webkit-scrollbar-thumb { background: #334155; border-radius: 3px; }
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton-shimmer {
background: linear-gradient(90deg, rgba(30,41,59,0.5) 25%, rgba(51,65,85,0.8) 50%, rgba(30,41,59,0.5) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
</style>
</head>
<body class="bg-slate-950 text-slate-100 min-h-screen flex flex-col selection:bg-teal-500 selection:text-white">
<!-- Header Banner -->
<header class="border-b border-slate-800 bg-slate-900/80 sticky top-0 z-40 backdrop-blur-md">
<div class="max-w-7xl mx-auto px-4 py-3 flex items-center justify-between">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-teal-500 to-emerald-400 flex items-center justify-center shadow-lg shadow-teal-500/20">
<i class="fa-solid fa-microscope text-slate-950 text-lg"></i>
</div>
<div>
<h1 class="font-bold text-lg text-slate-100 tracking-tight flex items-center gap-2">
DeepfakeDet-ViT <span class="text-xs px-2 py-0.5 rounded-full bg-teal-500/10 text-teal-400 font-mono border border-teal-500/20">XAI & Forensics Suite</span>
</h1>
<p class="text-xs text-slate-400">ViT-Small Deepfake Classifier (384×384) — Attribution & Digital Artifact Analysis</p>
</div>
</div>
<div class="flex items-center space-x-3">
<a href="https://huggingface.co/buildborderless/CommunityForensics-DeepfakeDet-ViT" target="_blank" class="text-xs px-3 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-300 transition flex items-center gap-1.5 border border-slate-700">
<i class="fa-brands fa-huggingface"></i> HF Model Hub
</a>
</div>
</div>
</header>
<!-- Navigation Tabs -->
<div class="bg-slate-900/50 border-b border-slate-800/80 sticky top-[61px] z-30 backdrop-blur-md">
<div class="max-w-7xl mx-auto px-4 flex space-x-2 overflow-x-auto custom-scrollbar">
<button onclick="switchTab('quick')" id="tab-quick" class="tab-btn py-3 px-4 text-sm font-medium border-b-2 border-teal-400 text-teal-400 flex items-center gap-2 transition">
<i class="fa-solid fa-bolt text-xs"></i> ⚡ Run Analysis
</button>
<button onclick="switchTab('explorer')" id="tab-explorer" class="tab-btn py-3 px-4 text-sm font-medium border-b-2 border-transparent text-slate-400 hover:text-slate-200 flex items-center gap-2 transition">
<i class="fa-solid fa-sliders text-xs"></i> 🔍 Method Explorer
</button>
<button onclick="switchTab('heads')" id="tab-heads" class="tab-btn py-3 px-4 text-sm font-medium border-b-2 border-transparent text-slate-400 hover:text-slate-200 flex items-center gap-2 transition">
<i class="fa-solid fa-brain text-xs"></i> 🧠 Per-Head Inspector
</button>
<button onclick="switchTab('forensics')" id="tab-forensics" class="tab-btn py-3 px-4 text-sm font-medium border-b-2 border-transparent text-slate-400 hover:text-slate-200 flex items-center gap-2 transition">
<i class="fa-solid fa-user-secret text-xs"></i> 🕵️ Classic Forensics
</button>
<button onclick="switchTab('guide')" id="tab-guide" class="tab-btn py-3 px-4 text-sm font-medium border-b-2 border-transparent text-slate-400 hover:text-slate-200 flex items-center gap-2 transition">
<i class="fa-solid fa-book-open text-xs"></i> 📖 Guide & Reference
</button>
</div>
</div>
<!-- Main Container -->
<main class="max-w-7xl mx-auto px-4 py-6 flex-1 w-full">
<!-- Global Image Upload Section -->
<div class="glass-panel rounded-2xl p-5 mb-6 shadow-xl">
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6 items-center">
<!-- Left: Upload Box -->
<div class="lg:col-span-5">
<div id="drop-zone" onclick="document.getElementById('file-input').click()" class="border-2 border-dashed border-slate-700 hover:border-teal-500/60 bg-slate-900/60 hover:bg-slate-900 transition rounded-xl p-6 text-center cursor-pointer relative group">
<input type="file" id="file-input" accept="image/*" class="hidden" onchange="handleFileSelect(event)" />
<div id="upload-prompt" class="py-2">
<i class="fa-solid fa-cloud-arrow-up text-3xl text-teal-400/80 group-hover:scale-110 transition mb-2"></i>
<p class="text-sm font-medium text-slate-200">Drag & Drop Image, Paste, or <span class="text-teal-400 underline">Browse</span></p>
<p class="text-xs text-slate-500 mt-1">Supports JPG, PNG, WEBP (Auto-cropped to 384×384)</p>
</div>
<div id="preview-container" class="hidden relative inline-block">
<img id="image-preview" src="" alt="Input Preview" class="max-h-48 rounded-lg shadow-lg border border-slate-700 object-contain mx-auto" />
<button onclick="event.stopPropagation(); clearImage()" class="absolute -top-2 -right-2 bg-rose-500 text-white w-6 h-6 rounded-full text-xs flex items-center justify-center hover:bg-rose-600 shadow z-10">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
</div>
</div>
<!-- Right: Control Panel & Status -->
<div class="lg:col-span-7 flex flex-col justify-between h-full space-y-4">
<div class="flex items-center justify-between border-b border-slate-800 pb-3">
<div>
<span class="text-xs font-mono text-slate-400 uppercase tracking-wider">Analysis Status</span>
<div id="prediction-badge" class="mt-1 text-sm font-bold text-slate-500 flex items-center gap-2">
<span class="w-2.5 h-2.5 rounded-full bg-slate-600"></span> Awaiting Input Image
</div>
</div>
<div id="confidence-box" class="hidden text-right">
<span class="text-xs text-slate-400">Confidence Score</span>
<p id="confidence-val" class="font-mono text-lg font-bold text-teal-400">0.00%</p>
</div>
</div>
<!-- Buttons -->
<div class="flex flex-wrap items-center gap-3">
<button id="btn-analyze" onclick="runAnalysis()" disabled class="flex-1 bg-gradient-to-r from-teal-500 to-emerald-500 hover:from-teal-400 hover:to-emerald-400 text-slate-950 font-bold px-6 py-3 rounded-xl shadow-lg shadow-teal-500/20 transition flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fa-solid fa-bolt"></i> Run Full XAI Suite
</button>
<button id="btn-report" onclick="generateReport()" disabled class="bg-slate-800 hover:bg-slate-700 text-slate-200 font-semibold px-4 py-3 rounded-xl border border-slate-700 transition flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fa-solid fa-file-arrow-down"></i> Report PNG
</button>
</div>
</div>
</div>
</div>
<!-- Loading Bar / Spinner -->
<div id="loading-spinner" class="hidden glass-panel rounded-2xl p-6 mb-6 text-center border-t border-teal-500">
<div class="inline-block animate-spin w-8 h-8 border-4 border-teal-500 border-t-transparent rounded-full mb-3"></div>
<p id="loading-text" class="text-sm font-medium text-slate-300">Computing ViT Attribution Maps in parallel...</p>
</div>
<!-- TAB 1: ⚡ Run Analysis -->
<div id="section-quick" class="tab-section">
<div class="glass-panel rounded-2xl p-6 shadow-xl mb-6">
<h2 class="text-base font-bold text-slate-100 mb-4 flex items-center justify-between">
<span><i class="fa-solid fa-layer-group text-teal-400 mr-2"></i> ViT Attribution Overlays</span>
<span class="text-xs font-normal text-slate-400">Click any map to inspect in fullscreen modal</span>
</h2>
<!-- Grid of 6 attribution overlays -->
<div id="results-grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Placeholder cards (initial state) -->
<div class="bg-slate-900/40 border border-slate-800/80 rounded-xl p-4 text-center py-12 text-slate-600">
<i class="fa-regular fa-image text-3xl mb-2"></i>
<p class="text-xs">Attention Rollout</p>
</div>
<div class="bg-slate-900/40 border border-slate-800/80 rounded-xl p-4 text-center py-12 text-slate-600">
<i class="fa-regular fa-image text-3xl mb-2"></i>
<p class="text-xs">Chefer Gradient-Rollout</p>
</div>
<div class="bg-slate-900/40 border border-slate-800/80 rounded-xl p-4 text-center py-12 text-slate-600">
<i class="fa-regular fa-image text-3xl mb-2"></i>
<p class="text-xs">GradCAM</p>
</div>
<div class="bg-slate-900/40 border border-slate-800/80 rounded-xl p-4 text-center py-12 text-slate-600">
<i class="fa-regular fa-image text-3xl mb-2"></i>
<p class="text-xs">Integrated Gradients</p>
</div>
<div class="bg-slate-900/40 border border-slate-800/80 rounded-xl p-4 text-center py-12 text-slate-600">
<i class="fa-regular fa-image text-3xl mb-2"></i>
<p class="text-xs">DeepLIFT</p>
</div>
<div class="bg-slate-900/40 border border-slate-800/80 rounded-xl p-4 text-center py-12 text-slate-600">
<i class="fa-regular fa-image text-3xl mb-2"></i>
<p class="text-xs">Occlusion Sensitivity</p>
</div>
</div>
</div>
<!-- Dataset Feedback Submission Panel -->
<div id="submit-panel" class="hidden glass-panel rounded-2xl p-6 shadow-xl border border-teal-500/20">
<h3 class="text-sm font-bold text-slate-200 mb-2 flex items-center gap-2">
<i class="fa-solid fa-database text-teal-400"></i> Contribute Ground Truth to Research Dataset
</h3>
<p class="text-xs text-slate-400 mb-4">
Help improve future forensic models by verifying prediction correctness. Your feedback and the composite XAI heatmaps will be uploaded to <code class="text-teal-400 bg-slate-900 px-1.5 py-0.5 rounded">buildborderless/deepfake-explainability</code>.
</p>
<div class="flex flex-wrap items-center gap-6">
<div>
<span class="text-xs text-slate-400 block mb-1">Ground Truth Label:</span>
<div class="flex items-center space-x-3">
<label class="inline-flex items-center text-xs text-slate-300 cursor-pointer">
<input type="radio" name="ground_truth" value="real" class="form-radio text-teal-500 bg-slate-900 border-slate-700" />
<span class="ml-1.5">Real Image</span>
</label>
<label class="inline-flex items-center text-xs text-slate-300 cursor-pointer">
<input type="radio" name="ground_truth" value="fake" class="form-radio text-rose-500 bg-slate-900 border-slate-700" />
<span class="ml-1.5">Deepfake</span>
</label>
<label class="inline-flex items-center text-xs text-slate-300 cursor-pointer">
<input type="radio" name="ground_truth" value="unknown" checked class="form-radio text-slate-500 bg-slate-900 border-slate-700" />
<span class="ml-1.5">Unsure / Unknown</span>
</label>
</div>
</div>
<label class="inline-flex items-center text-xs text-slate-300 cursor-pointer">
<input type="checkbox" id="opt-in-image" checked class="form-checkbox text-teal-500 bg-slate-900 border-slate-700 rounded" />
<span class="ml-2">Include raw image in dataset</span>
</label>
<button onclick="submitDatasetRun()" class="bg-teal-600 hover:bg-teal-500 text-slate-950 font-bold text-xs px-4 py-2 rounded-lg transition ml-auto flex items-center gap-1.5">
<i class="fa-solid fa-paper-plane"></i> Submit Entry
</button>
</div>
<p id="submit-status" class="text-xs font-mono text-teal-400 mt-2 hidden"></p>
</div>
</div>
<!-- TAB 2: 🔍 Method Explorer -->
<div id="section-explorer" class="tab-section hidden">
<div class="glass-panel rounded-2xl p-6 shadow-xl mb-6">
<h2 class="text-base font-bold text-slate-100 mb-4 flex items-center gap-2">
<i class="fa-solid fa-sliders text-teal-400"></i> Interactive Method Parameter Explorer
</h2>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
<!-- Parameter Controls -->
<div class="lg:col-span-4 space-y-4 bg-slate-900/60 p-4 rounded-xl border border-slate-800">
<div>
<label class="text-xs font-medium text-slate-300 block mb-1">Select XAI Method</label>
<select id="exp-method" onchange="debouncedRunExplorer()" class="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-xs text-slate-200 focus:outline-none focus:border-teal-500">
<option value="rollout">Attention Rollout</option>
<option value="chefer">Gradient-Weighted Rollout (Chefer)</option>
<option value="gradcam">GradCAM</option>
<option value="integrated">Integrated Gradients</option>
<option value="deeplift">DeepLIFT</option>
<option value="occlusion">Occlusion Sensitivity</option>
</select>
</div>
<div>
<label class="text-xs font-medium text-slate-300 block mb-1">Colormap</label>
<select id="exp-cmap" onchange="debouncedRunExplorer()" class="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-xs text-slate-200">
<option value="jet">Jet (Standard Forensic)</option>
<option value="inferno">Inferno (High Contrast)</option>
<option value="viridis">Viridis (Perceptual)</option>
<option value="hot">Hot (Thermal)</option>
</select>
</div>
<div>
<label class="text-xs font-medium text-slate-300 block mb-1">Overlay Transparency (Alpha: <span id="val-alpha">0.6</span>)</label>
<input type="range" id="exp-alpha" min="0.1" max="1.0" step="0.05" value="0.6" oninput="document.getElementById('val-alpha').innerText=this.value; debouncedRunExplorer()" class="w-full text-teal-500" />
</div>
<!-- Conditional Controls -->
<div id="ctrl-integrated" class="hidden space-y-3 pt-2 border-t border-slate-800">
<label class="text-xs font-medium text-slate-300 block mb-1">IG Steps: <span id="val-steps">20</span></label>
<input type="range" id="exp-steps" min="10" max="60" step="5" value="20" oninput="document.getElementById('val-steps').innerText=this.value; debouncedRunExplorer()" class="w-full" />
</div>
<div id="ctrl-occlusion" class="hidden space-y-3 pt-2 border-t border-slate-800">
<label class="text-xs font-medium text-slate-300 block mb-1">Patch Size: <span id="val-patch">32</span> px</label>
<input type="range" id="exp-patch" min="16" max="64" step="8" value="32" oninput="document.getElementById('val-patch').innerText=this.value; debouncedRunExplorer()" class="w-full" />
</div>
<div id="ctrl-gradcam" class="hidden space-y-3 pt-2 border-t border-slate-800">
<label class="text-xs font-medium text-slate-300 block mb-1">Target Transformer Layer: <span id="val-layer">12</span></label>
<input type="range" id="exp-layer" min="1" max="12" step="1" value="12" oninput="document.getElementById('val-layer').innerText=this.value; debouncedRunExplorer()" class="w-full" />
</div>
</div>
<!-- Display Box -->
<div class="lg:col-span-8 grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="bg-slate-900/60 p-4 rounded-xl border border-slate-800 text-center">
<span class="text-xs text-slate-400 block mb-2 font-mono">Saliency Overlay</span>
<img id="exp-img-overlay" src="" class="max-h-72 rounded-lg mx-auto shadow border border-slate-700 object-contain" />
</div>
<div class="bg-slate-900/60 p-4 rounded-xl border border-slate-800 text-center">
<span class="text-xs text-slate-400 block mb-2 font-mono">Raw Heatmap</span>
<img id="exp-img-raw" src="" class="max-h-72 rounded-lg mx-auto shadow border border-slate-700 object-contain" />
</div>
</div>
</div>
</div>
</div>
<!-- TAB 3: 🧠 Per-Head Inspector -->
<div id="section-heads" class="tab-section hidden">
<div class="glass-panel rounded-2xl p-6 shadow-xl mb-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-base font-bold text-slate-100 flex items-center gap-2">
<i class="fa-solid fa-brain text-teal-400"></i> Attention Head Disaggregation (6 Heads per Layer)
</h2>
<div class="flex items-center space-x-3">
<span class="text-xs text-slate-400">Layer: <strong id="head-layer-val" class="text-teal-400">12</strong> / 12</span>
<input type="range" id="head-layer-input" min="1" max="12" value="12" oninput="document.getElementById('head-layer-val').innerText=this.value; debouncedRunHeads()" class="w-32" />
</div>
</div>
<div id="heads-grid" class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
<!-- Placeholder cards -->
<div class="bg-slate-900/60 border border-slate-800 rounded-xl p-3 text-center py-8 text-slate-600 text-xs">Head 1</div>
<div class="bg-slate-900/60 border border-slate-800 rounded-xl p-3 text-center py-8 text-slate-600 text-xs">Head 2</div>
<div class="bg-slate-900/60 border border-slate-800 rounded-xl p-3 text-center py-8 text-slate-600 text-xs">Head 3</div>
<div class="bg-slate-900/60 border border-slate-800 rounded-xl p-3 text-center py-8 text-slate-600 text-xs">Head 4</div>
<div class="bg-slate-900/60 border border-slate-800 rounded-xl p-3 text-center py-8 text-slate-600 text-xs">Head 5</div>
<div class="bg-slate-900/60 border border-slate-800 rounded-xl p-3 text-center py-8 text-slate-600 text-xs">Head 6</div>
</div>
</div>
</div>
<!-- TAB 4: 🕵️ Classic Forensics -->
<div id="section-forensics" class="tab-section hidden">
<div class="glass-panel rounded-2xl p-6 shadow-xl mb-6">
<h2 class="text-base font-bold text-slate-100 mb-4 flex items-center gap-2">
<i class="fa-solid fa-user-secret text-teal-400"></i> Classic Digital Image Forensics Suite
</h2>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
<!-- Controls -->
<div class="lg:col-span-4 space-y-4 bg-slate-900/60 p-4 rounded-xl border border-slate-800 flex flex-col">
<div>
<label class="text-xs font-medium text-slate-300 block mb-1">Forensic Analysis Tool</label>
<select id="for-tool" onchange="debouncedRunForensics(true)" class="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-xs text-slate-200">
<option value="ela">Error Level Analysis (ELA)</option>
<option value="gradient">Spatial Edge Gradient</option>
<option value="bitplane">Bit Plane Extractor</option>
<option value="minmax">MinMax Local Deviation</option>
<option value="wavelet">Wavelet Noise Estimation</option>
</select>
</div>
<!-- ELA Options -->
<div id="ctrl-ela" class="space-y-3 pt-2 border-t border-slate-800">
<label class="text-xs font-medium text-slate-300 block mb-1">JPEG Quality: <span id="val-quality">75</span>%</label>
<input type="range" id="for-quality" min="30" max="95" value="75" oninput="document.getElementById('val-quality').innerText=this.value; debouncedRunForensics(true)" class="w-full" />
</div>
<!-- Bitplane Options -->
<div id="ctrl-bitplane" class="hidden space-y-3 pt-2 border-t border-slate-800">
<label class="text-xs font-medium text-slate-300 block mb-1">Bit Index (0=LSB, 7=MSB)</label>
<input type="range" id="for-bit" min="0" max="7" value="0" oninput="debouncedRunForensics(true)" class="w-full" />
</div>
<div class="mt-auto pt-4 border-t border-slate-800/50">
<h4 id="for-desc-title" class="font-bold text-sm text-teal-400 mb-1">Error Level Analysis (ELA)</h4>
<p id="for-desc-text" class="text-xs text-slate-400 mb-2 leading-relaxed">Re-saves the image at lower JPEG quality and computes the pixel-level difference. Regions that were edited or composited appear brighter because they were saved at a different compression level than the surrounding content.</p>
<p class="text-xs text-slate-300"><span class="font-semibold text-slate-200">Look for:</span> <span id="for-desc-look">Uniform bright glow across the face region = likely composite. Inconsistent brightness between face and background = editing boundary.</span></p>
</div>
<p id="for-info" class="text-[10px] font-mono text-teal-400/90 pt-2"></p>
</div>
<!-- Display Box -->
<div class="lg:col-span-8 bg-slate-900/60 p-4 rounded-xl border border-slate-800 text-center flex flex-col justify-center items-center min-h-[400px]">
<img id="for-img-output" src="" class="max-h-[450px] rounded-lg shadow border border-slate-700 object-contain hidden" />
<p id="for-placeholder" class="text-xs text-slate-500">Upload an image and run analysis</p>
</div>
</div>
</div>
</div>
<!-- TAB 5: 📖 Guide & Reference -->
<div id="section-guide" class="tab-section hidden">
<div class="glass-panel rounded-2xl p-6 shadow-xl space-y-6">
<div>
<h2 class="text-lg font-bold text-slate-100 mb-2 border-b border-slate-800 pb-2">1. How to Read XAI Heatmaps</h2>
<p class="text-sm text-slate-300 leading-relaxed mb-2">
Explainable AI (XAI) methods attribute the final prediction score back to the original image pixels.
<strong>Warm colors (red/yellow)</strong> indicate high attribution, meaning those pixels strongly influenced the model's prediction.
<strong>Cool colors (blue/purple)</strong> indicate low attribution or minimal influence.
Each method approaches this problem differently, effectively answering a different question about the model's internal behavior.
</p>
</div>
<div>
<h2 class="text-lg font-bold text-slate-100 mb-4 border-b border-slate-800 pb-2">2. XAI Methods</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 text-xs text-slate-300">
<div class="bg-slate-900/60 p-4 rounded-xl border border-slate-800">
<h3 class="font-bold text-teal-400 mb-1">Attention Rollout</h3>
<p class="text-slate-400">Averages attention weights across all heads and propagates them through all 12 layers. It highlights where the model focused its spatial attention globally, regardless of the final class prediction.</p>
</div>
<div class="bg-slate-900/60 p-4 rounded-xl border border-slate-800">
<h3 class="font-bold text-teal-400 mb-1">Chefer Gradient-Rollout</h3>
<p class="text-slate-400">Multiplies attention weights by their backward gradients. This makes the map class-discriminative, showing exactly which regions pushed the probability toward the final verdict (REAL or FAKE).</p>
</div>
<div class="bg-slate-900/60 p-4 rounded-xl border border-slate-800">
<h3 class="font-bold text-teal-400 mb-1">GradCAM</h3>
<p class="text-slate-400">Uses the gradients of the target concept flowing into the final attention layer. It provides the cleanest, most localized answer to "where did the model decide?", often highlighting the face center.</p>
</div>
<div class="bg-slate-900/60 p-4 rounded-xl border border-slate-800">
<h3 class="font-bold text-teal-400 mb-1">Integrated Gradients</h3>
<p class="text-slate-400">Computes the integral of gradients along a straight path from a black baseline image to the input. It meticulously captures pixel-level contributions, highlighting fine details that influenced the score.</p>
</div>
<div class="bg-slate-900/60 p-4 rounded-xl border border-slate-800">
<h3 class="font-bold text-teal-400 mb-1">DeepLIFT</h3>
<p class="text-slate-400">Decomposes the differences in non-linear activations relative to a baseline reference. It is excellent at finding isolated "hotspots" or blending artifacts along face boundaries.</p>
</div>
<div class="bg-slate-900/60 p-4 rounded-xl border border-slate-800">
<h3 class="font-bold text-teal-400 mb-1">Occlusion Sensitivity</h3>
<p class="text-slate-400">The most faithful, model-agnostic method. It slides a black patch over the image and measures the drop in prediction score. Bright regions directly mean: "if you cover this, the model changes its mind."</p>
</div>
</div>
</div>
<div>
<h2 class="text-lg font-bold text-slate-100 mb-4 border-b border-slate-800 pb-2">3. Classic Forensics Tools</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-xs text-slate-300">
<div class="bg-slate-900/60 p-4 rounded-xl border border-slate-800">
<h3 class="font-bold text-teal-400 mb-1">Error Level Analysis (ELA)</h3>
<p class="text-slate-400">Reveals differing compression levels in an image. Spliced or manipulated areas often stand out as noticeably brighter or darker than the surrounding native pixels when re-compressed.</p>
</div>
<div class="bg-slate-900/60 p-4 rounded-xl border border-slate-800">
<h3 class="font-bold text-teal-400 mb-1">Spatial Edge Gradient</h3>
<p class="text-slate-400">Calculates high-frequency transitions to expose unnatural edge profiles. Face swaps and AI generations frequently leave subtle halos, double edges, or unnatural sharpness at blending seams.</p>
</div>
<div class="bg-slate-900/60 p-4 rounded-xl border border-slate-800">
<h3 class="font-bold text-teal-400 mb-1">Bit Plane Extractor</h3>
<p class="text-slate-400">Strips away significant visual data to analyze the least significant bits (LSB). AI synthesis often leaves structured, blocky, or geometrically regular noise patterns in the lowest bit planes.</p>
</div>
<div class="bg-slate-900/60 p-4 rounded-xl border border-slate-800">
<h3 class="font-bold text-teal-400 mb-1">MinMax & Wavelet Estimators</h3>
<p class="text-slate-400">Statistical and frequency domain techniques to find regions where pixel noise variance or intensity drastically deviates from the local neighborhood, typical of pasted imagery.</p>
</div>
</div>
</div>
<div>
<h2 class="text-lg font-bold text-slate-100 mb-2 border-b border-slate-800 pb-2">4. Model Architecture</h2>
<p class="text-sm text-slate-300 leading-relaxed">
Model: <code class="text-teal-400 font-mono bg-slate-900 px-1 rounded">buildborderless/CommunityForensics-DeepfakeDet-ViT</code><br/>
Architecture: Vision Transformer (ViT-Small). Inputs are resized to 384×384. It contains 12 transformer blocks (layers) and 6 attention heads per layer. The final layer passes through a binary sigmoid classifier. The model was trained on 2.7M samples across 4,803 distinct generative models and manipulation techniques.
</p>
</div>
</div>
</div>
</main>
<!-- Fullscreen Modal Viewer -->
<div id="modal-viewer" class="hidden fixed inset-0 z-50 bg-slate-950/90 backdrop-blur-md flex items-center justify-center p-4" onclick="closeModal()">
<div class="relative max-w-4xl w-full bg-slate-900 border border-slate-700 rounded-2xl overflow-hidden p-4 shadow-2xl" onclick="event.stopPropagation()">
<div class="flex items-center justify-between mb-3 border-b border-slate-800 pb-2">
<h3 id="modal-title" class="text-sm font-bold text-slate-200">Attribution Overlay</h3>
<button onclick="closeModal()" class="text-slate-400 hover:text-slate-100 text-lg transition">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
<img id="modal-img" src="" class="max-h-[75vh] mx-auto rounded-lg object-contain bg-slate-900/50" />
</div>
</div>
<!-- JavaScript App Logic -->
<script>
// State Variables
let currentImageB64 = null;
let currentImageHash = null;
let analysisComplete = false;
let currentAnalysisData = null;
let cachedForensics = null;
let collectedResults = {};
const METHOD_META = {
rollout: { name: 'Attention Rollout', insight: 'Bright areas = where the model concentrated attention across all 12 layers. Unexpectedly bright regions on backgrounds or hair often indicate GAN artifacts the model caught.' },
chefer: { name: 'Chefer Gradient-Rollout', insight: 'Class-discriminative — shows specifically what pushed the score toward FAKE (warm) vs REAL (cool). Based on Chefer et al. (CVPR 2021).' },
gradcam: { name: 'GradCAM', insight: 'Red/yellow = strongest activations driving the final verdict. The cleanest spatial answer to "where did the model decide?"' },
integrated: { name: 'Integrated Gradients', insight: 'Pixel-level contributions relative to a black baseline. Warm = pushed toward FAKE; Cool = pushed toward REAL.' },
deeplift: { name: 'DeepLIFT', insight: 'Decomposes non-linear activation differences. Watch for isolated face-boundary hotspots that indicate blending artifacts.' },
occlusion: { name: 'Occlusion Sensitivity', insight: 'Most faithful — shows what actually changes the score when hidden. Bright = "if you cover this, the model changes its mind."' },
};
const FORENSIC_META = {
ela: {
name: 'Error Level Analysis (ELA)',
desc: 'Re-saves the image at lower JPEG quality and computes the pixel-level difference. Regions that were edited or composited appear brighter because they were saved at a different compression level than the surrounding content.',
lookFor: 'Uniform bright glow across the face region = likely composite. Inconsistent brightness between face and background = editing boundary.'
},
gradient: {
name: 'Spatial Edge Gradient',
desc: 'Computes directional edge gradients (Sobel/Scharr filters) to highlight high-frequency transitions. Natural images have smooth, consistent edge profiles; deepfakes often leave unnatural edge artifacts at blending boundaries.',
lookFor: 'Halos or sharp outlines around the face perimeter. Double edges where a face was pasted onto a different background.'
},
bitplane: {
name: 'Bit Plane Extractor',
desc: 'Extracts individual bit planes from the image data, from the Least Significant Bit (LSB, bit 0) to the Most Significant Bit (MSB, bit 7). Lower bit planes reveal hidden noise patterns and subtle data anomalies invisible to the naked eye.',
lookFor: 'Structured geometric patterns in the LSB (bit 0-1) that differ between the face and background regions. AI-generated images often show unusually regular or blocky LSB textures.'
},
minmax: {
name: 'MinMax Local Deviation',
desc: 'Measures the deviation between each pixel and its local neighborhood minimum/maximum values. This highlights regions where pixel intensity is statistically unusual compared to surrounding context.',
lookFor: 'Bright spots indicating patches that are "too smooth" or "too noisy" relative to neighbors — a telltale sign of AI-inserted content or localized editing.'
},
wavelet: {
name: 'Wavelet Noise Estimation',
desc: 'Applies wavelet decomposition to estimate the high-frequency noise variance across different regions of the image. Real camera photos have consistent sensor noise fingerprints; manipulated regions often have a different noise signature.',
lookFor: 'Significant noise variance differences between the face region and the rest of the image. Swapped faces typically carry noise from a different camera/source.'
},
};
// Debounce Utility
function debounce(fn, ms = 300) {
let timer;
return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), ms); };
}
const debouncedRunExplorer = debounce(runExplorerMethod, 300);
const debouncedRunHeads = debounce(runPerHeadInspector, 300);
const debouncedRunForensics = debounce(runForensicAnalysis, 300);
// Initialization & Event Listeners
document.addEventListener('DOMContentLoaded', () => {
const dropZone = document.getElementById('drop-zone');
dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('border-teal-500'); });
dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('border-teal-500'); });
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('border-teal-500');
const file = e.dataTransfer.files[0];
if (file && file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (ev) => loadNewImage(ev.target.result);
reader.readAsDataURL(file);
}
});
document.addEventListener('paste', (e) => {
const items = e.clipboardData?.items;
if (!items) return;
for (const item of items) {
if (item.type.startsWith('image/')) {
const file = item.getAsFile();
const reader = new FileReader();
reader.onload = (ev) => loadNewImage(ev.target.result);
reader.readAsDataURL(file);
break;
}
}
});
});
function loadNewImage(b64) {
currentImageB64 = b64;
document.getElementById('image-preview').src = currentImageB64;
document.getElementById('upload-prompt').classList.add('hidden');
document.getElementById('preview-container').classList.remove('hidden');
document.getElementById('btn-analyze').disabled = false;
analysisComplete = false;
}
function handleFileSelect(evt) {
const file = evt.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => loadNewImage(e.target.result);
reader.readAsDataURL(file);
}
function clearImage() {
currentImageB64 = null;
analysisComplete = false;
document.getElementById('file-input').value = '';
document.getElementById('upload-prompt').classList.remove('hidden');
document.getElementById('preview-container').classList.add('hidden');
document.getElementById('btn-analyze').disabled = true;
document.getElementById('btn-report').disabled = true;
document.getElementById('submit-panel').classList.add('hidden');
document.getElementById('confidence-box').classList.add('hidden');
const badge = document.getElementById('prediction-badge');
badge.className = 'mt-1 text-sm font-bold text-slate-500 flex items-center gap-2';
badge.innerHTML = '<span class="w-2.5 h-2.5 rounded-full bg-slate-600"></span> Awaiting Input Image';
}
function switchTab(tabId) {
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('border-teal-400', 'text-teal-400');
btn.classList.add('border-transparent', 'text-slate-400');
});
document.querySelectorAll('.tab-section').forEach(sec => sec.classList.add('hidden'));
document.getElementById('tab-' + tabId).classList.add('border-teal-400', 'text-teal-400');
document.getElementById('tab-' + tabId).classList.remove('border-transparent', 'text-slate-400');
document.getElementById('section-' + tabId).classList.remove('hidden');
if (analysisComplete) {
if (tabId === 'explorer') debouncedRunExplorer();
if (tabId === 'heads') debouncedRunHeads();
if (tabId === 'forensics') {
updateForensicDesc();
runForensicAnalysis(false);
}
}
}
async function apiCall(endpoint, payload) {
const resp = await fetch('/api/' + endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
return await resp.json();
}
async function runAnalysis() {
if (!currentImageB64) return;
document.getElementById('loading-spinner').classList.remove('hidden');
document.getElementById('btn-analyze').disabled = true;
document.getElementById('btn-report').disabled = true;
analysisComplete = false;
currentImageHash = null;
cachedForensics = null;
collectedResults = {};
currentAnalysisData = null;
// a) Clear results, show 6 skeleton cards
const grid = document.getElementById('results-grid');
grid.innerHTML = '';
const methods = ['rollout', 'chefer', 'gradcam', 'integrated', 'deeplift', 'occlusion'];
methods.forEach(m => {
const card = document.createElement('div');
card.id = `card-${m}`;
card.className = 'glass-panel rounded-xl p-3 flex flex-col h-full';
card.innerHTML = `
<div class="w-full h-44 rounded-lg skeleton-shimmer mb-3"></div>
<div class="h-4 w-3/4 rounded skeleton-shimmer mb-2"></div>
<div class="h-3 w-1/4 rounded skeleton-shimmer mb-4"></div>
<div class="h-8 w-full rounded skeleton-shimmer mt-auto"></div>
`;
grid.appendChild(card);
});
try {
// b) Call /api/predict
const predictRes = await apiCall('predict', { image_b64: currentImageB64 });
if (predictRes.status === 'success') {
const pred = predictRes.prediction;
currentImageHash = predictRes.image_hash;
currentAnalysisData = pred;
const isFake = pred.prediction === 'FAKE';
const badge = document.getElementById('prediction-badge');
badge.className = `mt-1 text-sm font-bold flex items-center gap-2 ${isFake ? 'text-rose-400' : 'text-emerald-400'}`;
badge.innerHTML = `<span class="w-2.5 h-2.5 rounded-full ${isFake ? 'bg-rose-500' : 'bg-emerald-500'}"></span> VERDICT: ${pred.prediction}`;
document.getElementById('confidence-val').innerText = pred.confidence_pct.toFixed(2) + '%';
document.getElementById('confidence-box').classList.remove('hidden');
}
// c & e) Fire parallel requests
const analyzePromise = apiCall('analyze', { image_b64: currentImageB64, alpha: 0.6, cmap: 'jet' }).then(res => {
if (res.status === 'success') {
cachedForensics = res.forensics;
if(!currentImageHash) currentImageHash = res.image_hash;
}
});
const methodPromises = methods.map(m => {
return apiCall('method_detail', {
image_b64: currentImageB64, method_name: m, steps: 20, patch_size: 32, target_layer_idx: 11, alpha: 0.6, cmap: 'jet'
}).then(res => {
if (res.status === 'success') {
// d) Replace skeleton card
collectedResults[m] = res.result;
renderResultCard(m, res.result);
}
});
});
await Promise.all([...methodPromises, analyzePromise]);
// f) When all complete
analysisComplete = true;
document.getElementById('submit-panel').classList.remove('hidden');
document.getElementById('btn-report').disabled = false;
} catch(err) {
console.error(err);
alert('API Request Failed: ' + err);
} finally {
document.getElementById('loading-spinner').classList.add('hidden');
document.getElementById('btn-analyze').disabled = false;
}
}
function toggleHeatmap(m, e) {
e.stopPropagation();
const state = collectedResults[m];
if(!state) return;
state.showingRaw = !state.showingRaw;
const imgEl = document.getElementById(`img-${m}`);
imgEl.src = state.showingRaw ? state.raw_heatmap_b64 : state.overlay_b64;
}
function renderResultCard(m, result) {
const meta = METHOD_META[m];
result.showingRaw = false;
const card = document.getElementById(`card-${m}`);
if(!card) return;
card.className = 'glass-panel rounded-xl p-3 hover:border-teal-500/50 transition cursor-pointer group flex flex-col h-full';
card.onclick = () => openModal(meta.name, result.showingRaw ? result.raw_heatmap_b64 : result.overlay_b64);
card.innerHTML = `
<div class="relative overflow-hidden rounded-lg mb-2 h-44 bg-slate-900/50 flex-shrink-0">
<img id="img-${m}" src="${result.overlay_b64}" class="w-full h-full object-contain group-hover:scale-105 transition duration-300" />
<span class="absolute bottom-2 right-2 bg-slate-950/80 text-teal-400 text-[10px] font-mono px-2 py-0.5 rounded border border-slate-700">${Math.round(result.compute_time_ms)} ms</span>
<button onclick="toggleHeatmap('${m}', event)" class="absolute top-2 right-2 bg-slate-900/80 text-slate-300 hover:text-white w-7 h-7 rounded-full text-xs flex items-center justify-center border border-slate-600 shadow transition z-10" title="Toggle Raw Heatmap">
<i class="fa-solid fa-arrows-rotate"></i>
</button>
</div>
<h4 class="text-xs font-bold text-slate-200 mb-1">${meta.name}</h4>
<p class="text-[10px] text-slate-400 line-clamp-3 leading-relaxed mt-auto">${meta.insight}</p>
`;
}
async function generateReport() {
if (!currentImageB64 || Object.keys(collectedResults).length < 6) return;
const btn = document.getElementById('btn-report');
btn.disabled = true;
btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Generating...';
try {
const reportDiv = document.createElement('div');
reportDiv.id = 'report-card';
reportDiv.style.position = 'absolute';
reportDiv.style.left = '-9999px';
reportDiv.style.top = '-9999px';
reportDiv.style.width = '800px';
reportDiv.style.backgroundColor = '#0f172a';
reportDiv.style.color = '#f1f5f9';
reportDiv.style.padding = '32px';
reportDiv.style.fontFamily = 'Inter, sans-serif';
let verdictColor = currentAnalysisData.prediction === 'FAKE' ? '#fb7185' : '#34d399';
let html = `
<div style="border-bottom: 1px solid #334155; padding-bottom: 16px; margin-bottom: 24px;">
<h1 style="font-size: 24px; font-weight: bold; margin: 0 0 8px 0; display: flex; align-items: center; gap: 8px;">
<span style="color: #2dd4bf;">●</span> ViT Deepfake Forensic Analysis Report
</h1>
<div style="display: flex; justify-content: space-between; align-items: center; font-size: 14px; color: #94a3b8;">
<span>Model: buildborderless/CommunityForensics-DeepfakeDet-ViT</span>
<span>${new Date().toLocaleString()}</span>
</div>
</div>
<div style="background: rgba(30, 41, 59, 0.5); border: 1px solid #334155; border-radius: 12px; padding: 20px; margin-bottom: 24px; display: flex; justify-content: space-between; align-items: center;">
<div>
<span style="font-size: 12px; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em;">Verdict</span>
<div style="font-size: 20px; font-weight: bold; color: ${verdictColor}; margin-top: 4px;">
${currentAnalysisData.prediction}
</div>
</div>
<div style="text-align: right;">
<span style="font-size: 12px; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em;">Confidence</span>
<div style="font-size: 20px; font-weight: bold; color: #2dd4bf; margin-top: 4px; font-family: monospace;">
${currentAnalysisData.confidence_pct.toFixed(2)}%
</div>
</div>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px;">
`;
const methods = ['rollout', 'chefer', 'gradcam', 'integrated', 'deeplift', 'occlusion'];
methods.forEach(m => {
const res = collectedResults[m];
const meta = METHOD_META[m];
html += `
<div style="background: #1e293b; border-radius: 8px; padding: 12px;">
<img src="${res.overlay_b64}" style="width: 100%; aspect-ratio: 1/1; object-fit: contain; background: #0f172a; border-radius: 6px; margin-bottom: 12px;" />
<div style="font-size: 14px; font-weight: 600; color: #f1f5f9;">${meta.name}</div>
<div style="font-size: 12px; color: #94a3b8; font-family: monospace;">${Math.round(res.compute_time_ms)} ms</div>
</div>
`;
});
html += `
</div>
<div style="border-top: 1px solid #334155; padding-top: 16px; font-size: 12px; color: #64748b; text-align: center;">
Generated by LPX55/DeepfakeDetection-Explainability
</div>
`;
reportDiv.innerHTML = html;
document.body.appendChild(reportDiv);
const canvas = await html2canvas(reportDiv, { scale: 2, backgroundColor: '#0f172a' });
const imgData = canvas.toDataURL('image/png');
document.body.removeChild(reportDiv);
const a = document.createElement('a');
a.href = imgData;
a.download = `forensic_report_${Date.now()}.png`;
a.click();
} catch (err) {
console.error(err);
alert('Report generation failed: ' + err);
} finally {
btn.disabled = false;
btn.innerHTML = '<i class="fa-solid fa-file-arrow-down"></i> Report PNG';
}
}
async function runExplorerMethod() {
if (!currentImageB64) return;
const method = document.getElementById('exp-method').value;
const cmap = document.getElementById('exp-cmap').value;
const alpha = parseFloat(document.getElementById('exp-alpha').value);
const steps = parseInt(document.getElementById('exp-steps').value);
const patch_size = parseInt(document.getElementById('exp-patch').value);
const target_layer_idx = parseInt(document.getElementById('exp-layer').value) - 1;
const ctrlIG = document.getElementById('ctrl-integrated');
if (ctrlIG) ctrlIG.classList.toggle('hidden', method !== 'integrated');
const ctrlOcc = document.getElementById('ctrl-occlusion');
if (ctrlOcc) ctrlOcc.classList.toggle('hidden', method !== 'occlusion');
const ctrlGC = document.getElementById('ctrl-gradcam');
if (ctrlGC) ctrlGC.classList.toggle('hidden', method !== 'gradcam');
const res = await apiCall('method_detail', {
image_b64: currentImageB64,
method_name: method,
steps: steps,
patch_size: patch_size,
target_layer_idx: target_layer_idx,
alpha: alpha,
cmap: cmap
});
if (res.status === 'success' && res.result) {
document.getElementById('exp-img-overlay').src = res.result.overlay_b64;
document.getElementById('exp-img-raw').src = res.result.raw_heatmap_b64;
}
}
async function runPerHeadInspector() {
if (!currentImageB64) return;
const displayLayer = parseInt(document.getElementById('head-layer-input').value);
const layer_idx = displayLayer - 1;
const res = await apiCall('heads', {
image_b64: currentImageB64,
layer_idx: layer_idx,
cmap: 'jet'
});
if (res.status === 'success' && res.heads) {
const grid = document.getElementById('heads-grid');
grid.innerHTML = '';
res.heads.forEach((head) => {
const card = document.createElement('div');
card.className = 'glass-panel rounded-xl p-2 text-center hover:border-teal-500/50 transition cursor-pointer';
card.onclick = () => openModal(`Layer ${displayLayer} — Head ${head.head}`, head.overlay_b64);
card.innerHTML = `
<img src="${head.overlay_b64}" class="w-full h-24 object-cover rounded-lg mb-1" />
<span class="text-[10px] font-mono text-slate-300">Head ${head.head}</span>
`;
grid.appendChild(card);
});
}
}
function updateForensicDesc() {
const tool = document.getElementById('for-tool').value;
const meta = FORENSIC_META[tool];
if(meta) {
document.getElementById('for-desc-title').innerText = meta.name;
document.getElementById('for-desc-text').innerText = meta.desc;
document.getElementById('for-desc-look').innerText = meta.lookFor;
}
}
async function runForensicAnalysis(force = true) {
if (!currentImageB64) return;
const tool = document.getElementById('for-tool').value;
const quality = parseInt(document.getElementById('for-quality').value);
const bit = parseInt(document.getElementById('for-bit').value);
updateForensicDesc();
const ctrlELA = document.getElementById('ctrl-ela');
if (ctrlELA) ctrlELA.classList.toggle('hidden', tool !== 'ela');
const ctrlBit = document.getElementById('ctrl-bitplane');
if (ctrlBit) ctrlBit.classList.toggle('hidden', tool !== 'bitplane');
if (!force && cachedForensics && cachedForensics[tool]) {
const cached = cachedForensics[tool];
displayForensicResult(cached.result_b64, cached.compute_time_ms, tool);
return;
}
const res = await apiCall('forensic', {
image_b64: currentImageB64,
tool_name: tool,
quality: quality,
bit: bit
});
if (res.status === 'success' && res.result_b64) {
displayForensicResult(res.result_b64, res.compute_time_ms, res.tool);
}
}
function displayForensicResult(b64, time, toolName) {
const img = document.getElementById('for-img-output');
img.src = b64;
img.classList.remove('hidden');
document.getElementById('for-placeholder').classList.add('hidden');
document.getElementById('for-info').innerText = `${toolName} analysis complete (${Math.round(time)} ms).`;
}
async function submitDatasetRun() {
if (!currentImageB64 || !currentImageHash) return;
const gtEl = document.querySelector('input[name="ground_truth"]:checked');
const gt = gtEl ? gtEl.value : 'unknown';
const optIn = document.getElementById('opt-in-image').checked;
const res = await apiCall('submit_run', {
image_b64: currentImageB64,
image_hash: currentImageHash,
ground_truth: gt,
opt_in_image: optIn
});
if (res.status === 'success') {
const statusP = document.getElementById('submit-status');
statusP.innerText = `Run ${res.run_id} logged to dataset buildborderless/deepfake-explainability!`;
statusP.classList.remove('hidden');
}
}
function openModal(title, b64) {
document.getElementById('modal-title').innerText = title;
document.getElementById('modal-img').src = b64;
document.getElementById('modal-viewer').classList.remove('hidden');
}
function closeModal() {
document.getElementById('modal-viewer').classList.add('hidden');
}
</script>
</body>
</html>
|