| | document.addEventListener('DOMContentLoaded', () => {
|
| | const dropZone = document.getElementById('drop-zone');
|
| | const fileInput = document.getElementById('file-input');
|
| | const previewContainer = document.getElementById('preview-container');
|
| | const imagePreview = document.getElementById('image-preview');
|
| | const removeBtn = document.getElementById('remove-btn');
|
| | const analyzeBtn = document.getElementById('analyze-btn');
|
| | const resultContainer = document.getElementById('result-container');
|
| | const resultBadge = document.getElementById('result-badge');
|
| |
|
| | const tabDetector = document.getElementById('tab-detector');
|
| | const tabRisk = document.getElementById('tab-risk');
|
| | let currentMode = 'detector';
|
| |
|
| | const modelLabel = document.getElementById('model-label');
|
| | const btnText = analyzeBtn.querySelector('.btn-text');
|
| | const loader = analyzeBtn.querySelector('.loader');
|
| |
|
| | let currentFile = null;
|
| |
|
| |
|
| | function switchTab(mode) {
|
| | currentMode = mode;
|
| | resultContainer.classList.add('hidden');
|
| |
|
| | if (mode === 'detector') {
|
| | tabDetector.className = 'px-6 py-2 rounded-full font-medium transition-all duration-300 bg-white text-black shadow-[0_0_20px_-5px_rgba(255,255,255,0.5)]';
|
| | tabRisk.className = 'px-6 py-2 rounded-full font-medium transition-all duration-300 bg-white/5 text-brand-gray hover:bg-white/10 hover:text-white border border-white/10';
|
| | btnText.textContent = 'Analyze Authenticity';
|
| | } else {
|
| | tabRisk.className = 'px-6 py-2 rounded-full font-medium transition-all duration-300 bg-brand-green text-black shadow-[0_0_20px_-5px_rgba(0,220,130,0.5)]';
|
| | tabDetector.className = 'px-6 py-2 rounded-full font-medium transition-all duration-300 bg-white/5 text-brand-gray hover:bg-white/10 hover:text-white border border-white/10';
|
| | btnText.textContent = 'Check Identity Risk';
|
| | }
|
| | }
|
| |
|
| | tabDetector.addEventListener('click', () => switchTab('detector'));
|
| | tabRisk.addEventListener('click', () => switchTab('risk'));
|
| |
|
| |
|
| |
|
| | dropZone.addEventListener('click', () => fileInput.click());
|
| |
|
| | dropZone.addEventListener('dragover', (e) => {
|
| | e.preventDefault();
|
| | dropZone.classList.add('dragover');
|
| | });
|
| |
|
| | dropZone.addEventListener('dragleave', () => {
|
| | dropZone.classList.remove('dragover');
|
| | });
|
| |
|
| | dropZone.addEventListener('drop', (e) => {
|
| | e.preventDefault();
|
| | dropZone.classList.remove('dragover');
|
| | if (e.dataTransfer.files.length) {
|
| | handleFile(e.dataTransfer.files[0]);
|
| | }
|
| | });
|
| |
|
| | fileInput.addEventListener('change', (e) => {
|
| | if (e.target.files.length) {
|
| | handleFile(e.target.files[0]);
|
| | }
|
| | });
|
| |
|
| |
|
| | function handleFile(file) {
|
| | if (!file.type.startsWith('image/')) {
|
| | alert('Please upload an image file (JPG, PNG).');
|
| | return;
|
| | }
|
| |
|
| | currentFile = file;
|
| | const reader = new FileReader();
|
| | reader.onload = (e) => {
|
| | imagePreview.src = e.target.result;
|
| | dropZone.classList.add('hidden');
|
| | previewContainer.classList.remove('hidden');
|
| | analyzeBtn.disabled = false;
|
| | resultContainer.classList.add('hidden');
|
| | };
|
| | reader.readAsDataURL(file);
|
| | }
|
| |
|
| |
|
| | removeBtn.addEventListener('click', () => {
|
| | currentFile = null;
|
| | fileInput.value = '';
|
| | dropZone.classList.remove('hidden');
|
| | previewContainer.classList.add('hidden');
|
| | analyzeBtn.disabled = true;
|
| | resultContainer.classList.add('hidden');
|
| | });
|
| |
|
| |
|
| | analyzeBtn.addEventListener('click', async () => {
|
| | if (!currentFile) return;
|
| |
|
| |
|
| | analyzeBtn.disabled = true;
|
| | btnText.textContent = currentMode === 'detector' ? 'Analysing...' : 'Checking Risk...';
|
| | loader.classList.remove('hidden');
|
| | resultContainer.classList.add('hidden');
|
| |
|
| | const formData = new FormData();
|
| | formData.append('image', currentFile);
|
| |
|
| | try {
|
| | const response = await fetch('/predict', {
|
| | method: 'POST',
|
| | body: formData
|
| | });
|
| |
|
| | if (!response.ok) throw new Error('API Error');
|
| |
|
| | const data = await response.json();
|
| | displayResult(data);
|
| |
|
| | } catch (error) {
|
| | console.error(error);
|
| | alert('Error processing image. Please try again.');
|
| | } finally {
|
| |
|
| | analyzeBtn.disabled = false;
|
| | btnText.textContent = currentMode === 'detector' ? 'Analyze Authenticity' : 'Check Identity Risk';
|
| | loader.classList.add('hidden');
|
| | }
|
| | });
|
| |
|
| | function displayResult(data) {
|
| | resultContainer.classList.remove('hidden');
|
| | const badgeContainer = document.getElementById('result-badge-container');
|
| |
|
| |
|
| | const existingWarning = document.getElementById('risk-warning');
|
| | if (existingWarning) existingWarning.remove();
|
| | const existingList = document.getElementById('risk-details');
|
| | if (existingList) existingList.remove();
|
| |
|
| |
|
| | const confidenceWrapper = document.getElementById('confidence-wrapper');
|
| | const explanationsSection = document.getElementById('explanations-section');
|
| | const qualificationTag = document.getElementById('qualification-tag');
|
| |
|
| | if (currentMode === 'detector') {
|
| |
|
| | if (confidenceWrapper) confidenceWrapper.classList.remove('hidden');
|
| |
|
| | modelLabel.textContent = 'AI Detection Result';
|
| | const label = data.prediction.toLowerCase();
|
| | resultBadge.textContent = label.toUpperCase();
|
| |
|
| |
|
| | if (qualificationTag) {
|
| | if (data.classification_tag) {
|
| | qualificationTag.classList.remove('hidden');
|
| | qualificationTag.textContent = data.classification_tag;
|
| |
|
| |
|
| | qualificationTag.className = "px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider border transition-colors duration-300";
|
| |
|
| | if (data.classification_tag.includes("AI") || data.classification_tag.includes("Manipulation")) {
|
| | qualificationTag.classList.add("bg-red-500/10", "border-red-500/20", "text-red-200");
|
| | } else if (data.classification_tag.includes("Raw")) {
|
| | qualificationTag.classList.add("bg-brand-green/10", "border-brand-green/20", "text-brand-green");
|
| | } else {
|
| | qualificationTag.classList.add("bg-yellow-500/10", "border-yellow-500/20", "text-yellow-200");
|
| | }
|
| | } else {
|
| | qualificationTag.classList.add('hidden');
|
| | }
|
| | }
|
| |
|
| |
|
| | const confidenceBar = document.getElementById('confidence-bar');
|
| | const confidenceValue = document.getElementById('confidence-value');
|
| | const percent = Math.round(data.confidence * 100);
|
| |
|
| |
|
| | setTimeout(() => {
|
| | confidenceBar.style.width = `${percent}%`;
|
| | }, 100);
|
| | confidenceValue.textContent = `${percent}%`;
|
| |
|
| | resultBadge.className = 'text-3xl font-bold tracking-tight transition-colors duration-300';
|
| | confidenceBar.className = 'h-full w-0 rounded-full transition-all duration-1000 ease-out';
|
| |
|
| | if (label.includes('real')) {
|
| | resultBadge.classList.add('text-brand-green', 'drop-shadow-[0_0_15px_rgba(0,220,130,0.5)]');
|
| | confidenceBar.classList.add('bg-brand-green', 'shadow-[0_0_10px_rgba(0,220,130,0.5)]');
|
| | } else if (label.includes('fake') || label.includes('ai')) {
|
| | resultBadge.classList.add('text-red-500', 'drop-shadow-[0_0_15px_rgba(255,50,50,0.5)]');
|
| | confidenceBar.classList.add('bg-red-500', 'shadow-[0_0_10px_rgba(255,50,50,0.5)]');
|
| | } else {
|
| | resultBadge.classList.add('text-brand-gray');
|
| | confidenceBar.classList.add('bg-gray-500');
|
| | }
|
| |
|
| |
|
| | const explanationsSection = document.getElementById('explanations-section');
|
| | const reasonsList = document.getElementById('reasons-list');
|
| |
|
| | reasonsList.innerHTML = '';
|
| |
|
| | if (data.analysis_points && data.analysis_points.length > 0) {
|
| | explanationsSection.classList.remove('hidden');
|
| | data.analysis_points.forEach(point => {
|
| | const li = document.createElement('li');
|
| | li.className = 'text-gray-300 text-sm flex items-start gap-2.5 leading-relaxed';
|
| | li.innerHTML = `
|
| | <i class="fa-solid fa-circle-check text-brand-green text-[10px] mt-1.5 opacity-80"></i>
|
| | <span class="opacity-90">${point}</span>
|
| | `;
|
| |
|
| | if (label.includes('ai')) {
|
| | li.innerHTML = `
|
| | <i class="fa-solid fa-circle-exclamation text-red-500 text-[10px] mt-1.5 opacity-80"></i>
|
| | <span class="opacity-90">${point}</span>
|
| | `;
|
| | }
|
| | reasonsList.appendChild(li);
|
| | });
|
| | } else {
|
| | explanationsSection.classList.add('hidden');
|
| | }
|
| |
|
| |
|
| | if (data.risk_analysis && data.risk_analysis.is_high_risk && label.includes('real')) {
|
| | const warningDiv = document.createElement('div');
|
| | warningDiv.id = 'risk-warning';
|
| | warningDiv.className = 'mt-4 p-3 rounded-xl bg-orange-500/10 border border-orange-500/20 backdrop-blur-sm animate-fade-in-up';
|
| | warningDiv.innerHTML = `
|
| | <div class="flex items-center gap-3">
|
| | <i class="fa-solid fa-triangle-exclamation text-orange-500"></i>
|
| | <p class="text-orange-200/80 text-xs">
|
| | Identity Risk Detected. Switch to the <b>Identity Risk</b> tab for details.
|
| | </p>
|
| | </div>
|
| | `;
|
| | const resultCard = resultContainer.querySelector('div');
|
| | resultCard.appendChild(warningDiv);
|
| | }
|
| |
|
| | } else {
|
| |
|
| | modelLabel.textContent = 'Identity Theft Risk Analysis';
|
| |
|
| |
|
| | if (confidenceWrapper) confidenceWrapper.classList.add('hidden');
|
| | if (explanationsSection) explanationsSection.classList.add('hidden');
|
| |
|
| |
|
| | if (data.prediction !== "Real") {
|
| |
|
| | resultBadge.textContent = "N/A (AI Detected)";
|
| | resultBadge.className = 'text-xl font-semibold text-brand-gray';
|
| |
|
| | const infoDiv = document.createElement('div');
|
| | infoDiv.id = 'risk-details';
|
| | infoDiv.className = 'mt-4 text-brand-gray/60 text-sm text-center';
|
| | infoDiv.textContent = "Identity risk analysis is only performed on real images.";
|
| |
|
| | const resultCard = resultContainer.querySelector('div');
|
| | resultCard.appendChild(infoDiv);
|
| | return;
|
| | }
|
| |
|
| | if (!data.risk_analysis || data.risk_analysis.error) {
|
| | resultBadge.textContent = "Error";
|
| | return;
|
| | }
|
| |
|
| | const isHighRisk = data.risk_analysis.is_high_risk;
|
| | resultBadge.textContent = isHighRisk ? "HIGH RISK" : "LOW RISK";
|
| |
|
| | resultBadge.className = 'text-3xl font-bold tracking-tight transition-colors duration-300';
|
| | if (isHighRisk) {
|
| | resultBadge.classList.add('text-red-500', 'drop-shadow-[0_0_15px_rgba(255,50,50,0.5)]');
|
| | } else {
|
| | resultBadge.classList.add('text-brand-green', 'drop-shadow-[0_0_15px_rgba(0,220,130,0.5)]');
|
| | }
|
| |
|
| |
|
| | const detailsDiv = document.createElement('div');
|
| | detailsDiv.id = 'risk-details';
|
| | detailsDiv.className = 'mt-6 pt-4 border-t border-white/10 animate-fade-in-up';
|
| |
|
| | let html = '<div class="space-y-3">';
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | const passed = data.risk_analysis.passed_criteria || [];
|
| | const failed = data.risk_analysis.details || [];
|
| |
|
| |
|
| | if (passed.length > 0) {
|
| | html += '<h5 class="text-xs font-semibold text-brand-gray uppercase mb-2">Risk Factors (ID Compliant)</h5>';
|
| | passed.forEach(item => {
|
| | html += `
|
| | <div class="flex items-center gap-2 text-sm text-red-200/80">
|
| | <i class="fa-solid fa-check text-red-500"></i>
|
| | <span>${item}</span>
|
| | </div>`;
|
| | });
|
| | }
|
| |
|
| |
|
| | if (failed.length > 0) {
|
| | html += '<h5 class="text-xs font-semibold text-brand-gray uppercase mt-4 mb-2">Safety Factors (Non-ID)</h5>';
|
| | failed.forEach(item => {
|
| | html += `
|
| | <div class="flex items-center gap-2 text-sm text-brand-green/80">
|
| | <i class="fa-solid fa-shield text-brand-green"></i>
|
| | <span>${item}</span>
|
| | </div>`;
|
| | });
|
| | }
|
| |
|
| | html += '</div>';
|
| | detailsDiv.innerHTML = html;
|
| |
|
| |
|
| |
|
| | const resultCard = resultContainer.querySelector('div');
|
| | resultCard.appendChild(detailsDiv);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | const advancedToggleContainer = document.getElementById('advanced-toggle-container');
|
| | const advancedSection = document.getElementById('advanced-analysis-section');
|
| | const advancedBtn = document.getElementById('advanced-btn');
|
| |
|
| | const frequencyMapImg = document.getElementById('frequency-map-img');
|
| | const patternMapImg = document.getElementById('pattern-map-img');
|
| |
|
| | const tabFreq = document.getElementById('tab-freq');
|
| | const tabPattern = document.getElementById('tab-pattern');
|
| | const panelFreq = document.getElementById('panel-freq');
|
| | const panelPattern = document.getElementById('panel-pattern');
|
| |
|
| | if (advancedToggleContainer && advancedSection) {
|
| |
|
| | advancedToggleContainer.classList.add('hidden');
|
| | advancedSection.classList.add('hidden');
|
| | advancedBtn.innerHTML = '<i class="fa-solid fa-chart-simple mr-2"></i>View Advanced Frequency Analysis';
|
| |
|
| |
|
| | if (currentMode === 'detector' && (data.frequency_analysis || data.pattern_analysis)) {
|
| | advancedToggleContainer.classList.remove('hidden');
|
| |
|
| |
|
| | if (data.frequency_analysis && frequencyMapImg) frequencyMapImg.src = 'data:image/png;base64,' + data.frequency_analysis;
|
| | if (data.pattern_analysis && patternMapImg) patternMapImg.src = 'data:image/png;base64,' + data.pattern_analysis;
|
| |
|
| |
|
| | const newBtn = advancedBtn.cloneNode(true);
|
| | advancedBtn.parentNode.replaceChild(newBtn, advancedBtn);
|
| |
|
| | newBtn.addEventListener('click', () => {
|
| | const isHidden = advancedSection.classList.contains('hidden');
|
| | if (isHidden) {
|
| | advancedSection.classList.remove('hidden');
|
| | newBtn.innerHTML = '<i class="fa-solid fa-chevron-up mr-2"></i>Hide Advanced Analysis';
|
| | } else {
|
| | advancedSection.classList.add('hidden');
|
| | newBtn.innerHTML = '<i class="fa-solid fa-chart-simple mr-2"></i>View Advanced Frequency Analysis';
|
| | }
|
| | });
|
| |
|
| |
|
| |
|
| | const newTabFreq = tabFreq.cloneNode(true);
|
| | if (tabFreq) tabFreq.parentNode.replaceChild(newTabFreq, tabFreq);
|
| |
|
| | const newTabPattern = tabPattern.cloneNode(true);
|
| | if (tabPattern) tabPattern.parentNode.replaceChild(newTabPattern, tabPattern);
|
| |
|
| | const setActive = (activeTab, activePanel) => {
|
| |
|
| | [newTabFreq, newTabPattern].forEach(t => {
|
| | if (t) t.className = "relative px-4 py-2 text-xs font-medium text-brand-gray bg-black/40 rounded-t-lg transition-colors border-t border-x border-white/5 hover:bg-white/5 hover:text-white cursor-pointer";
|
| | });
|
| | if (panelFreq) panelFreq.classList.add('hidden');
|
| | if (panelPattern) panelPattern.classList.add('hidden');
|
| |
|
| |
|
| | if (activeTab) activeTab.className = "relative px-4 py-2 text-xs font-medium text-white bg-white/10 rounded-t-lg transition-colors border-t border-x border-white/10 hover:bg-white/20 z-10 cursor-default";
|
| | if (activePanel) activePanel.classList.remove('hidden');
|
| | };
|
| |
|
| | if (newTabFreq) newTabFreq.addEventListener('click', () => setActive(newTabFreq, panelFreq));
|
| | if (newTabPattern) newTabPattern.addEventListener('click', () => setActive(newTabPattern, panelPattern));
|
| |
|
| |
|
| | setActive(newTabFreq, panelFreq);
|
| | }
|
| | }
|
| | }
|
| | });
|
| |
|