|
|
import gradio as gr |
|
|
import joblib |
|
|
import os |
|
|
|
|
|
|
|
|
def load_model(): |
|
|
"""Load model from local files in the Space""" |
|
|
try: |
|
|
print("Loading model from local files...") |
|
|
|
|
|
print("\nFiles in current directory:") |
|
|
for f in os.listdir("."): |
|
|
if f.endswith((".jobilib", ".pkl", ".txt")): |
|
|
print(f" - {f}") |
|
|
|
|
|
|
|
|
model_files = [ |
|
|
"tfidf_logreg_best.jobilib", |
|
|
"model.jobilib", |
|
|
"model.pkl" |
|
|
] |
|
|
|
|
|
model = None |
|
|
for mf in model_files: |
|
|
if os.path.exists(mf): |
|
|
print(f"\nLoading model: {mf}") |
|
|
model = joblib.load(mf) |
|
|
print(f"✅ Model loaded from {mf}") |
|
|
break |
|
|
|
|
|
if model is None: |
|
|
print("❌ No model file found!") |
|
|
return None |
|
|
|
|
|
|
|
|
vectorizer_files = [ |
|
|
"vocab.txt", |
|
|
"vocab", |
|
|
"vectorizer.jobilib", |
|
|
"tfidf.jobilib" |
|
|
] |
|
|
|
|
|
vectorizer = None |
|
|
for vf in vectorizer_files: |
|
|
if os.path.exists(vf): |
|
|
print(f"Loading vectorizer: {vf}") |
|
|
vectorizer = joblib.load(vf) |
|
|
print(f"✅ Vectorizer loaded from {vf}") |
|
|
break |
|
|
|
|
|
if vectorizer is None: |
|
|
print("⚠️ Vectorizer not found") |
|
|
|
|
|
|
|
|
label_encoder = None |
|
|
if os.path.exists("label_encoder.jobilib"): |
|
|
print("Loading label encoder...") |
|
|
label_encoder = joblib.load("label_encoder.jobilib") |
|
|
print("✅ Label encoder loaded") |
|
|
|
|
|
return { |
|
|
"model": model, |
|
|
"vectorizer": vectorizer, |
|
|
"label_encoder": label_encoder |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
print(f"❌ Error loading model: {str(e)}") |
|
|
import traceback |
|
|
print(traceback.format_exc()) |
|
|
return None |
|
|
|
|
|
|
|
|
print("Starting model loading...") |
|
|
model_components = load_model() |
|
|
|
|
|
if model_components is None: |
|
|
print("⚠️ Model loading failed!") |
|
|
else: |
|
|
print("✅ All models loaded successfully!") |
|
|
|
|
|
|
|
|
|
|
|
def predict(text): |
|
|
"""Predict cyberbullying category""" |
|
|
if not text.strip(): |
|
|
return "<div class='warn'>⚠️ Please enter some text.</div>" |
|
|
|
|
|
try: |
|
|
|
|
|
if model_components is None: |
|
|
return "<div class='warn'>❌ Model not loaded. Check logs.</div>" |
|
|
|
|
|
model = model_components["model"] |
|
|
vectorizer = model_components["vectorizer"] |
|
|
label_encoder = model_components["label_encoder"] |
|
|
|
|
|
|
|
|
if vectorizer is None: |
|
|
return "<div class='warn'>❌ Vectorizer not available</div>" |
|
|
|
|
|
|
|
|
text_vector = vectorizer.transform([text]) |
|
|
|
|
|
|
|
|
prediction = model.predict(text_vector)[0] |
|
|
|
|
|
|
|
|
try: |
|
|
probabilities = model.predict_proba(text_vector)[0] |
|
|
score = max(probabilities) |
|
|
except: |
|
|
score = 0.8 |
|
|
|
|
|
|
|
|
if label_encoder is not None: |
|
|
try: |
|
|
label = label_encoder.inverse_transform([prediction])[0] |
|
|
except: |
|
|
label = str(prediction) |
|
|
else: |
|
|
label = str(prediction) |
|
|
|
|
|
print(f"Prediction: {label}, Score: {score:.4f}") |
|
|
|
|
|
|
|
|
cyberbullying_types = { |
|
|
"age": {"emoji": "👶", "color": "#ff6b6b", "text": "Age-Based Cyberbullying"}, |
|
|
"gender": {"emoji": "⚥️", "color": "#ff8c42", "text": "Gender-Based Cyberbullying"}, |
|
|
"ethnicity": {"emoji": "🌍", "color": "#ffa502", "text": "Ethnicity-Based Cyberbullying"}, |
|
|
"religion": {"emoji": "🙏", "color": "#ff6b9d", "text": "Religion-Based Cyberbullying"}, |
|
|
"other_cyberbullying": {"emoji": "⚠️", "color": "#ff4757", "text": "Other Cyberbullying Detected"}, |
|
|
"not_cyberbullying": {"emoji": "✅", "color": "#00ff64", "text": "Safe Message"} |
|
|
} |
|
|
|
|
|
|
|
|
label_lower = str(label).lower().strip() |
|
|
category = cyberbullying_types.get( |
|
|
label_lower, |
|
|
cyberbullying_types.get(label, cyberbullying_types["not_cyberbullying"]) |
|
|
) |
|
|
|
|
|
|
|
|
if label_lower == "not_cyberbullying": |
|
|
return f""" |
|
|
<div class='safe'> |
|
|
<div class='checkmark'>{category['emoji']}</div> |
|
|
<div class='safe-text'>{category['text']}</div> |
|
|
<div class='confidence-bar'> |
|
|
<div class='confidence-fill safe-fill' style='width: {score*100}%'></div> |
|
|
</div> |
|
|
<span class='confidence-score'>Confidence: {score:.2%}</span> |
|
|
</div> |
|
|
""" |
|
|
else: |
|
|
|
|
|
return f""" |
|
|
<div class='bully'> |
|
|
<div class='warning-icon'>{category['emoji']}</div> |
|
|
<div class='bully-text'>{category['text']}</div> |
|
|
<div class='label-badge' style='background: {category["color"]}33; border-color: {category["color"]};'>{label}</div> |
|
|
<div class='confidence-bar'> |
|
|
<div class='confidence-fill bully-fill' style='width: {score*100}%; background: {category["color"]};'></div> |
|
|
</div> |
|
|
<span class='confidence-score'>Confidence: {score:.2%}</span> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
except Exception as e: |
|
|
import traceback |
|
|
error_msg = traceback.format_exc() |
|
|
print(f"ERROR in prediction: {str(e)}") |
|
|
print(error_msg) |
|
|
return f"<div class='warn'>❌ Error: {str(e)}</div>" |
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks(css=""" |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/> |
|
|
|
|
|
* { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
box-sizing: border-box; |
|
|
} |
|
|
|
|
|
html, body { |
|
|
background: linear-gradient(-45deg, #7d00ff, #5500ff, #4b7fff, #0099ff, #00bfff); |
|
|
background-size: 500% 500%; |
|
|
animation: gradientBG 15s ease infinite; |
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
|
|
min-height: 100vh; |
|
|
} |
|
|
|
|
|
@keyframes gradientBG { |
|
|
0% {background-position: 0% 50%;} |
|
|
25% {background-position: 100% 50%;} |
|
|
50% {background-position: 100% 100%;} |
|
|
75% {background-position: 0% 100%;} |
|
|
100% {background-position: 0% 50%;} |
|
|
} |
|
|
|
|
|
.gradio-container { |
|
|
background: rgba(20, 20, 40, 0.85) !important; |
|
|
border-radius: 25px !important; |
|
|
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.4), inset 0 0 30px rgba(255, 255, 255, 0.1) !important; |
|
|
backdrop-filter: blur(15px) !important; |
|
|
border: 1px solid rgba(255, 255, 255, 0.15) !important; |
|
|
padding: 40px !important; |
|
|
} |
|
|
|
|
|
.title { |
|
|
font-size: 52px; |
|
|
font-weight: 900; |
|
|
text-align: center; |
|
|
background: linear-gradient(135deg, #7d00ff, #5500ff, #4b7fff, #0099ff); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
background-clip: text; |
|
|
animation: slideInDown 1s ease-out, glow 3s ease-in-out infinite; |
|
|
margin-bottom: 10px; |
|
|
} |
|
|
|
|
|
@keyframes slideInDown { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: translateY(-50px); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: translateY(0); |
|
|
} |
|
|
} |
|
|
|
|
|
@keyframes glow { |
|
|
0%, 100% { |
|
|
text-shadow: 0 0 20px rgba(125, 0, 255, 0.5), 0 0 40px rgba(75, 127, 255, 0.3); |
|
|
} |
|
|
50% { |
|
|
text-shadow: 0 0 30px rgba(75, 127, 255, 0.6), 0 0 60px rgba(0, 153, 255, 0.4); |
|
|
} |
|
|
} |
|
|
|
|
|
.subtitle { |
|
|
text-align: center; |
|
|
color: #e0e0ff; |
|
|
margin-bottom: 30px; |
|
|
font-style: italic; |
|
|
font-size: 16px; |
|
|
animation: fadeInUp 1s ease-out 0.2s both; |
|
|
letter-spacing: 1px; |
|
|
} |
|
|
|
|
|
@keyframes fadeInUp { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: translateY(30px); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: translateY(0); |
|
|
} |
|
|
} |
|
|
|
|
|
textarea { |
|
|
background: rgba(255, 255, 255, 0.08) !important; |
|
|
border: 2px solid rgba(75, 127, 255, 0.3) !important; |
|
|
border-radius: 15px !important; |
|
|
padding: 18px !important; |
|
|
font-size: 16px !important; |
|
|
color: #e0e0ff !important; |
|
|
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) !important; |
|
|
box-shadow: 0 8px 32px rgba(75, 127, 255, 0.1), inset 0 0 20px rgba(255, 255, 255, 0.05) !important; |
|
|
backdrop-filter: blur(10px) !important; |
|
|
} |
|
|
|
|
|
textarea::placeholder { |
|
|
color: rgba(224, 224, 255, 0.5) !important; |
|
|
} |
|
|
|
|
|
textarea:focus { |
|
|
border-color: #0099ff !important; |
|
|
box-shadow: 0 15px 50px rgba(0, 153, 255, 0.4), inset 0 0 30px rgba(0, 153, 255, 0.1) !important; |
|
|
transform: translateY(-5px) scale(1.02); |
|
|
} |
|
|
|
|
|
.btn-primary { |
|
|
background: linear-gradient(135deg, #7d00ff, #5500ff, #4b7fff, #0099ff) !important; |
|
|
background-size: 300% 300% !important; |
|
|
border: 2px solid rgba(75, 127, 255, 0.5) !important; |
|
|
color: white !important; |
|
|
font-weight: 700 !important; |
|
|
font-size: 16px !important; |
|
|
padding: 14px 40px !important; |
|
|
border-radius: 50px !important; |
|
|
cursor: pointer !important; |
|
|
transition: all 0.4s ease !important; |
|
|
box-shadow: 0 10px 40px rgba(75, 127, 255, 0.4) !important; |
|
|
animation: bounceIn 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55) 0.4s both; |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
@keyframes bounceIn { |
|
|
0% { |
|
|
opacity: 0; |
|
|
transform: scale(0.1) rotateZ(-45deg); |
|
|
} |
|
|
50% { |
|
|
opacity: 1; |
|
|
transform: scale(1.1) rotateZ(10deg); |
|
|
} |
|
|
100% { |
|
|
transform: scale(1) rotateZ(0deg); |
|
|
} |
|
|
} |
|
|
|
|
|
.btn-primary:hover { |
|
|
transform: translateY(-5px) scale(1.05); |
|
|
box-shadow: 0 20px 60px rgba(0, 153, 255, 0.6) !important; |
|
|
background-position: 100% 0 !important; |
|
|
} |
|
|
|
|
|
.btn-primary:active { |
|
|
transform: translateY(-2px) scale(0.98); |
|
|
} |
|
|
|
|
|
.safe { |
|
|
background: linear-gradient(135deg, rgba(0, 255, 100, 0.15), rgba(100, 255, 150, 0.08)); |
|
|
border: 2px solid rgba(0, 255, 100, 0.4); |
|
|
padding: 35px; |
|
|
border-radius: 20px; |
|
|
color: #00ff64; |
|
|
text-align: center; |
|
|
animation: slideInRight 0.8s cubic-bezier(0.34, 1.56, 0.64, 1), cardGlowGreen 3s ease-in-out infinite; |
|
|
box-shadow: 0 20px 60px rgba(0, 255, 100, 0.25); |
|
|
backdrop-filter: blur(15px); |
|
|
} |
|
|
|
|
|
.bully { |
|
|
background: linear-gradient(135deg, rgba(255, 0, 0, 0.15), rgba(255, 100, 100, 0.08)); |
|
|
border: 2px solid rgba(255, 107, 107, 0.4); |
|
|
padding: 35px; |
|
|
border-radius: 20px; |
|
|
color: #ff6b6b; |
|
|
text-align: center; |
|
|
animation: slideInRight 0.8s cubic-bezier(0.34, 1.56, 0.64, 1), cardGlowRed 2s ease-in-out infinite; |
|
|
box-shadow: 0 20px 60px rgba(255, 107, 107, 0.25); |
|
|
backdrop-filter: blur(15px); |
|
|
} |
|
|
|
|
|
@keyframes slideInRight { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: translateX(100px) rotateY(20deg); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: translateX(0) rotateY(0deg); |
|
|
} |
|
|
} |
|
|
|
|
|
@keyframes cardGlowGreen { |
|
|
0%, 100% { |
|
|
box-shadow: 0 20px 60px rgba(0, 255, 100, 0.25); |
|
|
} |
|
|
50% { |
|
|
box-shadow: 0 30px 80px rgba(0, 255, 100, 0.4); |
|
|
} |
|
|
} |
|
|
|
|
|
@keyframes cardGlowRed { |
|
|
0%, 100% { |
|
|
box-shadow: 0 20px 60px rgba(255, 107, 107, 0.25); |
|
|
} |
|
|
50% { |
|
|
box-shadow: 0 30px 80px rgba(255, 107, 107, 0.4); |
|
|
} |
|
|
} |
|
|
|
|
|
.warn { |
|
|
color: #ffb700; |
|
|
text-align: center; |
|
|
font-weight: 700; |
|
|
font-size: 18px; |
|
|
animation: shake 0.5s ease-in-out; |
|
|
padding: 20px; |
|
|
} |
|
|
|
|
|
@keyframes shake { |
|
|
0%, 100% {transform: translateX(0);} |
|
|
10%, 30%, 50%, 70%, 90% {transform: translateX(-8px);} |
|
|
20%, 40%, 60%, 80% {transform: translateX(8px);} |
|
|
} |
|
|
|
|
|
.checkmark, .warning-icon { |
|
|
font-size: 56px; |
|
|
margin-bottom: 15px; |
|
|
animation: bounce 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55); |
|
|
display: inline-block; |
|
|
} |
|
|
|
|
|
@keyframes bounce { |
|
|
0% { |
|
|
opacity: 0; |
|
|
transform: scale(0) rotateZ(-45deg); |
|
|
} |
|
|
50% { |
|
|
transform: scale(1.2) rotateZ(15deg); |
|
|
} |
|
|
100% { |
|
|
opacity: 1; |
|
|
transform: scale(1) rotateZ(0deg); |
|
|
} |
|
|
} |
|
|
|
|
|
.safe-text, .bully-text { |
|
|
font-size: 28px; |
|
|
font-weight: 800; |
|
|
margin-bottom: 15px; |
|
|
animation: fadeInDown 0.8s ease-out 0.2s both; |
|
|
} |
|
|
|
|
|
.label-badge { |
|
|
display: inline-block; |
|
|
background: rgba(255, 107, 107, 0.2); |
|
|
border: 2px solid rgba(255, 107, 107, 0.5); |
|
|
padding: 10px 20px; |
|
|
border-radius: 25px; |
|
|
margin-bottom: 18px; |
|
|
font-size: 14px; |
|
|
font-weight: 700; |
|
|
animation: zoomIn 0.7s ease-out 0.3s both; |
|
|
} |
|
|
|
|
|
@keyframes zoomIn { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: scale(0.3); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: scale(1); |
|
|
} |
|
|
} |
|
|
|
|
|
@keyframes fadeInDown { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: translateY(-20px); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: translateY(0); |
|
|
} |
|
|
} |
|
|
|
|
|
.confidence-bar { |
|
|
width: 100%; |
|
|
height: 10px; |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
border-radius: 12px; |
|
|
margin-bottom: 15px; |
|
|
overflow: hidden; |
|
|
animation: fadeIn 0.8s ease-out 0.1s both; |
|
|
border: 1px solid rgba(255, 255, 255, 0.2); |
|
|
} |
|
|
|
|
|
.confidence-fill { |
|
|
height: 100%; |
|
|
border-radius: 12px; |
|
|
transition: width 1.2s cubic-bezier(0.34, 1.56, 0.64, 1); |
|
|
animation: fillWidth 1.2s ease-out; |
|
|
} |
|
|
|
|
|
@keyframes fillWidth { |
|
|
from { |
|
|
width: 0 !important; |
|
|
} |
|
|
} |
|
|
|
|
|
.safe-fill { |
|
|
background: linear-gradient(90deg, #00ff64, #00d452); |
|
|
box-shadow: 0 0 25px rgba(0, 255, 100, 0.8); |
|
|
} |
|
|
|
|
|
.bully-fill { |
|
|
background: linear-gradient(90deg, #ff6b6b, #ff4444); |
|
|
box-shadow: 0 0 25px rgba(255, 107, 107, 0.8); |
|
|
} |
|
|
|
|
|
.confidence-score { |
|
|
font-size: 15px; |
|
|
opacity: 0.9; |
|
|
animation: fadeIn 0.8s ease-out 0.4s both; |
|
|
font-weight: 600; |
|
|
} |
|
|
|
|
|
@keyframes fadeIn { |
|
|
from { |
|
|
opacity: 0; |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
} |
|
|
} |
|
|
|
|
|
@media (max-width: 768px) { |
|
|
.title { |
|
|
font-size: 36px; |
|
|
} |
|
|
|
|
|
.safe, .bully { |
|
|
padding: 25px; |
|
|
} |
|
|
} |
|
|
""") as demo: |
|
|
|
|
|
gr.Markdown("<div class='title'>🛡️ Cyberbullying Detection System</div>") |
|
|
gr.Markdown("<div class='subtitle'>End-to-end NLP system for detecting cyberbullying, including religion-based abuse</div>") |
|
|
|
|
|
with gr.Group(): |
|
|
text_input = gr.Textbox( |
|
|
lines=4, |
|
|
placeholder="Enter a message to analyze...", |
|
|
label="Input Text" |
|
|
) |
|
|
detect_btn = gr.Button("🔍 Detect", variant="primary") |
|
|
|
|
|
output = gr.HTML() |
|
|
|
|
|
detect_btn.click( |
|
|
fn=predict, |
|
|
inputs=text_input, |
|
|
outputs=output |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch() |