import gradio as gr import json from datetime import datetime import time from pipeline.orchestrator import analyse # Custom CSS for luxury theme CUSTOM_CSS = """ * { margin: 0; padding: 0; box-sizing: border-box; } body { background: linear-gradient(135deg, #0B0B0F 0%, #1A1A2E 100%); font-family: 'Segoe UI', 'Helvetica Neue', sans-serif; color: #E8E8E8; } /* Hero Section */ .hero-container { background: linear-gradient(135deg, #1A0033 0%, #0B0B0F 50%, #1A0033 100%); padding: 60px 40px; text-align: center; border-bottom: 1px solid rgba(212, 175, 55, 0.3); position: relative; overflow: hidden; } .hero-container::before { content: ''; position: absolute; top: -50%; right: -10%; width: 500px; height: 500px; background: radial-gradient(circle, rgba(212, 175, 55, 0.1) 0%, transparent 70%); border-radius: 50%; pointer-events: none; } .hero-container::after { content: ''; position: absolute; bottom: -50%; left: -10%; width: 500px; height: 500px; background: radial-gradient(circle, rgba(147, 51, 234, 0.1) 0%, transparent 70%); border-radius: 50%; pointer-events: none; } .hero-title { font-size: 3.5em; font-weight: 700; margin-bottom: 15px; background: linear-gradient(135deg, #D4AF37 0%, #E8D4B8 50%, #D4AF37 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; position: relative; z-index: 1; text-shadow: 0 0 30px rgba(212, 175, 55, 0.3); letter-spacing: 2px; } .hero-subtitle { font-size: 1.2em; color: rgba(232, 232, 232, 0.7); position: relative; z-index: 1; max-width: 600px; margin: 0 auto; font-weight: 300; letter-spacing: 0.5px; } /* Main Container */ .main-container { padding: 40px; max-width: 1400px; margin: 0 auto; } /* Two Column Layout */ .content-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 40px; } @media (max-width: 1024px) { .content-grid { grid-template-columns: 1fr; } } /* Input Panel */ .input-panel { background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(212, 175, 55, 0.2); border-radius: 20px; padding: 30px; backdrop-filter: blur(10px); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); transition: all 0.3s ease; } .input-panel:hover { border-color: rgba(212, 175, 55, 0.4); box-shadow: 0 12px 40px rgba(212, 175, 55, 0.1); } .panel-title { font-size: 1.4em; font-weight: 600; margin-bottom: 25px; color: #D4AF37; display: flex; align-items: center; gap: 10px; } /* Upload Area */ .upload-area { border: 2px dashed rgba(212, 175, 55, 0.4); border-radius: 16px; padding: 40px 20px; text-align: center; background: rgba(212, 175, 55, 0.02); transition: all 0.3s ease; cursor: pointer; margin-bottom: 25px; } .upload-area:hover { border-color: rgba(212, 175, 55, 0.7); background: rgba(212, 175, 55, 0.05); } /* Buttons */ .btn-primary { background: linear-gradient(135deg, #D4AF37 0%, #E8D4B8 100%); color: #0B0B0F; border: none; padding: 14px 32px; border-radius: 12px; font-weight: 600; font-size: 1em; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(212, 175, 55, 0.3); width: 100%; margin-bottom: 15px; } .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(212, 175, 55, 0.5); background: linear-gradient(135deg, #E8D4B8 0%, #D4AF37 100%); } .btn-primary:active { transform: translateY(0); } /* Output Panel */ .output-panel { background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(212, 175, 55, 0.2); border-radius: 20px; padding: 30px; backdrop-filter: blur(10px); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); transition: all 0.3s ease; animation: fadeIn 0.5s ease; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } /* Result Card */ .result-card { background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(212, 175, 55, 0.3); border-radius: 16px; padding: 25px; margin-bottom: 25px; text-align: center; animation: slideIn 0.6s ease; } @keyframes slideIn { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } } .result-label { font-size: 0.9em; color: rgba(232, 232, 232, 0.6); margin-bottom: 10px; text-transform: uppercase; letter-spacing: 1px; } .result-status { font-size: 2.5em; font-weight: 700; margin: 15px 0; letter-spacing: 1px; } .status-authentic { color: #4ADF83; text-shadow: 0 0 20px rgba(74, 223, 131, 0.4); } .status-suspicious { color: #FFD700; text-shadow: 0 0 20px rgba(255, 215, 0, 0.4); } .status-fake { color: #FF6B6B; text-shadow: 0 0 20px rgba(255, 107, 107, 0.4); } .confidence-score { font-size: 1.2em; margin: 15px 0; color: #D4AF37; } /* Analysis Layers */ .layers-container { margin-top: 30px; } .layer-item { background: rgba(255, 255, 255, 0.04); border: 1px solid rgba(147, 51, 234, 0.2); border-radius: 12px; padding: 18px; margin-bottom: 15px; display: flex; align-items: center; gap: 15px; animation: fadeInUp 0.6s ease; transition: all 0.3s ease; } .layer-item:hover { border-color: rgba(212, 175, 55, 0.3); background: rgba(212, 175, 55, 0.02); transform: translateX(5px); } @keyframes fadeInUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .layer-icon { font-size: 1.8em; min-width: 40px; text-align: center; } .layer-info { flex: 1; text-align: left; } .layer-label { font-weight: 600; color: #E8E8E8; margin-bottom: 5px; display: flex; justify-content: space-between; align-items: center; } .layer-confidence { color: #D4AF37; font-weight: 600; } .progress-bar { width: 100%; height: 6px; background: rgba(255, 255, 255, 0.1); border-radius: 3px; overflow: hidden; margin-top: 8px; } .progress-fill { height: 100%; background: linear-gradient(90deg, #D4AF37 0%, #E8D4B8 100%); border-radius: 3px; animation: fillProgress 1s ease; } @keyframes fillProgress { from { width: 0; } to { width: var(--confidence); } } /* Loading State */ .loading-animation { display: inline-block; width: 20px; height: 20px; border: 3px solid rgba(212, 175, 55, 0.3); border-top: 3px solid #D4AF37; border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Glow Effect */ .glow-border { position: relative; border: 2px solid transparent; background: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05)) padding-box, linear-gradient(135deg, rgba(212, 175, 55, 0.5), rgba(147, 51, 234, 0.5)) border-box; border-radius: 12px; animation: glowPulse 2s ease-in-out infinite; } @keyframes glowPulse { 0%, 100% { opacity: 0.8; } 50% { opacity: 1; } } /* Responsive */ @media (max-width: 768px) { .hero-title { font-size: 2.2em; } .hero-subtitle { font-size: 1em; } .main-container { padding: 20px; } .input-panel, .output-panel { padding: 20px; } } /* Gradio Component Overrides */ .gradio-container { background: transparent !important; } .form { background: transparent !important; border: none !important; } """ def determine_status(confidence_score): """Map confidence score to authentication status.""" if confidence_score >= 75: return "Authentic", "status-authentic", "✓" elif confidence_score < 25: return "Fake", "status-fake", "✗" else: return "Suspicious", "status-suspicious", "⚠" def format_layer_name(layer_num, layer_data): """Extract meaningful name from layer data.""" names = { 1: f"Source Type: {layer_data.get('source_type', 'Unknown')}", 2: f"Object: {layer_data.get('brand', 'Unknown')}", 3: "Confidence Signal", 4: f"Provenance: {layer_data.get('provenance_status', 'Unknown')}", 5: "Recommendations" } return names.get(layer_num, f"Layer {layer_num}") def extract_confidence(layer_num, layer_data): """Extract confidence percentage from layer.""" if layer_num == 1: return int(layer_data.get('confidence', 0) * 100) elif layer_num == 2: return int(layer_data.get('confidence', 0) * 100) elif layer_num == 3: return layer_data.get('confidence_score', 50) elif layer_num == 4: # Provenance: Clean = 95%, Flagged = 0% return 95 if layer_data.get('provenance_status') == 'Clean' else 0 elif layer_num == 5: # Map severity to confidence severity = layer_data.get('severity', 'info') severity_map = {'critical': 10, 'warning': 30, 'caution': 60, 'info': 85} return severity_map.get(severity, 50) return 50 def analyze_image(image): """Run real pipeline analysis.""" if image is None: return "Please upload an image first." try: # Run the real pipeline result = analyse(image) # Extract key data l1 = result['layer1'] l2 = result['layer2'] l3 = result['layer3'] l4 = result['layer4'] l5 = result['layer5'] # Determine overall status confidence_score = l3['confidence_score'] status, status_color, status_icon = determine_status(confidence_score) # Build result HTML result_html = f"""
Advanced AI-powered verification for luxury items. Authenticate with precision, decide with confidence.
Upload a clear photo of the luxury item. Analysis will begin automatically.
') analyze_btn = gr.Button( "🔍 Run Analysis", elem_classes="btn-primary", variant="primary" ) # Right Column - Output Panel with gr.Column(scale=1, elem_classes="output-panel"): gr.HTML('Upload an image and click "Run Analysis" to see results
Analysis powered by AI-driven verification across 5 verification layers