|
|
|
|
|
import importlib.util |
|
|
import os |
|
|
import sys |
|
|
import time |
|
|
import cv2 |
|
|
import torch |
|
|
import numpy as np |
|
|
import gradio as gr |
|
|
from PIL import Image |
|
|
from torchvision import transforms |
|
|
import torch.nn as nn |
|
|
import torch.nn.functional as F |
|
|
import traceback |
|
|
from torchvision.models import vit_b_16 |
|
|
from transformers import AutoModel, CLIPImageProcessor |
|
|
import joblib |
|
|
import zipfile |
|
|
import json |
|
|
from datetime import datetime |
|
|
import requests |
|
|
import base64 |
|
|
import io |
|
|
|
|
|
|
|
|
if not os.getcwd() in sys.path: |
|
|
sys.path.append(os.getcwd()) |
|
|
|
|
|
|
|
|
if importlib.util.find_spec("detectron") is None: |
|
|
print("π Detectron2 not found. Attempting installation...") |
|
|
print("Installing PyTorch and Detectron2...") |
|
|
os.system("pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/cpu") |
|
|
os.system("pip install git+https://github.com/facebookresearch/detectron2.git") |
|
|
print("Installation complete!") |
|
|
|
|
|
|
|
|
DETECTRON2_AVAILABLE = False |
|
|
try: |
|
|
print("Attempting to import Detectron2...") |
|
|
from detectron2.engine import DefaultPredictor |
|
|
from detectron2.config import get_cfg |
|
|
from detectron2.utils.visualizer import Visualizer, ColorMode |
|
|
from detectron2 import model_zoo |
|
|
|
|
|
DETECTRON2_AVAILABLE = True |
|
|
print("β
Detectron2 imported successfully") |
|
|
except ImportError as e: |
|
|
print(f"β οΈ Detectron2 not available: {e}") |
|
|
DETECTRON2_AVAILABLE = False |
|
|
|
|
|
|
|
|
huggingface_model_path = None |
|
|
try: |
|
|
from huggingface_hub import hf_hub_download |
|
|
|
|
|
|
|
|
huggingface_model_path = hf_hub_download( |
|
|
repo_id=os.getenv('PRIVATE_REPO', 'fallback'), |
|
|
filename="V1.pkl", |
|
|
token=os.getenv('key') |
|
|
) |
|
|
print(f"β
Model downloaded from Hugging Face: {huggingface_model_path}") |
|
|
except Exception as e: |
|
|
print(f"β οΈ Could not download model from Hugging Face: {e}") |
|
|
print("π Will use demo mode with simulated results") |
|
|
huggingface_model_path = None |
|
|
|
|
|
|
|
|
DEFAULT_DAMAGE_MODEL_PATH = "./output/model_final.pth" |
|
|
DEFAULT_AI_DETECTION_MODEL_PATH = "./output/V1.pkl" |
|
|
|
|
|
|
|
|
if torch.backends.mps.is_available(): |
|
|
RADIO_DEVICE = torch.device("mps") |
|
|
elif torch.cuda.is_available(): |
|
|
RADIO_DEVICE = torch.device("cuda") |
|
|
else: |
|
|
RADIO_DEVICE = torch.device("cpu") |
|
|
|
|
|
|
|
|
radio_l_image_processor = None |
|
|
radio_l_model = None |
|
|
ai_detection_classifier = None |
|
|
|
|
|
|
|
|
|
|
|
def preload_models(): |
|
|
"""Preload models at startup to improve response time""" |
|
|
global radio_l_image_processor, radio_l_model |
|
|
|
|
|
print("π Preloading C model (4GB)...") |
|
|
try: |
|
|
hf_repo = os.getenv('MODEL_REPO', 'fallback') |
|
|
if hf_repo and hf_repo != 'fallback': |
|
|
from transformers import AutoModel, CLIPImageProcessor |
|
|
radio_l_image_processor = CLIPImageProcessor.from_pretrained(hf_repo) |
|
|
radio_l_model = AutoModel.from_pretrained(hf_repo, trust_remote_code=True) |
|
|
radio_l_model = radio_l_model.to(RADIO_DEVICE) |
|
|
radio_l_model.eval() |
|
|
print("β
C model preloaded successfully!") |
|
|
return True |
|
|
except Exception as e: |
|
|
print(f"β οΈ Could not preload C model: {e}") |
|
|
return False |
|
|
|
|
|
|
|
|
|
|
|
MAX_TRIES = 10 |
|
|
|
|
|
|
|
|
MAILJET_CONFIG = { |
|
|
'API_KEY': os.getenv('MAILJET_API_KEY', ''), |
|
|
'SECRET_KEY': os.getenv('MAILJET_SECRET_KEY', ''), |
|
|
'FROM_EMAIL': os.getenv('FROM_EMAIL', 'sales@askhedi.fr'), |
|
|
'FROM_NAME': os.getenv('FROM_NAME', 'Simon de HEDI - Askhedi'), |
|
|
'URL': 'https://api.mailjet.com/v3.1/send' |
|
|
} |
|
|
|
|
|
|
|
|
COOKIE_JAVASCRIPT = """ |
|
|
<script> |
|
|
// Fonctions de gestion des cookies HEDI - Version Debug |
|
|
function setCookie(name, value, days = 1) { |
|
|
try { |
|
|
const expires = new Date(); |
|
|
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000)); |
|
|
document.cookie = name + '=' + value + ';expires=' + expires.toUTCString() + ';path=/;SameSite=Lax'; |
|
|
console.log('β
Cookie set:', name, '=', value); |
|
|
return true; |
|
|
} catch (e) { |
|
|
console.error('β Error setting cookie:', e); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
function getCookie(name) { |
|
|
try { |
|
|
const nameEQ = name + '='; |
|
|
const ca = document.cookie.split(';'); |
|
|
for(let i = 0; i < ca.length; i++) { |
|
|
let c = ca[i]; |
|
|
while (c.charAt(0) == ' ') c = c.substring(1, c.length); |
|
|
if (c.indexOf(nameEQ) == 0) { |
|
|
const value = c.substring(nameEQ.length, c.length); |
|
|
console.log('π Cookie read:', name, '=', value); |
|
|
return value; |
|
|
} |
|
|
} |
|
|
console.log('π Cookie not found:', name); |
|
|
return null; |
|
|
} catch (e) { |
|
|
console.error('β Error reading cookie:', e); |
|
|
return null; |
|
|
} |
|
|
} |
|
|
function getHediUsage() { |
|
|
try { |
|
|
console.log('π Getting HEDI usage...'); |
|
|
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD |
|
|
const lastDate = getCookie('hedi_last_date'); |
|
|
|
|
|
// Reset quotidien automatique |
|
|
if (lastDate !== today) { |
|
|
console.log('π Daily reset detected: ' + lastDate + ' β ' + today); |
|
|
setCookie('hedi_usage_count', '0', 1); |
|
|
setCookie('hedi_last_date', today, 1); |
|
|
console.log('β
Usage reset to 0'); |
|
|
return 0; |
|
|
} |
|
|
|
|
|
const usage = parseInt(getCookie('hedi_usage_count') || '0'); |
|
|
console.log('πͺ Current usage from cookies: ' + usage + '/10'); |
|
|
return usage; |
|
|
} catch (e) { |
|
|
console.error('β Error getting usage from cookies:', e); |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
function saveHediUsage(count) { |
|
|
try { |
|
|
console.log('πΎ Saving usage to cookies:', count); |
|
|
const today = new Date().toISOString().split('T')[0]; |
|
|
const success1 = setCookie('hedi_usage_count', count.toString(), 1); |
|
|
const success2 = setCookie('hedi_last_date', today, 1); |
|
|
|
|
|
if (success1 && success2) { |
|
|
console.log('β
Usage saved successfully: ' + count + '/10'); |
|
|
return true; |
|
|
} else { |
|
|
console.error('β Failed to save usage'); |
|
|
return false; |
|
|
} |
|
|
} catch (e) { |
|
|
console.error('β Error saving usage to cookies:', e); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
// Exposer les fonctions globalement avec fallback |
|
|
window.hediCookies = { |
|
|
getUsage: function() { |
|
|
try { |
|
|
return getHediUsage(); |
|
|
} catch (e) { |
|
|
console.error('Fallback: Error in getUsage', e); |
|
|
return 0; |
|
|
} |
|
|
}, |
|
|
saveUsage: function(count) { |
|
|
try { |
|
|
return saveHediUsage(count); |
|
|
} catch (e) { |
|
|
console.error('Fallback: Error in saveUsage', e); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
}; |
|
|
// Initialiser immΓ©diatement ET au chargement |
|
|
console.log('πͺ HEDI Cookies loading...'); |
|
|
try { |
|
|
const initialUsage = getHediUsage(); |
|
|
console.log('πͺ HEDI Cookies initialized with usage:', initialUsage); |
|
|
} catch (e) { |
|
|
console.error('β Error during initialization:', e); |
|
|
} |
|
|
// Double initialisation pour Γͺtre sΓ»r |
|
|
setTimeout(function() { |
|
|
console.log('πͺ HEDI Cookies late initialization...'); |
|
|
window.hediCookies.getUsage(); |
|
|
}, 1000); |
|
|
</script> |
|
|
""" |
|
|
|
|
|
|
|
|
def load_usage_cache(): |
|
|
"""Load usage from browser cookies (handled by JavaScript)""" |
|
|
|
|
|
|
|
|
return 0 |
|
|
|
|
|
|
|
|
def save_usage_cache(usage_count): |
|
|
"""Save usage to browser cookies (handled by JavaScript)""" |
|
|
|
|
|
print(f"πΎ Usage will be saved to cookies: {usage_count}/{MAX_TRIES}") |
|
|
return True |
|
|
|
|
|
|
|
|
def get_usage_display_html(usage_count): |
|
|
"""Generate usage display HTML with cookies info""" |
|
|
usage_percent = (usage_count / MAX_TRIES) * 100 |
|
|
color = "#dc2626" if usage_count >= MAX_TRIES else "#2563eb" if usage_count < 7 else "#f59e0b" |
|
|
|
|
|
return f""" |
|
|
<div id="usage-display" style="background: white; border: 1px solid #e5e7eb; padding: 15px; border-radius: 8px;"> |
|
|
<div style="display: flex; justify-content: space-between; margin-bottom: 10px;"> |
|
|
<span>Daily Usage:</span> |
|
|
<span style="background: #dbeafe; color: #1e40af; padding: 2px 8px; border-radius: 12px;">{usage_count}/{MAX_TRIES}</span> |
|
|
</div> |
|
|
<div style="background: #e5e7eb; height: 6px; border-radius: 3px;"> |
|
|
<div style="background: {color}; height: 6px; border-radius: 3px; width: {usage_percent}%; transition: width 0.3s;"></div> |
|
|
</div> |
|
|
<div style="font-size: 12px; color: #6b7280; margin-top: 5px; text-align: center;"> |
|
|
{'β οΈ Daily limit reached!' if usage_count >= MAX_TRIES else f'β
{MAX_TRIES - usage_count} remaining' if usage_count < MAX_TRIES else ''} |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
def verify_detectron2_installation(): |
|
|
"""Verify that Detectron2 is properly installed""" |
|
|
results = { |
|
|
"detectron2_installed": False, |
|
|
"model_zoo_accessible": False, |
|
|
"can_create_cfg": False, |
|
|
"error_messages": [] |
|
|
} |
|
|
|
|
|
try: |
|
|
import importlib.util |
|
|
if importlib.util.find_spec("detectron2") is not None: |
|
|
results["detectron2_installed"] = True |
|
|
|
|
|
try: |
|
|
import detectron2 |
|
|
from detectron2 import model_zoo |
|
|
config_file = "COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml" |
|
|
config_path = model_zoo.get_config_file(config_file) |
|
|
if os.path.exists(config_path): |
|
|
results["model_zoo_accessible"] = True |
|
|
except Exception as e: |
|
|
results["error_messages"].append(f"Error accessing model zoo: {str(e)}") |
|
|
|
|
|
try: |
|
|
from detectron2.config import get_cfg |
|
|
cfg = get_cfg() |
|
|
results["can_create_cfg"] = True |
|
|
except Exception as e: |
|
|
results["error_messages"].append(f"Error creating Detectron2 config: {str(e)}") |
|
|
else: |
|
|
results["error_messages"].append("Detectron2 is not installed") |
|
|
except Exception as e: |
|
|
results["error_messages"].append(f"Error checking Detectron2 installation: {str(e)}") |
|
|
|
|
|
return results |
|
|
|
|
|
|
|
|
def auto_install_dependencies(): |
|
|
"""Attempt to install dependencies if needed""" |
|
|
try: |
|
|
import importlib.util |
|
|
|
|
|
|
|
|
if importlib.util.find_spec("torch") is None: |
|
|
print("Installing PyTorch...") |
|
|
os.system("pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/cpu") |
|
|
|
|
|
|
|
|
if importlib.util.find_spec("detectron2") is None: |
|
|
print("Installing Detectron2...") |
|
|
os.system("pip install git+https://github.com/facebookresearch/detectron2.git") |
|
|
|
|
|
|
|
|
if importlib.util.find_spec("gradio") is None: |
|
|
print("Installing Gradio...") |
|
|
os.system("pip install gradio") |
|
|
|
|
|
print("Dependencies installation complete!") |
|
|
return True |
|
|
except Exception as e: |
|
|
print(f"Error installing dependencies: {e}") |
|
|
return False |
|
|
|
|
|
|
|
|
def send_email_with_mailjet(recipient_email, analysis_text, result_image, original_filename): |
|
|
"""Send email using Mailjet API (works perfectly in cloud environments)""" |
|
|
|
|
|
if not MAILJET_CONFIG['API_KEY'] or not MAILJET_CONFIG['SECRET_KEY']: |
|
|
return False, "Mailjet API credentials not configured" |
|
|
|
|
|
if not recipient_email or "@" not in recipient_email: |
|
|
return False, "Invalid email address" |
|
|
|
|
|
try: |
|
|
|
|
|
attachments = [] |
|
|
if result_image is not None and isinstance(result_image, np.ndarray): |
|
|
try: |
|
|
pil_image = Image.fromarray(result_image.astype('uint8')) |
|
|
img_buffer = io.BytesIO() |
|
|
pil_image.save(img_buffer, format='PNG') |
|
|
image_b64 = base64.b64encode(img_buffer.getvalue()).decode() |
|
|
|
|
|
attachments.append({ |
|
|
"ContentType": "image/png", |
|
|
"Filename": f"analysis_result_{original_filename}.png", |
|
|
"Base64Content": image_b64 |
|
|
}) |
|
|
print(f"β
Image attachment prepared: {len(image_b64)} characters") |
|
|
except Exception as img_error: |
|
|
print(f"β οΈ Warning: Could not prepare image attachment: {img_error}") |
|
|
|
|
|
|
|
|
|
|
|
html_content = f""" |
|
|
<!DOCTYPE html> |
|
|
<html> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<title>HEDI - Car Fraud Detection Analysis Report</title> |
|
|
<style> |
|
|
body {{ |
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
background-color: #f8f9fa; |
|
|
}} |
|
|
.email-container {{ |
|
|
max-width: 800px; |
|
|
margin: 20px auto; |
|
|
background-color: white; |
|
|
border-radius: 10px; |
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.1); |
|
|
overflow: hidden; |
|
|
}} |
|
|
.header {{ |
|
|
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); |
|
|
color: white; |
|
|
padding: 30px; |
|
|
text-align: center; |
|
|
}} |
|
|
.header h1 {{ |
|
|
margin: 0; |
|
|
font-size: 28px; |
|
|
font-weight: bold; |
|
|
}} |
|
|
.header p {{ |
|
|
margin: 10px 0 0 0; |
|
|
opacity: 0.95; |
|
|
font-size: 16px; |
|
|
}} |
|
|
.content {{ |
|
|
padding: 30px; |
|
|
}} |
|
|
.highlight {{ |
|
|
background-color: #e8f4f8; |
|
|
padding: 20px; |
|
|
border-radius: 8px; |
|
|
margin: 20px 0; |
|
|
border-left: 5px solid #2a5298; |
|
|
}} |
|
|
.results {{ |
|
|
margin: 25px 0; |
|
|
padding: 20px; |
|
|
background-color: #f8f9fa; |
|
|
border-radius: 8px; |
|
|
border-left: 5px solid #2a5298; |
|
|
}} |
|
|
.results h3 {{ |
|
|
color: #2a5298; |
|
|
margin-top: 0; |
|
|
font-size: 20px; |
|
|
}} |
|
|
.results pre {{ |
|
|
background-color: white; |
|
|
padding: 20px; |
|
|
border-radius: 8px; |
|
|
border: 1px solid #dee2e6; |
|
|
white-space: pre-wrap; |
|
|
font-size: 14px; |
|
|
line-height: 1.6; |
|
|
font-family: 'Courier New', monospace; |
|
|
}} |
|
|
.info-box {{ |
|
|
background-color: #f0f7ff; |
|
|
padding: 20px; |
|
|
border-radius: 8px; |
|
|
margin: 20px 0; |
|
|
border-left: 5px solid #2a5298; |
|
|
}} |
|
|
.footer {{ |
|
|
color: #6c757d; |
|
|
font-size: 14px; |
|
|
margin-top: 40px; |
|
|
text-align: center; |
|
|
padding: 30px; |
|
|
background-color: #f8f9fa; |
|
|
border-top: 1px solid #dee2e6; |
|
|
}} |
|
|
.cta-button {{ |
|
|
display: inline-block; |
|
|
background-color: #2a5298; |
|
|
color: white; |
|
|
padding: 12px 24px; |
|
|
text-decoration: none; |
|
|
border-radius: 6px; |
|
|
font-weight: bold; |
|
|
margin: 15px 0; |
|
|
}} |
|
|
.trusted-badge {{ |
|
|
background: linear-gradient(90deg, #28a745 0%, #2a5298 100%); |
|
|
color: white; |
|
|
padding: 15px; |
|
|
border-radius: 8px; |
|
|
text-align: center; |
|
|
margin: 20px 0; |
|
|
font-weight: bold; |
|
|
}} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="email-container"> |
|
|
<div class="header"> |
|
|
<h1>π‘οΈ HEDI - AI Fraud Detection</h1> |
|
|
<p>AI that detects fraud before it hurts you</p> |
|
|
<p>Analysis generated on {datetime.now().strftime('%d/%m/%Y at %H:%M:%S')}</p> |
|
|
</div> |
|
|
|
|
|
<div class="content"> |
|
|
<div class="trusted-badge"> |
|
|
π Trusted by Industry Leaders - AXA, Orange, Γcole polytechnique paris |
|
|
</div> |
|
|
|
|
|
<div class="info-box"> |
|
|
<h4>π File Details</h4> |
|
|
<p><strong>Original filename:</strong> {original_filename}</p> |
|
|
<p><strong>Analysis platform:</strong> HEDI AI Platform with Individual Quotas</p> |
|
|
<p><strong>Processing pipeline:</strong> Advanced multimodal AI</p> |
|
|
<p><strong>Processing time:</strong> {datetime.now().strftime('%d/%m/%Y at %H:%M:%S')}</p> |
|
|
</div> |
|
|
|
|
|
<div class="results"> |
|
|
<h3>π AI Analysis Results</h3> |
|
|
<pre>{analysis_text}</pre> |
|
|
</div> |
|
|
|
|
|
<div class="info-box"> |
|
|
<h4>π¦ Complete Report Package</h4> |
|
|
<p>A comprehensive analysis package is also available for download, including:</p> |
|
|
<ul> |
|
|
<li>Professional HTML report</li> |
|
|
<li>JSON data for integration</li> |
|
|
<li>Text summary</li> |
|
|
<li>Analyzed image with detection annotations</li> |
|
|
</ul> |
|
|
</div> |
|
|
|
|
|
<div style="text-align: center; margin: 30px 0;"> |
|
|
<a href="mailto:contact@askhedi.com" class="cta-button">Contact us for a demo</a> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="footer"> |
|
|
<p><strong>π’ Powered by HEDI - AI Fraud Detection Solutions</strong></p> |
|
|
<p>Professional fraud protection with multimodal AI</p> |
|
|
<p>π§ Contact: contact@askhedi.com | π Website: askhedi.com</p> |
|
|
<p>πͺ Individual usage tracking via browser cookies</p> |
|
|
</div> |
|
|
</div> |
|
|
</body> |
|
|
</html> |
|
|
""" |
|
|
|
|
|
|
|
|
auth_string = f"{MAILJET_CONFIG['API_KEY']}:{MAILJET_CONFIG['SECRET_KEY']}" |
|
|
auth_b64 = base64.b64encode(auth_string.encode()).decode() |
|
|
|
|
|
headers = { |
|
|
"Authorization": f"Basic {auth_b64}", |
|
|
"Content-Type": "application/json" |
|
|
} |
|
|
|
|
|
payload = { |
|
|
"Messages": [{ |
|
|
"From": { |
|
|
"Email": MAILJET_CONFIG['FROM_EMAIL'], |
|
|
"Name": MAILJET_CONFIG['FROM_NAME'] |
|
|
}, |
|
|
"To": [{ |
|
|
"Email": recipient_email |
|
|
}], |
|
|
"Subject": f"HEDI AI Analysis Results - {original_filename}", |
|
|
"HTMLPart": html_content, |
|
|
"Attachments": attachments |
|
|
}] |
|
|
} |
|
|
|
|
|
|
|
|
response = requests.post( |
|
|
MAILJET_CONFIG['URL'], |
|
|
headers=headers, |
|
|
json=payload, |
|
|
timeout=30 |
|
|
) |
|
|
|
|
|
if response.status_code == 200: |
|
|
response_data = response.json() |
|
|
if response_data.get('Messages') and len(response_data['Messages']) > 0: |
|
|
message_status = response_data['Messages'][0].get('Status') |
|
|
if message_status == 'success': |
|
|
print(f"β
Email sent successfully to {recipient_email}") |
|
|
return True, "Email sent successfully via Mailjet" |
|
|
else: |
|
|
print(f"β Email sending failed: {message_status}") |
|
|
return False, f"Email sending failed: {message_status}" |
|
|
else: |
|
|
print("β Unexpected email response format") |
|
|
return False, "Unexpected email response format" |
|
|
else: |
|
|
print(f"β Mailjet API error: {response.status_code}") |
|
|
return False, f"Email service error: {response.status_code}" |
|
|
|
|
|
except requests.exceptions.Timeout: |
|
|
print("β Email sending timeout") |
|
|
return False, "Email sending timeout" |
|
|
except Exception as e: |
|
|
print(f"β Email sending error: {e}") |
|
|
return False, f"Email sending error: {str(e)}" |
|
|
|
|
|
|
|
|
def test_mailjet_connection(): |
|
|
"""Test Mailjet API connection and configuration""" |
|
|
print("\nπ Testing Mailjet Configuration...") |
|
|
print(f"API Key: {MAILJET_CONFIG['API_KEY'][:8]}...{MAILJET_CONFIG['API_KEY'][-4:]}") |
|
|
print(f"From Email: {MAILJET_CONFIG['FROM_EMAIL']}") |
|
|
print(f"From Name: {MAILJET_CONFIG['FROM_NAME']}") |
|
|
|
|
|
try: |
|
|
|
|
|
auth_string = f"{MAILJET_CONFIG['API_KEY']}:{MAILJET_CONFIG['SECRET_KEY']}" |
|
|
auth_b64 = base64.b64encode(auth_string.encode()).decode() |
|
|
|
|
|
headers = { |
|
|
"Authorization": f"Basic {auth_b64}", |
|
|
"Content-Type": "application/json" |
|
|
} |
|
|
|
|
|
|
|
|
test_response = requests.get( |
|
|
"https://api.mailjet.com/v3/REST/sender", |
|
|
headers=headers, |
|
|
timeout=10 |
|
|
) |
|
|
|
|
|
if test_response.status_code == 200: |
|
|
print("β
Mailjet API connection successful") |
|
|
return True |
|
|
else: |
|
|
print(f"β Mailjet API test failed: {test_response.status_code}") |
|
|
return False |
|
|
|
|
|
except Exception as e: |
|
|
print(f"β Mailjet connection test error: {e}") |
|
|
return False |
|
|
|
|
|
|
|
|
def create_gradio_interface(): |
|
|
"""Interface Gradio avec gestion des cookies pour quota individuel""" |
|
|
|
|
|
|
|
|
custom_css = """ |
|
|
/* FORCE LIGHT MODE - Version corrigΓ©e */ |
|
|
|
|
|
/* Variables CSS globales */ |
|
|
:root { |
|
|
--background-fill-primary: #ffffff !important; |
|
|
--background-fill-secondary: #f8f9fa !important; |
|
|
--border-color-primary: #e5e7eb !important; |
|
|
--body-text-color: #000000 !important; |
|
|
--body-text-color-subdued: #374151 !important; |
|
|
--block-background-fill: #ffffff !important; |
|
|
--block-border-color: #e5e7eb !important; |
|
|
--input-background-fill: #ffffff !important; |
|
|
--input-border-color: #d1d5db !important; |
|
|
--input-text-color: #000000 !important; |
|
|
--button-primary-background-fill: #2563eb !important; |
|
|
--button-primary-text-color: #ffffff !important; |
|
|
--button-secondary-background-fill: #ffffff !important; |
|
|
--button-secondary-text-color: #000000 !important; |
|
|
--button-secondary-border-color: #d1d5db !important; |
|
|
} |
|
|
|
|
|
/* Force sur tous les Γ©lΓ©ments */ |
|
|
*, *::before, *::after { |
|
|
color-scheme: light !important; |
|
|
} |
|
|
|
|
|
/* Conteneurs principaux */ |
|
|
.gradio-container, |
|
|
body, |
|
|
.app, |
|
|
.main { |
|
|
background-color: #ffffff !important; |
|
|
color: #000000 !important; |
|
|
} |
|
|
|
|
|
/* Blocs et conteneurs */ |
|
|
.block, |
|
|
.gr-block, |
|
|
.gr-box, |
|
|
.gr-panel { |
|
|
background-color: #ffffff !important; |
|
|
color: #000000 !important; |
|
|
border-color: #e5e7eb !important; |
|
|
} |
|
|
|
|
|
/* Inputs et textareas */ |
|
|
.gr-textbox, |
|
|
.gr-textbox input, |
|
|
.gr-textbox textarea, |
|
|
input, |
|
|
textarea { |
|
|
background-color: #ffffff !important; |
|
|
color: #000000 !important; |
|
|
border-color: #d1d5db !important; |
|
|
} |
|
|
|
|
|
/* File upload */ |
|
|
.gr-file, |
|
|
.gr-file-upload, |
|
|
.file-upload { |
|
|
background-color: #ffffff !important; |
|
|
color: #000000 !important; |
|
|
border-color: #d1d5db !important; |
|
|
} |
|
|
|
|
|
/* Image upload area */ |
|
|
.image-upload, |
|
|
.gr-image, |
|
|
.gr-image .upload-container { |
|
|
background-color: #f8f9fa !important; |
|
|
color: #000000 !important; |
|
|
border-color: #d1d5db !important; |
|
|
} |
|
|
|
|
|
/* Dropzone styling */ |
|
|
.upload-container, |
|
|
.file-drop { |
|
|
background-color: #f8f9fa !important; |
|
|
color: #000000 !important; |
|
|
border: 2px dashed #d1d5db !important; |
|
|
} |
|
|
|
|
|
.upload-container:hover, |
|
|
.file-drop:hover { |
|
|
background-color: #f3f4f6 !important; |
|
|
border-color: #2563eb !important; |
|
|
} |
|
|
|
|
|
/* Text dans les upload areas */ |
|
|
.upload-text, |
|
|
.file-drop-text { |
|
|
color: #000000 !important; |
|
|
} |
|
|
|
|
|
/* Boutons */ |
|
|
.gr-button { |
|
|
background-color: #ffffff !important; |
|
|
color: #000000 !important; |
|
|
border: 1px solid #d1d5db !important; |
|
|
} |
|
|
|
|
|
.gr-button:hover { |
|
|
background-color: #f3f4f6 !important; |
|
|
} |
|
|
|
|
|
.gr-button-primary { |
|
|
background-color: #2563eb !important; |
|
|
color: #ffffff !important; |
|
|
border-color: #2563eb !important; |
|
|
} |
|
|
|
|
|
.gr-button-primary:hover { |
|
|
background-color: #1d4ed8 !important; |
|
|
} |
|
|
|
|
|
/* Labels et text */ |
|
|
label, |
|
|
.gr-label, |
|
|
.label, |
|
|
p, |
|
|
span, |
|
|
div { |
|
|
color: #000000 !important; |
|
|
} |
|
|
|
|
|
/* AccordΓ©ons et tabs */ |
|
|
.gr-accordion, |
|
|
.gr-tab-nav, |
|
|
.gr-tab { |
|
|
background-color: #ffffff !important; |
|
|
color: #000000 !important; |
|
|
border-color: #e5e7eb !important; |
|
|
} |
|
|
|
|
|
/* Sliders */ |
|
|
.gr-slider, |
|
|
.gr-slider input { |
|
|
background-color: #ffffff !important; |
|
|
color: #000000 !important; |
|
|
} |
|
|
|
|
|
/* Dropdowns */ |
|
|
.gr-dropdown, |
|
|
.gr-dropdown select { |
|
|
background-color: #ffffff !important; |
|
|
color: #000000 !important; |
|
|
border-color: #d1d5db !important; |
|
|
} |
|
|
|
|
|
/* Markdown et HTML content */ |
|
|
.gr-markdown, |
|
|
.gr-html { |
|
|
background-color: inherit !important; |
|
|
color: #000000 !important; |
|
|
} |
|
|
|
|
|
/* Pour les Γ©lΓ©ments spΓ©cifiques de votre app */ |
|
|
.status-display, |
|
|
.usage-display, |
|
|
.info-box { |
|
|
background-color: #ffffff !important; |
|
|
color: #000000 !important; |
|
|
border-color: #e5e7eb !important; |
|
|
} |
|
|
|
|
|
/* Force sur les Γ©lΓ©ments avec dark mode system */ |
|
|
@media (prefers-color-scheme: dark) { |
|
|
* { |
|
|
background-color: #ffffff !important; |
|
|
color: #000000 !important; |
|
|
} |
|
|
|
|
|
.gradio-container { |
|
|
background-color: #ffffff !important; |
|
|
color: #000000 !important; |
|
|
} |
|
|
|
|
|
input, textarea, select { |
|
|
background-color: #ffffff !important; |
|
|
color: #000000 !important; |
|
|
border-color: #d1d5db !important; |
|
|
} |
|
|
} |
|
|
|
|
|
/* Placeholder text */ |
|
|
::placeholder { |
|
|
color: #6b7280 !important; |
|
|
opacity: 0.8 !important; |
|
|
} |
|
|
|
|
|
/* Focus states */ |
|
|
input:focus, |
|
|
textarea:focus, |
|
|
select:focus { |
|
|
border-color: #2563eb !important; |
|
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1) !important; |
|
|
} |
|
|
""" + COOKIE_JAVASCRIPT |
|
|
|
|
|
with gr.Blocks( |
|
|
title="HEDI - AI Fraud Detection", |
|
|
theme=gr.themes.Soft( |
|
|
primary_hue="blue", |
|
|
secondary_hue="slate", |
|
|
neutral_hue="zinc" |
|
|
), |
|
|
css=custom_css |
|
|
) as app: |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<script type='text/javascript'> |
|
|
window.smartlook||(function(d) { |
|
|
var o=smartlook=function(){ o.api.push(arguments)},h=d.getElementsByTagName('head')[0]; |
|
|
var c=d.createElement('script');o.api=new Array();c.async=true;c.type='text/javascript'; |
|
|
c.charset='utf-8';c.src='https://web-sdk.smartlook.com/recorder.js';h.appendChild(c); |
|
|
})(document); |
|
|
smartlook('init', '4b047e7d1ebab6af323dcd34967e0fc05b3566ec', { region: 'eu' }); |
|
|
</script> |
|
|
""") |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div style="background: linear-gradient(90deg, #1e40af, #2563eb); color: white; padding: 20px; border-radius: 10px; margin-bottom: 20px; text-align: center;"> |
|
|
<h1 style="margin: 0; color: white;">π‘οΈ HEDI - AI Fraud Detection</h1> |
|
|
<p style="margin: 5px 0 0 0; color: white; opacity: 0.9;">Individual Quota System - Cookie-Based Tracking</p> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
usage_counter = gr.State(0) |
|
|
|
|
|
|
|
|
gr.HTML("""<h2 style="color: #000000 !important;">πΈ Upload & Email</h2>""") |
|
|
with gr.Row(equal_height=True): |
|
|
with gr.Column(): |
|
|
gr.HTML("""<h3 style="color: #000000 !important;">Upload Your Image</h3>""") |
|
|
input_image = gr.Image( |
|
|
type="numpy", |
|
|
label="", |
|
|
height=250, |
|
|
elem_classes="light-mode-image" |
|
|
) |
|
|
|
|
|
with gr.Column(): |
|
|
gr.HTML("""<h3 style="color: #000000 !important;">π§ Email Delivery</h3>""") |
|
|
recipient_email = gr.Textbox( |
|
|
label="Your Email", |
|
|
placeholder="your.email@company.com", |
|
|
elem_classes="light-mode-input" |
|
|
) |
|
|
|
|
|
status_display = gr.HTML(""" |
|
|
<div style="background: #f9fafb; padding: 20px; border-radius: 8px; border: 1px solid #e5e7eb; margin-top: 10px; color: #000000 !important;"> |
|
|
<div style="display: flex; align-items: center; margin-bottom: 12px;"> |
|
|
<span style="font-size: 18px; margin-right: 8px;">π</span> |
|
|
<strong style="color: #000000 !important;">Analysis Status</strong> |
|
|
</div> |
|
|
<div style="color: #6b7280 !important; text-align: center;"> |
|
|
<div style="color: #000000 !important;">Ready to analyze your image...</div> |
|
|
<div style="color: #6b7280 !important; font-size: 14px; margin-top: 8px;">Upload an image and click Analyze</div> |
|
|
</div> |
|
|
</div> |
|
|
""") |
|
|
gr.HTML(""" |
|
|
<div style="background: #f0fdf4; padding: 15px; border-radius: 8px; margin-top: 10px; border-left: 4px solid #22c55e; color: #000000 !important;"> |
|
|
<strong style="color: #000000 !important;">π¬ You'll receive:</strong> Complete analysis report, annotated images, and risk assessment |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
gr.HTML("") |
|
|
with gr.Row(): |
|
|
analyze_btn = gr.Button( |
|
|
"π Analyze with HEDI AI", |
|
|
variant="primary", |
|
|
size="lg", |
|
|
elem_classes="hedi-btn-primary", |
|
|
scale=2 |
|
|
) |
|
|
clear_btn = gr.Button( |
|
|
"ποΈ Clear", |
|
|
variant="secondary", |
|
|
scale=1 |
|
|
) |
|
|
|
|
|
|
|
|
debug_info = gr.HTML("") |
|
|
|
|
|
|
|
|
with gr.Row(equal_height=True): |
|
|
with gr.Column(): |
|
|
gr.HTML("""<h3 style="color: #000000 !important;">π Individual Usage (Cookies)</h3>""") |
|
|
usage_display = gr.HTML(get_usage_display_html(0)) |
|
|
|
|
|
with gr.Column(): |
|
|
gr.HTML("""<h3 style="color: #000000 !important;">β±οΈ Processing Monitor</h3>""") |
|
|
gr.HTML(""" |
|
|
<div style="background: #f0f9ff; padding: 20px; border-radius: 8px; border: 1px solid #bfdbfe; color: #000000 !important;"> |
|
|
<div style="display: flex; align-items: center; margin-bottom: 12px;"> |
|
|
<span style="font-size: 18px; margin-right: 8px;">π</span> |
|
|
<strong style="color: #000000 !important;">Processing Timing</strong> |
|
|
</div> |
|
|
<div style="color: #374151 !important; font-size: 14px;"> |
|
|
β’ <strong style="color: #000000 !important;">Stage 1:</strong> Damage Detection (15-25s)<br> |
|
|
β’ <strong style="color: #000000 !important;">Stage 2:</strong> AI Detection (10-15s)<br> |
|
|
β’ <strong style="color: #000000 !important;">Email Delivery:</strong> 5-10s<br> |
|
|
β’ <strong style="color: #000000 !important;">Total Average:</strong> 30-60 seconds |
|
|
</div> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
gr.HTML("""<h2 style="color: #000000 !important;">π± What You'll Receive</h2>""") |
|
|
gr.HTML(""" |
|
|
<div style="background: white; border: 1px solid #e5e7eb; padding: 20px; border-radius: 8px; color: #000000 !important;"> |
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;"> |
|
|
<div style="text-align: center; padding: 15px;"> |
|
|
<div style="font-size: 24px; margin-bottom: 8px;">π§</div> |
|
|
<h4 style="margin: 0; color: #000000 !important;">Email Report</h4> |
|
|
<p style="font-size: 14px; color: #6b7280; margin: 5px 0;">Complete analysis with AI findings</p> |
|
|
</div> |
|
|
<div style="text-align: center; padding: 15px;"> |
|
|
<div style="font-size: 24px; margin-bottom: 8px;">πΌοΈ</div> |
|
|
<h4 style="margin: 0; color: #000000 !important;">Annotated Images</h4> |
|
|
<p style="font-size: 14px; color: #6b7280; margin: 5px 0;">Visual damage detection results</p> |
|
|
</div> |
|
|
<div style="text-align: center; padding: 15px;"> |
|
|
<div style="font-size: 24px; margin-bottom: 8px;">π‘οΈ</div> |
|
|
<h4 style="margin: 0; color: #000000 !important;">Risk Assessment</h4> |
|
|
<p style="font-size: 14px; color: #6b7280; margin: 5px 0;">Fraud probability and recommendations</p> |
|
|
</div> |
|
|
<div style="text-align: center; padding: 15px;"> |
|
|
<div style="font-size: 24px; margin-bottom: 8px;">π</div> |
|
|
<h4 style="margin: 0; color: #000000 !important;">Professional Report</h4> |
|
|
<p style="font-size: 14px; color: #6b7280; margin: 5px 0;">PDF and JSON formats</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
with gr.Accordion("βοΈ Advanced Settings", open=False): |
|
|
with gr.Row(): |
|
|
damage_threshold = gr.Slider( |
|
|
minimum=0.1, maximum=0.95, value=0.7, step=0.05, |
|
|
label="π Damage Detection Sensitivity", |
|
|
elem_classes="light-mode-slider" |
|
|
) |
|
|
ai_detection_threshold = gr.Slider( |
|
|
minimum=0.1, maximum=0.9, value=0.5, step=0.05, |
|
|
label="π€ AI Detection Sensitivity", |
|
|
elem_classes="light-mode-slider" |
|
|
) |
|
|
device = gr.Dropdown( |
|
|
choices=["cpu", "auto"], |
|
|
value="cpu", |
|
|
label="Processing Mode", |
|
|
visible=False |
|
|
) |
|
|
|
|
|
|
|
|
download_file = gr.File(label="Download", visible=False) |
|
|
download_info = gr.Markdown("", visible=False) |
|
|
output_text = gr.Markdown("", visible=False) |
|
|
|
|
|
|
|
|
with gr.Tab("π How It Works"): |
|
|
gr.HTML(""" |
|
|
<div style="color: #000000 !important;"> |
|
|
<h2 style="color: #000000 !important;">π€ Analysis Process</h2> |
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 20px 0;"> |
|
|
<div style="background: #f0f9ff; padding: 20px; border-radius: 10px; border: 1px solid #bfdbfe; color: #000000 !important;"> |
|
|
<h3 style="color: #000000 !important;">1. π Damage Detection</h3> |
|
|
<ul style="color: #000000 !important;"> |
|
|
<li>β Advanced computer vision scanning</li> |
|
|
<li>β Damage area identification</li> |
|
|
<li>β Confidence scoring</li> |
|
|
<li>β Damage type classification</li> |
|
|
</ul> |
|
|
</div> |
|
|
<div style="background: #faf5ff; padding: 20px; border-radius: 10px; border: 1px solid #c4b5fd; color: #000000 !important;"> |
|
|
<h3 style="color: #000000 !important;">2. π€ AI Detection </h3> |
|
|
<ul style="color: #000000 !important;"> |
|
|
<li>β AI-generated image detection</li> |
|
|
<li>β Fraud prevention</li> |
|
|
</ul> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
with gr.Tab("β Help & Support"): |
|
|
gr.HTML(""" |
|
|
<div style="color: #000000 !important;"> |
|
|
<h2 style="color: #000000 !important;">π Quick Start Guide</h2> |
|
|
<div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 15px; margin: 20px 0;"> |
|
|
<div style="text-align: center; padding: 15px; background: #f8fafc; border-radius: 8px; color: #000000 !important;"> |
|
|
<div style="font-size: 30px; margin-bottom: 10px;">πΈ</div> |
|
|
<h4 style="color: #000000 !important;">1. Upload</h4> |
|
|
<p style="font-size: 12px; color: #6b7280;">Add your image</p> |
|
|
</div> |
|
|
<div style="text-align: center; padding: 15px; background: #f8fafc; border-radius: 8px; color: #000000 !important;"> |
|
|
<div style="font-size: 30px; margin-bottom: 10px;">π§</div> |
|
|
<h4 style="color: #000000 !important;">2. Email</h4> |
|
|
<p style="font-size: 12px; color: #6b7280;">Enter email</p> |
|
|
</div> |
|
|
<div style="text-align: center; padding: 15px; background: #f8fafc; border-radius: 8px; color: #000000 !important;"> |
|
|
<div style="font-size: 30px; margin-bottom: 10px;">π</div> |
|
|
<h4 style="color: #000000 !important;">3. Analyze</h4> |
|
|
<p style="font-size: 12px; color: #6b7280;">Click analyze</p> |
|
|
</div> |
|
|
<div style="text-align: center; padding: 15px; background: #f8fafc; border-radius: 8px; color: #000000 !important;"> |
|
|
<div style="font-size: 30px; margin-bottom: 10px;">π</div> |
|
|
<h4 style="color: #000000 !important;">4. Review</h4> |
|
|
<p style="font-size: 12px; color: #6b7280;">Check results</p> |
|
|
</div> |
|
|
<div style="text-align: center; padding: 15px; background: #f8fafc; border-radius: 8px; color: #000000 !important;"> |
|
|
<div style="font-size: 30px; margin-bottom: 10px;">π</div> |
|
|
<h4 style="color: #000000 !important;">5. Download</h4> |
|
|
<p style="font-size: 12px; color: #6b7280;">Get report</p> |
|
|
</div> |
|
|
</div> |
|
|
<div style="background: #ecfdf5; border: 1px solid #a7f3d0; padding: 16px; border-radius: 12px; margin-top: 20px; color: #000000 !important;"> |
|
|
<h3 style="color: #059669; display: flex; align-items: center; margin-bottom: 12px;"><span style="margin-right: 12px;">πͺ</span>Cookie-Based Individual Quotas</h3> |
|
|
<div style="color: #047857;"> |
|
|
<p>β’ <strong>Individual tracking:</strong> Each user has their own 10-analysis daily quota</p> |
|
|
<p>β’ <strong>Daily reset:</strong> Automatically resets at midnight local time</p> |
|
|
<p>β’ <strong>Privacy-first:</strong> Data stored locally in your browser only</p> |
|
|
<p>β’ <strong>Cross-session:</strong> Quota persists between browser sessions</p> |
|
|
<p>β’ <strong>No registration:</strong> No account needed, just cookies</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div style="background: #fff7ed; border: 1px solid #fed7aa; padding: 16px; border-radius: 12px; margin-top: 20px; color: #000000 !important;"> |
|
|
<h3 style="color: #ea580c; display: flex; align-items: center; margin-bottom: 12px;"><span style="margin-right: 12px;">π§</span>Troubleshooting & Debug</h3> |
|
|
<div style="color: #c2410c;"> |
|
|
<p>β’ <strong>Test Cookies button:</strong> Click to verify JavaScript functions are working</p> |
|
|
<p>β’ <strong>Browser Console:</strong> Press F12 and check Console tab for cookie debugging info</p> |
|
|
<p>β’ <strong>Debug info:</strong> Green area below buttons shows function call status</p> |
|
|
<p>β’ <strong>If nothing happens:</strong> Check console for errors, try refreshing page</p> |
|
|
<p>β’ <strong>Cookie issues:</strong> Clear browser cookies for this site and try again</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
def test_javascript_cookies(): |
|
|
"""Fonction pour tester les cookies JavaScript""" |
|
|
return """ |
|
|
<div style="background: #f0f9ff; padding: 15px; border-radius: 8px; border: 1px solid #bfdbfe; color: #000000 !important;"> |
|
|
<h4 style="color: #000000 !important;">πͺ JavaScript Cookie Test</h4> |
|
|
<p>Check browser console (F12) for cookie debugging info.</p> |
|
|
<p>This test verifies that JavaScript functions are working.</p> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
def update_interface(*args): |
|
|
try: |
|
|
image, damage_thresh, deepfake_thresh, device_val, current_usage, email = args |
|
|
|
|
|
print( |
|
|
f"π― update_interface called with args: image={image is not None}, usage={current_usage}, email={bool(email)}") |
|
|
|
|
|
if image is None: |
|
|
return [ |
|
|
"""<div style="background: #fef2f2; padding: 20px; border-radius: 8px; border: 1px solid #fecaca; margin-top: 10px; color: #000000 !important;"> |
|
|
<div style="display: flex; align-items: center; margin-bottom: 12px;"> |
|
|
<span style="font-size: 18px; margin-right: 8px;">β</span> |
|
|
<strong style="color: #000000 !important;">Analysis Status</strong> |
|
|
</div> |
|
|
<div style="color: #dc2626; text-align: center;"> |
|
|
<div><strong>No image uploaded</strong></div> |
|
|
<div style="font-size: 14px; margin-top: 8px;">Please upload an image first</div> |
|
|
</div> |
|
|
</div>""", |
|
|
current_usage, |
|
|
gr.update(visible=False), |
|
|
"", |
|
|
"", |
|
|
get_usage_display_html(current_usage), |
|
|
f"<div style='color: green;'>β
Function called successfully with usage: {current_usage}</div>" |
|
|
] |
|
|
|
|
|
if not email: |
|
|
return [ |
|
|
"""<div style="background: #fef2f2; padding: 20px; border-radius: 8px; border: 1px solid #fecaca; margin-top: 10px; color: #000000 !important;"> |
|
|
<div style="display: flex; align-items: center; margin-bottom: 12px;"> |
|
|
<span style="font-size: 18px; margin-right: 8px;">β</span> |
|
|
<strong style="color: #000000 !important;">Analysis Status</strong> |
|
|
</div> |
|
|
<div style="color: #dc2626; text-align: center;"> |
|
|
<div><strong>Email required</strong></div> |
|
|
<div style="font-size: 14px; margin-top: 8px;">Please enter your email address</div> |
|
|
</div> |
|
|
</div>""", |
|
|
current_usage, |
|
|
gr.update(visible=False), |
|
|
"", |
|
|
"", |
|
|
get_usage_display_html(current_usage), |
|
|
f"<div style='color: orange;'>β οΈ Email missing. Usage: {current_usage}</div>" |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if current_usage >= MAX_TRIES: |
|
|
return [ |
|
|
"""<div style="background: #fef2f2; padding: 20px; border-radius: 8px; border: 1px solid #fecaca; margin-top: 10px; color: #000000 !important;"> |
|
|
<div style="display: flex; align-items: center; margin-bottom: 12px;"> |
|
|
<span style="font-size: 18px; margin-right: 8px;">β οΈ</span> |
|
|
<strong style="color: #000000 !important;">Analysis Status</strong> |
|
|
</div> |
|
|
<div style="color: #dc2626; text-align: center;"> |
|
|
<div><strong>Daily limit reached!</strong></div> |
|
|
<div style="font-size: 14px; margin-top: 8px;">Maximum 10 analyses per day</div> |
|
|
<div style="font-size: 12px; margin-top: 4px; opacity: 0.8;">Resets tomorrow | Contact sales@askhedi.fr for extended access</div> |
|
|
</div> |
|
|
</div>""", |
|
|
current_usage, |
|
|
gr.update(visible=False), |
|
|
"", |
|
|
"", |
|
|
get_usage_display_html(current_usage), |
|
|
f"<div style='color: red;'>β Usage limit reached: {current_usage}/{MAX_TRIES}</div>" |
|
|
] |
|
|
|
|
|
print(f"π Starting analysis process...") |
|
|
|
|
|
|
|
|
analysis_text, new_usage_count, status_message, download_path = process_image_sequential( |
|
|
image, damage_thresh, deepfake_thresh, device_val, current_usage, email |
|
|
) |
|
|
|
|
|
print(f"β
Analysis completed. New usage: {new_usage_count}") |
|
|
|
|
|
|
|
|
if "β
" in status_message or "sent via Mailjet" in status_message: |
|
|
success_status = """<div style="background: #f0fdf4; padding: 20px; border-radius: 8px; border: 1px solid #bbf7d0; margin-top: 10px; color: #000000 !important;"> |
|
|
<div style="display: flex; align-items: center; margin-bottom: 12px;"> |
|
|
<span style="font-size: 18px; margin-right: 8px;">β
</span> |
|
|
<strong style="color: #000000 !important;">Analysis Status</strong> |
|
|
</div> |
|
|
<div style="color: #166534; text-align: center;"> |
|
|
<div><strong>Analysis Complete!</strong></div> |
|
|
<div style="font-size: 14px; margin-top: 8px;">Results have been sent to your email</div> |
|
|
<div style="font-size: 12px; margin-top: 4px; opacity: 0.8;">Check your inbox and spam folder</div> |
|
|
<div style="margin-top: 10px; padding: 8px; background: rgba(255,255,255,0.3); border-radius: 4px; font-size: 12px;"> |
|
|
πͺ Individual usage updated in cookies |
|
|
</div> |
|
|
</div> |
|
|
</div>""" |
|
|
|
|
|
return [ |
|
|
success_status, |
|
|
new_usage_count, |
|
|
gr.update(value=download_path, visible=bool(download_path)), |
|
|
"", |
|
|
analysis_text, |
|
|
get_usage_display_html(new_usage_count), |
|
|
f"<div style='color: green;'>β
Analysis successful! Usage: {new_usage_count}/{MAX_TRIES}</div>" |
|
|
] |
|
|
else: |
|
|
|
|
|
error_status = f"""<div style="background: #fef2f2; padding: 20px; border-radius: 8px; border: 1px solid #fecaca; margin-top: 10px; color: #000000 !important;"> |
|
|
<div style="display: flex; align-items: center; margin-bottom: 12px;"> |
|
|
<span style="font-size: 18px; margin-right: 8px;">β</span> |
|
|
<strong style="color: #000000 !important;">Analysis Status</strong> |
|
|
</div> |
|
|
<div style="color: #dc2626; text-align: center;"> |
|
|
<div><strong>Analysis Failed</strong></div> |
|
|
<div style="font-size: 14px; margin-top: 8px;">{status_message}</div> |
|
|
</div> |
|
|
</div>""" |
|
|
|
|
|
return [ |
|
|
error_status, |
|
|
new_usage_count, |
|
|
gr.update(visible=False), |
|
|
"", |
|
|
analysis_text, |
|
|
get_usage_display_html(new_usage_count), |
|
|
f"<div style='color: red;'>β Analysis failed: {status_message}</div>" |
|
|
] |
|
|
|
|
|
except Exception as e: |
|
|
print(f"β Error in update_interface: {e}") |
|
|
import traceback |
|
|
traceback.print_exc() |
|
|
|
|
|
error_status = f"""<div style="background: #fef2f2; padding: 20px; border-radius: 8px; border: 1px solid #fecaca; margin-top: 10px; color: #000000 !important;"> |
|
|
<div style="display: flex; align-items: center; margin-bottom: 12px;"> |
|
|
<span style="font-size: 18px; margin-right: 8px;">β</span> |
|
|
<strong style="color: #000000 !important;">Analysis Status</strong> |
|
|
</div> |
|
|
<div style="color: #dc2626; text-align: center;"> |
|
|
<div><strong>Unexpected Error</strong></div> |
|
|
<div style="font-size: 14px; margin-top: 8px;">{str(e)}</div> |
|
|
</div> |
|
|
</div>""" |
|
|
|
|
|
return [ |
|
|
error_status, |
|
|
current_usage, |
|
|
gr.update(visible=False), |
|
|
"", |
|
|
f"Error: {str(e)}", |
|
|
get_usage_display_html(current_usage), |
|
|
f"<div style='color: red;'>β Exception: {str(e)}</div>" |
|
|
] |
|
|
|
|
|
def clear_interface(): |
|
|
return [ |
|
|
"""<div style="background: #f9fafb; padding: 20px; border-radius: 8px; border: 1px solid #e5e7eb; margin-top: 10px; color: #000000 !important;"> |
|
|
<div style="display: flex; align-items: center; margin-bottom: 12px;"> |
|
|
<span style="font-size: 18px; margin-right: 8px;">π</span> |
|
|
<strong style="color: #000000 !important;">Analysis Status</strong> |
|
|
</div> |
|
|
<div style="color: #6b7280; text-align: center;"> |
|
|
<div style="color: #000000 !important;">Ready to analyze your image...</div> |
|
|
<div style="color: #6b7280; font-size: 14px; margin-top: 8px;">Upload an image and click Analyze</div> |
|
|
</div> |
|
|
</div>""", |
|
|
0, |
|
|
gr.update(visible=False), |
|
|
"", |
|
|
"", |
|
|
get_usage_display_html(0), |
|
|
"<div style='color: blue;'>π Interface cleared</div>", |
|
|
"" |
|
|
] |
|
|
|
|
|
|
|
|
def handle_analyze_click(image, damage_thresh, deepfake_thresh, device_val, current_usage, email): |
|
|
|
|
|
print(f"π― Analyze button clicked!") |
|
|
print(f"π Current inputs: image={image is not None}, usage={current_usage}, email={bool(email)}") |
|
|
return update_interface(image, damage_thresh, deepfake_thresh, device_val, current_usage, email) |
|
|
|
|
|
analyze_btn.click( |
|
|
fn=handle_analyze_click, |
|
|
inputs=[input_image, damage_threshold, ai_detection_threshold, device, usage_counter, recipient_email], |
|
|
outputs=[status_display, usage_counter, download_file, download_info, output_text, usage_display, |
|
|
debug_info] |
|
|
) |
|
|
|
|
|
clear_btn.click( |
|
|
fn=clear_interface, |
|
|
outputs=[status_display, usage_counter, download_file, download_info, output_text, usage_display, |
|
|
debug_info, recipient_email] |
|
|
) |
|
|
|
|
|
|
|
|
app.load( |
|
|
fn=lambda: [0, get_usage_display_html(0), "<div ></div>"], |
|
|
outputs=[usage_counter, usage_display, debug_info] |
|
|
) |
|
|
|
|
|
return app |
|
|
|
|
|
|
|
|
def setup_device(device_str): |
|
|
"""Set up computation device""" |
|
|
if device_str == 'auto': |
|
|
if torch.cuda.is_available(): |
|
|
return torch.device('cuda:0') |
|
|
elif hasattr(torch, 'backends') and hasattr(torch.backends, 'mps') and torch.backends.mps.is_available(): |
|
|
return torch.device('mps') |
|
|
else: |
|
|
return torch.device('cpu') |
|
|
elif device_str == 'cuda' and torch.cuda.is_available(): |
|
|
return torch.device('cuda:0') |
|
|
elif device_str == 'mps' and hasattr(torch, 'backends') and hasattr(torch.backends, |
|
|
'mps') and torch.backends.mps.is_available(): |
|
|
return torch.device('mps') |
|
|
else: |
|
|
return torch.device('cpu') |
|
|
|
|
|
|
|
|
def load_detectron2_damage_model(model_path, device): |
|
|
"""Load fine-tuned Detectron2 model for damage detection (Stage 1)""" |
|
|
if not DETECTRON2_AVAILABLE: |
|
|
print("β Detectron2 not available") |
|
|
return None |
|
|
|
|
|
if model_path is None or not os.path.exists(model_path): |
|
|
print(f"β Damage model not found at: {model_path}") |
|
|
return None |
|
|
|
|
|
try: |
|
|
cfg = get_cfg() |
|
|
cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml")) |
|
|
cfg.MODEL.WEIGHTS = model_path |
|
|
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.5 |
|
|
cfg.MODEL.DEVICE = str(device) |
|
|
|
|
|
|
|
|
cfg.MODEL.ROI_HEADS.NUM_CLASSES = 1 |
|
|
|
|
|
predictor = DefaultPredictor(cfg) |
|
|
print("β
Detectron2 damage detection model loaded successfully") |
|
|
return predictor |
|
|
except Exception as e: |
|
|
print(f"β Error loading Detectron2 model: {e}") |
|
|
return None |
|
|
|
|
|
|
|
|
def initialize_radiov3_model(): |
|
|
"""Initialize the model for feature extraction""" |
|
|
global radio_l_image_processor, radio_l_model |
|
|
|
|
|
|
|
|
if radio_l_image_processor is not None and radio_l_model is not None: |
|
|
print("β
C model already loaded, reusing...") |
|
|
return True |
|
|
|
|
|
try: |
|
|
print("π Loading model C...") |
|
|
hf_repo = os.getenv('MODEL_REPO', 'fallback') |
|
|
radio_l_image_processor = CLIPImageProcessor.from_pretrained(hf_repo) |
|
|
radio_l_model = AutoModel.from_pretrained(hf_repo, trust_remote_code=True) |
|
|
radio_l_model = radio_l_model.to(RADIO_DEVICE) |
|
|
radio_l_model.eval() |
|
|
print("β
C model loaded successfully") |
|
|
return True |
|
|
except Exception as e: |
|
|
print(f"β Error loading model: {e}") |
|
|
return False |
|
|
|
|
|
|
|
|
def extract_radio_l_features(image): |
|
|
"""Extract C features from a PIL image with 224x224 resize""" |
|
|
global radio_l_image_processor, radio_l_model |
|
|
|
|
|
if radio_l_image_processor is None or radio_l_model is None: |
|
|
raise Exception("C model not initialized") |
|
|
|
|
|
|
|
|
if isinstance(image, np.ndarray): |
|
|
image = Image.fromarray(image.astype('uint8')) |
|
|
|
|
|
image = image.resize((224, 224)) |
|
|
|
|
|
pixel_values = radio_l_image_processor(images=image, return_tensors='pt', do_resize=True).pixel_values |
|
|
pixel_values = pixel_values.to(RADIO_DEVICE) |
|
|
|
|
|
with torch.no_grad(): |
|
|
summary, features = radio_l_model(pixel_values) |
|
|
features = features.detach().flatten() |
|
|
features = F.normalize(features, p=2, dim=-1).cpu().flatten() |
|
|
|
|
|
return features.numpy() |
|
|
|
|
|
|
|
|
def load_ai_detection_classifier(model_path): |
|
|
"""Load the AI detection (Stage 2)""" |
|
|
global ai_detection_classifier |
|
|
|
|
|
if model_path is None or not os.path.exists(model_path): |
|
|
print(f"β AI detection model not found at: {model_path}") |
|
|
return None |
|
|
|
|
|
try: |
|
|
ai_detection_classifier = joblib.load(model_path) |
|
|
print("β
V1.pkl AI detection classifier loaded successfully") |
|
|
return ai_detection_classifier |
|
|
except Exception as e: |
|
|
print(f"β Error loading V1.pkl classifier: {e}") |
|
|
return None |
|
|
|
|
|
|
|
|
def simulate_damage_detection(image): |
|
|
"""Simulate damage detection when Zone model is not available""" |
|
|
import random |
|
|
import hashlib |
|
|
|
|
|
|
|
|
if isinstance(image, np.ndarray): |
|
|
|
|
|
img_hash = hashlib.md5(image.tobytes()).hexdigest() |
|
|
seed = int(img_hash[:8], 16) % 1000 |
|
|
random.seed(seed) |
|
|
|
|
|
h, w = image.shape[:2] |
|
|
num_damages = random.randint(1, 3) |
|
|
|
|
|
damages = [] |
|
|
for i in range(num_damages): |
|
|
|
|
|
x1 = random.randint(0, w // 2) |
|
|
y1 = random.randint(0, h // 2) |
|
|
x2 = x1 + random.randint(w // 6, w // 3) |
|
|
y2 = y1 + random.randint(h // 6, h // 3) |
|
|
|
|
|
|
|
|
x2 = min(x2, w - 1) |
|
|
y2 = min(y2, h - 1) |
|
|
|
|
|
confidence = random.uniform(0.6, 0.95) |
|
|
damage_type = random.choice(["Scratch", "Dent", "Crack", "Paint Damage"]) |
|
|
|
|
|
damages.append({ |
|
|
"bbox": [x1, y1, x2, y2], |
|
|
"confidence": confidence, |
|
|
"type": damage_type, |
|
|
"area": (x2 - x1) * (y2 - y1) |
|
|
}) |
|
|
|
|
|
return { |
|
|
"damages": damages, |
|
|
"total_damages": len(damages), |
|
|
"demo_mode": True |
|
|
} |
|
|
else: |
|
|
|
|
|
return { |
|
|
"damages": [{"bbox": [100, 100, 200, 200], "confidence": 0.85, "type": "Dent", "area": 10000}], |
|
|
"total_damages": 1, |
|
|
"demo_mode": True |
|
|
} |
|
|
|
|
|
|
|
|
def simulate_ai_detection(image, threshold=0.5): |
|
|
"""Simulate AI detection analysis when real model is not available""" |
|
|
import random |
|
|
import hashlib |
|
|
|
|
|
|
|
|
if isinstance(image, np.ndarray): |
|
|
|
|
|
img_hash = hashlib.md5(image.tobytes()).hexdigest() |
|
|
seed = int(img_hash[:8], 16) % 1000 |
|
|
random.seed(seed) |
|
|
|
|
|
|
|
|
ai_prob = random.uniform(0.1, 0.9) |
|
|
real_prob = 1.0 - ai_prob |
|
|
is_ai = ai_prob > threshold |
|
|
|
|
|
return { |
|
|
"ai_prob": ai_prob, |
|
|
"real_prob": real_prob, |
|
|
"is_ai": is_ai, |
|
|
"prediction": 1 if is_ai else 0, |
|
|
"confidence": "HIGH" if abs(ai_prob - 0.5) > 0.3 else "MEDIUM" if abs(ai_prob - 0.5) > 0.15 else "LOW", |
|
|
"demo_mode": True |
|
|
} |
|
|
else: |
|
|
|
|
|
return { |
|
|
"ai_prob": 0.3, |
|
|
"real_prob": 0.7, |
|
|
"is_ai": False, |
|
|
"prediction": 0, |
|
|
"confidence": "MEDIUM", |
|
|
"demo_mode": True |
|
|
} |
|
|
|
|
|
|
|
|
def check_model_paths(damage_path, deepfake_path): |
|
|
"""Check if model paths are valid and exist""" |
|
|
output = ["## Path Verification Results\n"] |
|
|
|
|
|
|
|
|
if huggingface_model_path and os.path.exists(huggingface_model_path): |
|
|
file_size = os.path.getsize(huggingface_model_path) / (1024 * 1024) |
|
|
output.append(f"β
**Hugging Face Model:** Found at {huggingface_model_path} ({file_size:.2f} MB)") |
|
|
|
|
|
|
|
|
if os.path.exists(damage_path): |
|
|
file_size = os.path.getsize(damage_path) / (1024 * 1024) |
|
|
output.append(f"β
**Damage model:** Found at {damage_path} ({file_size:.2f} MB)") |
|
|
else: |
|
|
output.append(f"β **Damage model:** NOT found at {damage_path}") |
|
|
|
|
|
|
|
|
if os.path.exists(deepfake_path): |
|
|
file_size = os.path.getsize(deepfake_path) / (1024 * 1024) |
|
|
output.append(f"β
**Deepfake model:** Found at {deepfake_path} ({file_size:.2f} MB)") |
|
|
else: |
|
|
if huggingface_model_path and os.path.exists(huggingface_model_path): |
|
|
output.append(f"β οΈ **Deepfake model:** NOT found at {deepfake_path}, but will use downloaded model instead") |
|
|
else: |
|
|
output.append(f"β **Deepfake model:** NOT found at {deepfake_path}") |
|
|
|
|
|
return "\n".join(output) |
|
|
|
|
|
|
|
|
|
|
|
def validate_email(email): |
|
|
"""Validate email format""" |
|
|
import re |
|
|
if not email or "@" not in email: |
|
|
return False, "Invalid email format" |
|
|
|
|
|
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' |
|
|
if re.match(email_pattern, email): |
|
|
return True, "Valid email" |
|
|
else: |
|
|
return False, "Invalid email format" |
|
|
|
|
|
|
|
|
def process_image_sequential(input_image, damage_threshold, ai_detection_threshold, device_str, usage_count, |
|
|
recipient_email): |
|
|
print(f"π process_image_sequential called!") |
|
|
print( |
|
|
f"π Parameters: image={input_image is not None}, threshold_damage={damage_threshold}, ai_detection_threshold={ai_detection_threshold}") |
|
|
print(f"π§ Email: {recipient_email}, Usage: {usage_count}") |
|
|
|
|
|
|
|
|
if usage_count is None: |
|
|
usage_count = 0 |
|
|
print(f"β οΈ Usage count was None, set to 0") |
|
|
|
|
|
try: |
|
|
usage_count = int(usage_count) |
|
|
except (TypeError, ValueError): |
|
|
print(f"β οΈ Could not convert usage_count to int: {usage_count}, defaulting to 0") |
|
|
usage_count = 0 |
|
|
|
|
|
usage_count = usage_count + 1 |
|
|
print(f"π Incremented usage count to: {usage_count}") |
|
|
|
|
|
progress_info = [] |
|
|
progress_info.append(f"π Individual Usage: {usage_count}/{MAX_TRIES}") |
|
|
|
|
|
email_valid, email_message = validate_email(recipient_email) |
|
|
if not email_valid: |
|
|
return ( |
|
|
email_message + "\n\nPlease provide a valid email address to receive your analysis results.", |
|
|
usage_count - 1, |
|
|
email_message, |
|
|
None |
|
|
) |
|
|
|
|
|
|
|
|
if usage_count > MAX_TRIES: |
|
|
return ( |
|
|
f"β οΈ Daily usage limit reached ({MAX_TRIES} tries maximum).\n\nYour quota will reset tomorrow. To continue using this service immediately, please contact sales@askhedi.fr", |
|
|
usage_count, |
|
|
"β Daily usage limit reached", |
|
|
None |
|
|
) |
|
|
|
|
|
|
|
|
try: |
|
|
if input_image is None: |
|
|
return "β Please upload an image to analyze.", usage_count, "β No image provided", None |
|
|
|
|
|
|
|
|
if isinstance(input_image, dict) and "path" in input_image: |
|
|
img = cv2.imread(input_image["path"]) |
|
|
original_filename = os.path.basename(input_image["path"]) |
|
|
elif isinstance(input_image, str): |
|
|
img = cv2.imread(input_image) |
|
|
original_filename = os.path.basename(input_image) |
|
|
elif isinstance(input_image, np.ndarray): |
|
|
img = input_image.copy() |
|
|
if len(img.shape) == 3 and img.shape[2] == 3: |
|
|
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) |
|
|
original_filename = "uploaded_image" |
|
|
else: |
|
|
return ( |
|
|
"β Unsupported image format", |
|
|
usage_count, |
|
|
"β Invalid format", |
|
|
None |
|
|
) |
|
|
|
|
|
if img is None: |
|
|
return ( |
|
|
"β Could not read the image", |
|
|
usage_count, |
|
|
"β Cannot read image", |
|
|
None |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
return ( |
|
|
f"β Error loading image: {str(e)}", |
|
|
usage_count, |
|
|
f"β Error: {str(e)}", |
|
|
None |
|
|
) |
|
|
|
|
|
|
|
|
device = setup_device(device_str) |
|
|
|
|
|
|
|
|
if len(img.shape) == 3 and img.shape[2] == 3: |
|
|
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) |
|
|
else: |
|
|
rgb_img = img |
|
|
|
|
|
|
|
|
damage_model_path = DEFAULT_DAMAGE_MODEL_PATH |
|
|
ai_detection_model_path = huggingface_model_path or DEFAULT_AI_DETECTION_MODEL_PATH |
|
|
|
|
|
damage_model = None |
|
|
ai_classifier = None |
|
|
demo_mode = False |
|
|
|
|
|
|
|
|
if damage_model_path and os.path.exists(damage_model_path): |
|
|
damage_model = load_detectron2_damage_model(damage_model_path, device) |
|
|
if not damage_model: |
|
|
demo_mode = True |
|
|
else: |
|
|
demo_mode = True |
|
|
|
|
|
|
|
|
radiov3_initialized = initialize_radiov3_model() |
|
|
if not radiov3_initialized: |
|
|
demo_mode = True |
|
|
|
|
|
|
|
|
if ai_detection_model_path and os.path.exists(ai_detection_model_path): |
|
|
ai_classifier = load_ai_detection_classifier(ai_detection_model_path) |
|
|
if not ai_classifier: |
|
|
demo_mode = True |
|
|
else: |
|
|
demo_mode = True |
|
|
|
|
|
|
|
|
if damage_model is None or not radiov3_initialized or ai_classifier is None: |
|
|
demo_mode = True |
|
|
|
|
|
|
|
|
try: |
|
|
if damage_model and not demo_mode: |
|
|
|
|
|
outputs = damage_model(rgb_img) |
|
|
instances = outputs["instances"].to("cpu") |
|
|
|
|
|
damages = [] |
|
|
boxes = instances.pred_boxes.tensor.numpy() if len(instances) > 0 else [] |
|
|
scores = instances.scores.numpy() if len(instances) > 0 else [] |
|
|
|
|
|
for i, (box, score) in enumerate(zip(boxes, scores)): |
|
|
if score > float(damage_threshold): |
|
|
x1, y1, x2, y2 = box |
|
|
damages.append({ |
|
|
"bbox": [int(x1), int(y1), int(x2), int(y2)], |
|
|
"confidence": float(score), |
|
|
"type": f"Damage_{i + 1}", |
|
|
"area": int((x2 - x1) * (y2 - y1)) |
|
|
}) |
|
|
|
|
|
damage_result = { |
|
|
"damages": damages, |
|
|
"total_damages": len(damages), |
|
|
"demo_mode": False |
|
|
} |
|
|
else: |
|
|
|
|
|
damage_result = simulate_damage_detection(rgb_img) |
|
|
|
|
|
|
|
|
damages = damage_result["damages"] |
|
|
total_damages = damage_result["total_damages"] |
|
|
|
|
|
except Exception as e: |
|
|
damage_result = simulate_damage_detection(rgb_img) |
|
|
damages = damage_result["damages"] |
|
|
total_damages = damage_result["total_damages"] |
|
|
|
|
|
|
|
|
try: |
|
|
if radiov3_initialized and ai_classifier and not demo_mode: |
|
|
|
|
|
features = extract_radio_l_features(rgb_img) |
|
|
features = features.reshape(1, -1) |
|
|
|
|
|
|
|
|
prediction = ai_classifier.predict(features)[0] |
|
|
|
|
|
|
|
|
try: |
|
|
if hasattr(ai_classifier, 'predict_proba'): |
|
|
probabilities = ai_classifier.predict_proba(features)[0] |
|
|
prob_real = float(probabilities[0]) if len(probabilities) > 1 else 1 - prediction |
|
|
prob_ai = float(probabilities[1]) if len(probabilities) > 1 else prediction |
|
|
else: |
|
|
|
|
|
decision_score = ai_classifier.decision_function(features)[0] |
|
|
prob_real = 0.5 + decision_score / 2 if decision_score < 0 else 0.5 - decision_score / 2 |
|
|
prob_ai = 1 - prob_real |
|
|
except Exception: |
|
|
prob_real = 0.5 |
|
|
prob_ai = 0.5 |
|
|
|
|
|
is_ai = prediction == 1 |
|
|
|
|
|
ai_detection_result = { |
|
|
"ai_prob": prob_ai, |
|
|
"real_prob": prob_real, |
|
|
"is_ai": is_ai, |
|
|
"prediction": int(prediction), |
|
|
"confidence": "HIGH" if abs(prob_ai - 0.5) > 0.3 else "MEDIUM" if abs(prob_ai - 0.5) > 0.15 else "LOW", |
|
|
"demo_mode": False |
|
|
} |
|
|
else: |
|
|
|
|
|
ai_detection_result = simulate_ai_detection(rgb_img, float(ai_detection_threshold)) |
|
|
|
|
|
|
|
|
ai_prob = ai_detection_result["ai_prob"] |
|
|
real_prob = ai_detection_result["real_prob"] |
|
|
is_ai = ai_detection_result["is_ai"] |
|
|
ai_confidence = ai_detection_result["confidence"] |
|
|
|
|
|
except Exception as e: |
|
|
ai_detection_result = simulate_ai_detection(rgb_img, float(ai_detection_threshold)) |
|
|
ai_prob = ai_detection_result["ai_prob"] |
|
|
real_prob = ai_detection_result["real_prob"] |
|
|
is_ai = ai_detection_result["is_ai"] |
|
|
ai_confidence = ai_detection_result["confidence"] |
|
|
|
|
|
|
|
|
progress_info.append("\nπ SEQUENTIAL ANALYSIS SYNTHESIS:") |
|
|
|
|
|
if demo_mode: |
|
|
progress_info.append("β οΈ Note: Using demo simulation (models not fully available)") |
|
|
|
|
|
|
|
|
if total_damages > 0 and not is_ai: |
|
|
final_verdict = "β
LEGITIMATE DAMAGE CLAIM" |
|
|
verdict_explanation = "Genuine vehicle damage detected in authentic image" |
|
|
recommendation = "β
Proceed with claim processing" |
|
|
risk_level = "LOW" |
|
|
elif total_damages > 0 and is_ai: |
|
|
final_verdict = "β οΈ POTENTIAL FRAUD - AI-GENERATED IMAGE" |
|
|
verdict_explanation = "Damage detected but image appears to be AI-generated" |
|
|
recommendation = "π Flag for manual review and investigation" |
|
|
risk_level = "HIGH" |
|
|
elif total_damages == 0 and is_ai: |
|
|
final_verdict = "π¨ FRAUD DETECTED" |
|
|
verdict_explanation = "No significant damage found and image appears to be AI-generated" |
|
|
recommendation = "β Reject claim - likely fraudulent" |
|
|
risk_level = "VERY HIGH" |
|
|
else: |
|
|
final_verdict = "β οΈ NO DAMAGE DETECTED" |
|
|
verdict_explanation = "Authentic image but no significant damage found" |
|
|
recommendation = "π Verify claim details and request additional evidence" |
|
|
risk_level = "MEDIUM" |
|
|
|
|
|
progress_info.append(f"ββ Final Verdict: {final_verdict}") |
|
|
progress_info.append(f"ββ Explanation: {verdict_explanation}") |
|
|
progress_info.append(f"ββ Risk Level: {risk_level}") |
|
|
progress_info.append(f"ββ Recommendation: {recommendation}") |
|
|
|
|
|
|
|
|
result_img = rgb_img.copy() |
|
|
|
|
|
|
|
|
for i, damage in enumerate(damages): |
|
|
bbox = damage["bbox"] |
|
|
conf = damage["confidence"] |
|
|
x1, y1, x2, y2 = bbox |
|
|
|
|
|
|
|
|
cv2.rectangle(result_img, (x1, y1), (x2, y2), (0, 255, 255), 2) |
|
|
cv2.putText(result_img, f"Damage {i + 1}: {conf * 100:.1f}%", |
|
|
(x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2) |
|
|
|
|
|
|
|
|
ai_color = (255, 0, 0) if is_ai else (0, 255, 0) |
|
|
ai_text = f"{'AI-GENERATED' if is_ai else 'AUTHENTIC'}" |
|
|
ai_prob_text = f"Confidence: {(ai_prob if is_ai else real_prob) * 100:.1f}%" |
|
|
|
|
|
|
|
|
cv2.putText(result_img, final_verdict, (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.0, ai_color, 3) |
|
|
cv2.putText(result_img, f"Damage Count: {total_damages}", (30, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 255), 2) |
|
|
cv2.putText(result_img, f"AI Detection: {ai_text}", (30, 130), cv2.FONT_HERSHEY_SIMPLEX, 0.8, ai_color, 2) |
|
|
cv2.putText(result_img, ai_prob_text, (30, 170), cv2.FONT_HERSHEY_SIMPLEX, 0.6, ai_color, 2) |
|
|
cv2.putText(result_img, f"Risk Level: {risk_level}", (30, 210), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2) |
|
|
|
|
|
|
|
|
analysis_text = "Advanced Detection System" |
|
|
mode_text = "DEMO MODE" if demo_mode else "FULL ANALYSIS" |
|
|
cv2.putText(result_img, analysis_text, (30, 250), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (128, 128, 128), 2) |
|
|
cv2.putText(result_img, mode_text, (30, 280), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (128, 128, 128), 2) |
|
|
|
|
|
|
|
|
cv2.putText(result_img, f"Individual Usage: {usage_count}/{MAX_TRIES}", |
|
|
(30, result_img.shape[0] - 60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (128, 128, 128), 2) |
|
|
|
|
|
timestamp = time.strftime("%Y-%m-%d %H:%M:%S") |
|
|
cv2.putText(result_img, f"Analysis: {timestamp}", |
|
|
(30, result_img.shape[0] - 30), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (128, 128, 128), 1) |
|
|
|
|
|
|
|
|
if usage_count >= MAX_TRIES: |
|
|
progress_info.append(f"\nβ οΈ Daily usage limit reached ({MAX_TRIES} tries)") |
|
|
progress_info.append("Your quota will reset tomorrow") |
|
|
progress_info.append("Contact sales@askhedi.fr for extended access") |
|
|
else: |
|
|
progress_info.append(f"\nRemaining tries today: {MAX_TRIES - usage_count}") |
|
|
|
|
|
analysis_text = "\n".join(progress_info) |
|
|
|
|
|
|
|
|
progress_info.append(f"\nπͺ Usage saved to browser cookies: {usage_count}/{MAX_TRIES}") |
|
|
|
|
|
|
|
|
email_success, email_message = send_email_with_mailjet(recipient_email, analysis_text, result_img, |
|
|
original_filename) |
|
|
|
|
|
|
|
|
download_path = create_results_package(analysis_text, result_img, original_filename) |
|
|
|
|
|
if email_success: |
|
|
final_message = f"β
Sequential analysis sent via Mailjet AND download ready" |
|
|
else: |
|
|
final_message = f"π¦ {email_message} - Download package ready" |
|
|
|
|
|
return ( |
|
|
analysis_text + f"\n\nπ§ {final_message}", |
|
|
usage_count, |
|
|
final_message, |
|
|
download_path |
|
|
) |
|
|
|
|
|
|
|
|
def create_results_package(analysis_text, result_img, original_filename): |
|
|
"""Create downloadable results package""" |
|
|
try: |
|
|
timestamp = time.strftime("%Y%m%d_%H%M%S") |
|
|
package_name = f"hedi_analysis_{timestamp}.zip" |
|
|
|
|
|
with zipfile.ZipFile(package_name, 'w') as zipf: |
|
|
|
|
|
zipf.writestr(f"analysis_report_{timestamp}.txt", analysis_text) |
|
|
|
|
|
|
|
|
if result_img is not None: |
|
|
|
|
|
try: |
|
|
pil_img = Image.fromarray(result_img.astype('uint8')) |
|
|
img_buffer = io.BytesIO() |
|
|
pil_img.save(img_buffer, format='PNG') |
|
|
zipf.writestr(f"analysis_result_{timestamp}.png", img_buffer.getvalue()) |
|
|
except Exception as e: |
|
|
print(f"Warning: Could not add image to package: {e}") |
|
|
|
|
|
|
|
|
json_data = { |
|
|
"timestamp": timestamp, |
|
|
"original_filename": original_filename, |
|
|
"analysis_summary": "HEDI AI Fraud Detection Analysis - Individual Cookie Tracking", |
|
|
"pipeline": "Sequential: HEDI AI + Cookie Quotas", |
|
|
"quota_system": "Individual browser-based tracking" |
|
|
} |
|
|
zipf.writestr(f"analysis_data_{timestamp}.json", json.dumps(json_data, indent=2)) |
|
|
|
|
|
print(f"β
Results package created: {package_name}") |
|
|
return package_name |
|
|
except Exception as e: |
|
|
print(f"β Error creating results package: {e}") |
|
|
return None |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
print("π Starting Car Damage Fraud Detector - Cookie-Based Individual Quotas...") |
|
|
print(f"πͺ Quota System: Individual tracking via browser cookies") |
|
|
print(f"β
Damage model: {'Available' if os.path.exists(DEFAULT_DAMAGE_MODEL_PATH) else 'Demo mode'}") |
|
|
print( |
|
|
f"β
AI Detection Model: {'Available' if huggingface_model_path or os.path.exists(DEFAULT_AI_DETECTION_MODEL_PATH) else 'Demo mode'}") |
|
|
|
|
|
|
|
|
auto_install_dependencies() |
|
|
|
|
|
|
|
|
preload_models() |
|
|
|
|
|
|
|
|
if MAILJET_CONFIG['API_KEY'] and MAILJET_CONFIG['SECRET_KEY']: |
|
|
print("π§ Mailjet API: β
Configured") |
|
|
print(f"π§ From: {MAILJET_CONFIG['FROM_NAME']} <{MAILJET_CONFIG['FROM_EMAIL']}>") |
|
|
|
|
|
if test_mailjet_connection(): |
|
|
print("π§ Mailjet: β
Connection test successful") |
|
|
else: |
|
|
print("π§ Mailjet: β οΈ Connection test failed") |
|
|
else: |
|
|
print("π§ Mailjet API: β Not configured") |
|
|
|
|
|
print("πͺ Cookie System: Individual quotas enabled (10 per user per day)") |
|
|
print("π Daily Reset: Automatic at midnight local time") |
|
|
|
|
|
app = create_gradio_interface() |
|
|
app.launch( |
|
|
share=False, |
|
|
server_name="0.0.0.0", |
|
|
server_port=7860, |
|
|
show_error=True |
|
|
) |