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 """
🛡️
Enter a website URL above to verify its safety
We analyze content, domains, and patterns to detect scams
"""
def error_state(msg):
return f"""
"""
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'•{f}'
else:
findings_items = 'No specific issues found.'
# AI Explanation Section
explanation = r.get('explanation', '')
# Advice Section
advice = r.get('advice', '')
return f"""
{theme['desc']}
Risk Score
{int(score * 100)}%
{r.get('summary', '')}
"""
# 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("""
Developed by Devesh Punjabi
""")
# 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("""
Phishing Detection System
Powered by Hybrid AI Engine (LightGBM + TinyLlama)
""")
# 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,
)