Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import logging | |
| from src.core.pipeline import Pipeline | |
| logging.basicConfig(level=logging.INFO, format='%(name)s: %(message)s') | |
| pipeline = Pipeline() | |
| def check_website(url): | |
| """Check if a website is safe.""" | |
| if not url or not url.strip(): | |
| return empty_state() | |
| result = pipeline.analyze(url.strip()) | |
| if not result.get('success'): | |
| return error_state(result.get('error', 'Something went wrong')) | |
| return build_result(result) | |
| def empty_state(): | |
| return """ | |
| <div class="empty-state"> | |
| <div class="empty-icon">🛡️</div> | |
| <div class="empty-text">Enter a website URL above to verify its safety</div> | |
| <div class="empty-sub">We analyze content, domains, and patterns to detect scams</div> | |
| </div> | |
| """ | |
| def error_state(msg): | |
| return f""" | |
| <div class="error-state"> | |
| <div class="error-icon">❌</div> | |
| <div class="error-text">{msg}</div> | |
| </div> | |
| """ | |
| def build_result(r): | |
| verdict = r.get('verdict', 'UNKNOWN').upper() | |
| score = r.get('score', 0) | |
| # Theme configuration based on verdict | |
| if verdict == 'DANGER': | |
| theme = { | |
| 'color': '#ef4444', | |
| 'bg': '#fef2f2', | |
| 'border': '#fca5a5', | |
| 'icon': '🚨', | |
| 'label': 'Dangerous', | |
| 'desc': 'This site shows strong signs of phishing or malicious activity.' | |
| } | |
| elif verdict == 'WARNING': | |
| theme = { | |
| 'color': '#f59e0b', | |
| 'bg': '#fffbeb', | |
| 'border': '#fcd34d', | |
| 'icon': '⚠️', | |
| 'label': 'Suspicious', | |
| 'desc': 'Caution is advised. This site has some irregular signals.' | |
| } | |
| else: # SAFE or UNKNOWN | |
| theme = { | |
| 'color': '#10b981', | |
| 'bg': '#f0fdf4', | |
| 'border': '#86efac', | |
| 'icon': '✅', | |
| 'label': 'Safe', | |
| 'desc': 'No significant threats were detected on this site.' | |
| } | |
| # Build findings list | |
| findings_items = "" | |
| findings = r.get('findings', [])[:5] | |
| if findings: | |
| for f in findings: | |
| findings_items += f'<li class="finding-item"><span class="bullet">•</span>{f}</li>' | |
| else: | |
| findings_items = '<li class="finding-item empty">No specific issues found.</li>' | |
| # AI Explanation Section | |
| explanation = r.get('explanation', '') | |
| # Advice Section | |
| advice = r.get('advice', '') | |
| return f""" | |
| <div class="result-container"> | |
| <div class="verdict-card" style="background-color: {theme['bg']}; border-color: {theme['border']};"> | |
| <div class="verdict-header"> | |
| <span class="verdict-icon">{theme['icon']}</span> | |
| <span class="verdict-label" style="color: {theme['color']}">{theme['label']}</span> | |
| </div> | |
| <div class="verdict-summary" style="color: {theme['color']}">{theme['desc']}</div> | |
| <div class="score-container"> | |
| <div class="score-label">Risk Score</div> | |
| <div class="score-bar-bg"> | |
| <div class="score-bar-fill" style="width: {int(score * 100)}%; background-color: {theme['color']};"></div> | |
| </div> | |
| <div class="score-value">{int(score * 100)}%</div> | |
| </div> | |
| <div class="main-summary">{r.get('summary', '')}</div> | |
| </div> | |
| <div class="details-grid" style="{ 'grid-template-columns: 1fr;' if not findings else '' }"> | |
| {'<div class="card findings-card">' + | |
| '<div class="card-header"><span class="card-icon">🔍</span> Detailed Findings</div>' + | |
| '<ul class="findings-list">' + findings_items + '</ul>' + | |
| '</div>' if findings else ''} | |
| <div class="card ai-card"> | |
| <div class="card-header"> | |
| <span class="card-icon">🤖</span> AI Analysis | |
| </div> | |
| <div id="ai-typing-text" class="ai-text"> | |
| {explanation} | |
| </div> | |
| </div> | |
| </div> | |
| <div class="recommendation-card" style="border-left-color: {theme['color']};"> | |
| <div class="card-header" style="color: {theme['color']};"> | |
| <span class="card-icon">💡</span> Recommendation | |
| </div> | |
| <div class="recommendation-text">{advice}</div> | |
| </div> | |
| <script> | |
| function typeWriter() {{ | |
| const element = document.getElementById('ai-typing-text'); | |
| if (!element) return; | |
| const text = element.innerText; | |
| element.innerText = ''; | |
| element.classList.add('typing-active'); | |
| let i = 0; | |
| function type() {{ | |
| if (i < text.length) {{ | |
| element.innerHTML += text.charAt(i); | |
| i++; | |
| setTimeout(type, 10); | |
| }} | |
| }} | |
| type(); | |
| }} | |
| setTimeout(typeWriter, 100); | |
| </script> | |
| </div> | |
| """ | |
| # Custom CSS | |
| custom_css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); | |
| body { font-family: 'Inter', system-ui, -apple-system, sans-serif !important; } | |
| .gradio-container { | |
| background-color: #f8fafc !important; | |
| max-width: 900px !important; | |
| margin: 0 auto !important; | |
| } | |
| /* Header */ | |
| .main-header { | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| padding-top: 2rem; | |
| } | |
| .header-icon { font-size: 3rem; margin-bottom: 0.5rem; display: block; } | |
| .header-title { font-size: 2rem; font-weight: 700; color: #0f172a; margin: 0; } | |
| .header-subtitle { color: #64748b; font-size: 1.1rem; margin-top: 0.5rem; } | |
| /* Input Area */ | |
| .input-row { margin-bottom: 2rem; } | |
| /* Empty State */ | |
| .empty-state { | |
| text-align: center; | |
| padding: 4rem 2rem; | |
| color: #94a3b8; | |
| background: white; | |
| border-radius: 1rem; | |
| border: 1px dashed #e2e8f0; | |
| } | |
| .empty-icon { font-size: 3.5rem; margin-bottom: 1rem; opacity: 0.8; } | |
| .empty-text { font-size: 1.25rem; font-weight: 500; color: #64748b; margin-bottom: 0.5rem; } | |
| .empty-sub { font-size: 0.95rem; } | |
| /* Error State */ | |
| .error-state { | |
| text-align: center; | |
| padding: 3rem; | |
| background: #fff5f5; | |
| border-radius: 1rem; | |
| border: 1px solid #fed7d7; | |
| color: #c53030; | |
| } | |
| .error-icon { font-size: 3rem; margin-bottom: 1rem; } | |
| .error-text { font-size: 1.2rem; font-weight: 600; } | |
| /* Results */ | |
| .result-container { animation: fadeIn 0.5s ease-out; } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .verdict-card { | |
| padding: 2.5rem; | |
| border-radius: 1.5rem; | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| border: 2px solid; | |
| box-shadow: 0 10px 30px -10px rgba(0,0,0,0.05); | |
| } | |
| .verdict-icon { font-size: 4rem; display: block; margin-bottom: 1rem; } | |
| .verdict-label { font-size: 2.5rem; font-weight: 800; display: block; line-height: 1.1; margin-bottom: 0.5rem; } | |
| .verdict-summary { font-size: 1.1rem; opacity: 0.9; margin-bottom: 2rem; } | |
| .main-summary { font-size: 1.1rem; line-height: 1.6; color: #334155; margin-top: 1.5rem; max-width: 600px; margin-left: auto; margin-right: auto; text-align: justify; } | |
| .score-container { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 1rem; | |
| max-width: 400px; | |
| margin: 0 auto; | |
| } | |
| .score-label { font-weight: 600; color: #64748b; font-size: 0.9rem; white-space: nowrap; } | |
| .score-bar-bg { | |
| flex-grow: 1; | |
| height: 8px; | |
| background: rgba(0,0,0,0.05); | |
| border-radius: 4px; | |
| overflow: hidden; | |
| } | |
| .score-bar-fill { height: 100%; border-radius: 4px; transition: width 1s ease-out; } | |
| .score-value { font-weight: 700; font-size: 1.1rem; width: 3rem; text-align: left; opacity: 0.8; } | |
| .details-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 1.5rem; | |
| margin-bottom: 2rem; | |
| } | |
| @media (max-width: 768px) { | |
| .details-grid { grid-template-columns: 1fr; } | |
| } | |
| @media (max-width: 1024px) { | |
| .floating-credits { | |
| position: static !important; | |
| margin: 0 auto 1.5rem auto; | |
| width: fit-content; | |
| max-width: 95%; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.05); | |
| display: flex; /* Ensure flex is active */ | |
| justify-content: center; | |
| transform: none !important; | |
| flex-wrap: wrap; | |
| } | |
| .main-header { | |
| padding-top: 1rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .header-title { | |
| font-size: 1.75rem !important; /* Smaller title */ | |
| line-height: 1.2; | |
| } | |
| .header-icon { | |
| font-size: 2.5rem !important; /* Smaller icon */ | |
| } | |
| .gradio-container { | |
| padding: 0 1rem !important; /* Ensure some side padding */ | |
| } | |
| .input-row { | |
| flex-direction: column !important; /* Stack input and button */ | |
| gap: 0.75rem; | |
| } | |
| .input-row > * { | |
| width: 100% !important; /* Full width for children */ | |
| min-width: 0 !important; | |
| margin: 0 !important; /* Reset margins */ | |
| } | |
| .input-row button { | |
| width: 100% !important; | |
| } | |
| .verdict-card { | |
| padding: 1.5rem !important; | |
| } | |
| .verdict-label { | |
| font-size: 1.8rem !important; | |
| } | |
| .verdict-icon { | |
| font-size: 3rem !important; | |
| } | |
| .empty-state { | |
| padding: 2rem 1rem !important; | |
| } | |
| .empty-icon { | |
| font-size: 2.5rem !important; | |
| } | |
| .empty-text { | |
| font-size: 1.1rem !important; | |
| } | |
| } | |
| .card { | |
| background: white; | |
| border: 1px solid #e2e8f0; | |
| border-radius: 1rem; | |
| padding: 1.5rem; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); | |
| } | |
| .card-header { | |
| font-size: 1.1rem; | |
| font-weight: 600; | |
| color: #1e293b; | |
| margin-bottom: 1rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .card-icon { font-size: 1.2rem; } | |
| .findings-list { list-style: none; padding: 0; margin: 0; } | |
| .finding-item { | |
| padding: 0.75rem 0; | |
| border-bottom: 1px solid #f1f5f9; | |
| color: #475569; | |
| font-size: 0.95rem; | |
| display: flex; | |
| align-items: start; | |
| gap: 0.5rem; | |
| } | |
| .finding-item:last-child { border-bottom: none; } | |
| .finding-item.empty { color: #94a3b8; font-style: italic; } | |
| .bullet { color: #94a3b8; font-size: 1.2rem; line-height: 1; } | |
| .ai-card { background: #f8fafc; border-color: #e2e8f0; } | |
| .ai-text { | |
| color: #334155; | |
| line-height: 1.6; | |
| font-size: 0.95rem; | |
| text-align: justify; | |
| hyphens: auto; | |
| } | |
| .recommendation-card { | |
| background: white; | |
| border-left: 5px solid #cbd5e1; | |
| border-radius: 0.75rem; | |
| padding: 1.5rem; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); | |
| } | |
| .recommendation-text { | |
| color: #475569; | |
| line-height: 1.6; | |
| } | |
| /* Floating Credits */ | |
| .floating-credits { | |
| position: fixed; | |
| top: 1.5rem; | |
| right: 2rem; | |
| background: rgba(255, 255, 255, 0.82); | |
| backdrop-filter: blur(12px); | |
| -webkit-backdrop-filter: blur(12px); | |
| padding: 0.75rem 1rem; | |
| border-radius: 999px; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| border: 1px solid rgba(226, 232, 240, 0.8); | |
| z-index: 100; | |
| transition: transform 0.2s; | |
| } | |
| .floating-credits:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
| } | |
| .credit-icons { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| } | |
| .credit-icon { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: transform 0.2s; | |
| } | |
| .credit-icon img { | |
| height: 25px; | |
| width: auto; | |
| transition: 0.3s ease; | |
| opacity: 0.75; | |
| } | |
| .credit-icon img:hover { | |
| opacity: 1; | |
| transform: scale(1.15); | |
| } | |
| .credit-text { | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| color: #334155; | |
| white-space: nowrap; | |
| background: linear-gradient(90deg, #334155, #64748b); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| padding-right: 1rem; | |
| border-right: 1px solid #cbd5e1; | |
| } | |
| @media (max-width: 640px) { | |
| .floating-credits { | |
| position: static; | |
| margin: 0 auto 1.5rem auto; | |
| width: fit-content; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.05); | |
| } | |
| .main-header { | |
| padding-top: 1rem; | |
| } | |
| } | |
| /* NEW CSS TO STYLE EXAMPLES AS PILLS */ | |
| .gradio-examples { | |
| margin-top: 1rem; | |
| } | |
| /* Force table to behave like a horizontal list */ | |
| .gradio-examples table { | |
| border: none !important; | |
| background: transparent !important; | |
| display: block !important; | |
| } | |
| .gradio-examples tbody { | |
| display: flex !important; | |
| flex-wrap: wrap; | |
| gap: 0.5rem; | |
| } | |
| .gradio-examples tr { | |
| display: contents !important; | |
| } | |
| .gradio-examples td { | |
| background: white; | |
| border: 1px solid #e2e8f0; | |
| border-radius: 999px; | |
| padding: 0.5rem 1rem; | |
| display: inline-block; | |
| cursor: pointer; | |
| font-size: 0.9rem; | |
| color: #475569; | |
| transition: all 0.2s; | |
| margin: 0 !important; | |
| } | |
| .gradio-examples td:hover { | |
| border-color: #cbd5e1; | |
| background: #f8fafc; | |
| transform: translateY(-1px); | |
| } | |
| .gradio-examples thead { display: none; } | |
| """ | |
| # Theme Configuration | |
| app_theme = gr.themes.Soft( | |
| primary_hue="blue", | |
| neutral_hue="slate", | |
| font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"] | |
| ) | |
| # Build the interface | |
| with gr.Blocks( | |
| title="PhishingInsight", | |
| # removed js=js_func so the footer is visible | |
| ) as app: | |
| # Header | |
| gr.HTML(""" | |
| <div class="floating-credits"> | |
| <div class="credit-text">Developed by Devesh Punjabi</div> | |
| <div class="credit-icons"> | |
| <a href="https://github.com/deveshpunjabi" target="_blank" class="credit-icon" title="GitHub"> | |
| <img src="https://cdn-icons-png.flaticon.com/512/25/25231.png" alt="GitHub"> | |
| </a> | |
| <a href="https://www.linkedin.com/in/deveshpunjabi" target="_blank" class="credit-icon linkedin" title="LinkedIn"> | |
| <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/81/LinkedIn_icon.svg/2048px-LinkedIn_icon.svg.png" alt="LinkedIn"> | |
| </a> | |
| <a href="https://huggingface.co/deveshpunjabi" target="_blank" class="credit-icon emoji" title="Hugging Face"> | |
| <img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" alt="HF"> | |
| </a> | |
| </div> | |
| </div> | |
| <div class="main-header"> | |
| <span class="header-icon">🛡️</span> | |
| <h1 class="header-title">PhishingInsight</h1> | |
| <div class="header-subtitle">Expert AI Phishing Detection & Analysis</div> | |
| </div> | |
| """) | |
| # Input section | |
| with gr.Row(elem_classes="input-row"): | |
| url_input = gr.Textbox( | |
| placeholder="Paste a website link here to scan (e.g., https://example.com/login)", | |
| show_label=False, | |
| scale=5, | |
| container=False, | |
| autofocus=True | |
| ) | |
| check_btn = gr.Button("Scan Website", variant="primary", scale=1, min_width=120) | |
| # Example links | |
| gr.Examples( | |
| examples=[ | |
| ["https://paypal-verify.tk/login"], | |
| ["https://secure-bank-update.xyz"], | |
| ["https://www.google.com"], | |
| ["https://github.com"], | |
| ], | |
| inputs=url_input, | |
| label="Quick Examples", | |
| examples_per_page=4 | |
| ) | |
| # Results | |
| output = gr.HTML(value=empty_state()) | |
| # Footer | |
| gr.HTML(""" | |
| <div style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #e5e7eb; text-align: center; color: #6b7280; font-size: 0.9rem;"> | |
| <p><strong>Phishing Detection System</strong></p> | |
| <p style="font-size: 0.8rem; margin-top: 5px;">Powered by Hybrid AI Engine (LightGBM + TinyLlama)</p> | |
| </div> | |
| """) | |
| # Events | |
| check_btn.click(fn=check_website, inputs=url_input, outputs=output) | |
| url_input.submit(fn=check_website, inputs=url_input, outputs=output) | |
| if __name__ == "__main__": | |
| app.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| theme=app_theme, | |
| css=custom_css, | |
| show_error=True, | |
| ) |