Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import joblib | |
| import numpy as np | |
| import re | |
| # ---------------------------- | |
| # Load the trained model | |
| # ---------------------------- | |
| model = joblib.load("spam_model.joblib") | |
| # ---------------------------- | |
| # Text normalization (optional but helps) | |
| # ---------------------------- | |
| _url = re.compile(r'https?://\S+|www\.\S+') | |
| _email = re.compile(r'\b[\w\.-]+@[\w\.-]+\.\w+\b') | |
| _num = re.compile(r'\b\d[\d,.\-:/]*\b') | |
| def normalize_text(t: str) -> str: | |
| t = t.strip().lower() | |
| t = _url.sub(' __url__ ', t) | |
| t = _email.sub(' __email__ ', t) | |
| t = _num.sub(' __number__ ', t) | |
| return t | |
| # ---------------------------- | |
| # Prediction function with confidence | |
| # ---------------------------- | |
| def predict_with_confidence(text: str): | |
| text_norm = normalize_text(text) | |
| _input = [text_norm if text_norm else text] | |
| pred = int(model.predict(_input)[0]) | |
| if hasattr(model, "predict_proba"): | |
| proba = float(model.predict_proba(_input)[0][1]) # spam prob | |
| else: | |
| score = float(model.decision_function(_input)[0]) | |
| proba = 1 / (1 + np.exp(-score)) # sigmoid | |
| label = "SPAM π¨" if pred == 1 else "HAM β " | |
| return label, proba | |
| # ---------------------------- | |
| # Custom CSS | |
| # ---------------------------- | |
| CUSTOM_CSS = """ | |
| :root { | |
| --brand: #0ea5e9; | |
| --brand-2: #0284c7; | |
| --success: #22c55e; | |
| --danger: #ef4444; | |
| --card-bg: #0b1220; | |
| --ink: #e5e7eb; | |
| } | |
| .gradio-container {max-width: 2100px !important;} | |
| #app-card { | |
| background: linear-gradient(135deg, #0b1220 0%, #0f172a 100%); | |
| color: var(--ink); | |
| border-radius: 18px; | |
| padding: 28px; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.35); | |
| border: 1px solid rgba(255,255,255,0.08); | |
| } | |
| #title {font-size: 28px;font-weight: 800;letter-spacing: 0.2px;} | |
| .subtitle {opacity: 0.85;margin-top: 2px;font-size: 14px;} | |
| .badge { | |
| display: inline-block;padding: 4px 10px;border-radius: 999px; | |
| background: linear-gradient(135deg, var(--brand) 0%, var(--brand-2) 100%); | |
| color: white;font-weight: 600;font-size: 12px; | |
| } | |
| #predict-btn > button { | |
| background: linear-gradient(135deg, var(--brand) 0%, var(--brand-2) 100%) !important; | |
| border: none !important;color: white !important;font-weight: 700 !important; | |
| border-radius: 10px !important; | |
| } | |
| #clear-btn > button { | |
| background: transparent !important;border: 1px solid rgba(255,255,255,0.15) !important; | |
| color: var(--ink) !important;font-weight: 600 !important;border-radius: 10px !important; | |
| } | |
| .result-card { | |
| border-radius: 14px;padding: 14px 16px;border: 1px solid rgba(255,255,255,0.08); | |
| display: flex;align-items: center;gap: 10px; | |
| } | |
| .result-label {font-size: 18px;font-weight: 800;} | |
| .confidence-wrap {display: flex;align-items: center;gap: 10px;} | |
| .confidence-bar { | |
| height: 10px;border-radius: 999px;flex: 1; | |
| background: linear-gradient(90deg, var(--danger), var(--success)); | |
| position: relative;overflow: hidden; | |
| } | |
| .confidence-fill { | |
| position: absolute;top: 0;left: 0;bottom: 0; | |
| background: rgba(255,255,255,0.9); | |
| mix-blend-mode: overlay; | |
| } | |
| .footer {opacity: 0.7;font-size: 12px;margin-top: 8px;} | |
| """ | |
| # ---------------------------- | |
| # UI Prediction wrapper | |
| # ---------------------------- | |
| def ui_predict(msg): | |
| label, proba = predict_with_confidence(msg) | |
| pct = int(round(proba * 100)) | |
| color = "var(--danger)" if "SPAM" in label else "var(--success)" | |
| styled_label = f"<span style='color:{color}'>{label}</span>" | |
| html = f""" | |
| <div class="result-card"> | |
| <div class="result-label">{styled_label}</div> | |
| <div class="confidence-wrap" style="flex:1"> | |
| <div style="min-width:120px; text-align:right; font-weight:700;">{pct}%</div> | |
| <div class="confidence-bar"> | |
| <div class="confidence-fill" style="width:{pct}%;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| return html | |
| # ---------------------------- | |
| # Gradio App | |
| # ---------------------------- | |
| with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft()) as demo: | |
| with gr.Column(elem_id="app-card"): | |
| gr.HTML(""" | |
| <div id="title">π© SMS Spam Classifier <span class="badge">TF-IDF + Logistic Regression</span></div> | |
| <div class="subtitle">Type an SMS below and get an instant prediction with confidence.</div> | |
| """) | |
| with gr.Row(): | |
| msg = gr.Textbox( | |
| label="Your message", | |
| placeholder="e.g., Congratulations! You have won a FREE vacation. Text WIN to 90909 now!", | |
| lines=5 | |
| ) | |
| with gr.Row(): | |
| predict_btn = gr.Button("Predict", elem_id="predict-btn") | |
| clear_btn = gr.Button("Clear", elem_id="clear-btn") | |
| result = gr.HTML(label="Result") | |
| with gr.Accordion("Try examples", open=False): | |
| gr.Examples( | |
| examples=[ | |
| ["Hey, are we still meeting at 7 tonight?"], | |
| ["Donβt forget to bring your homework tomorrow."], | |
| ["URGENT! Claim your Β£1000 cash prize by calling 0800-123-456."], | |
| ["Free entry in 2 a weekly comp for a chance to win an iPhone. Text WIN to 88888."] | |
| ], | |
| inputs=[msg] | |
| ) | |
| gr.Markdown('<div class="footer">Tip: spam words like "free", "win", "claim", "prize" increase the spam score.</div>') | |
| predict_btn.click(fn=ui_predict, inputs=msg, outputs=result) | |
| clear_btn.click(lambda: "", [], [msg]) | |
| msg.submit(fn=ui_predict, inputs=msg, outputs=result) | |
| if __name__ == "__main__": | |
| demo.launch() | |