spoortishankar's picture
Create app.py
4736b82 verified
Raw History Blame Contribute Delete
5.79 kB
"""app.py - Gradio UI for SentimentDetector (Hugging Face Space entry point)."""
import gradio as gr
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from detector import SentimentDetector, TextProfile
MAX_CHARS = 4000
MAX_EXPLAINED = 12
ICON = {"positive": "🟢", "negative": "🔴", "neutral": "⚪", "mixed": "🟡"}
detector = SentimentDetector()
EXAMPLES = [
["I got into the coding club! So excited 🎉 Then I found out the first meeting is at 6 AM. "
"Oh great, just what I needed... 🙄 Whatever, I'll survive. Actually, the seniors were super welcoming ☺️", True, True],
["Love waiting 2 hours for a delayed flight. Best. Airline. Ever. 🙃", True, True],
["Just shipped my first ML project and it actually works! 🚀🔥", True, True],
["Yeah right, because 'group projects' always go smoothly /s", True, True],
["Perfect. Just perfect. My laptop died right before the deadline.", True, True],
["The package arrived on Tuesday. Nothing special, nothing terrible.", True, True],
["I'm literally dying 😂😂 this meme is too good", True, True],
]
def summary_md(p: TextProfile) -> str:
return (
f"### {ICON[p.overall_label]} Current mood: **{p.overall_label.title()}** ({p.overall:+.2f})\n\n"
f"| Trend | Volatility | Sarcasm rate | Dominant emotion | Sarcasm-awareness shift |\n"
f"|---|---|---|---|---|\n"
f"| {p.trend} ({p.slope:+.2f}/sentence) | {p.volatility:.2f} | {p.sarcasm_rate:.0%} | "
f"{p.dominant_emotion} | {p.sarcasm_shift:+.2f} |\n\n"
f"*{len(p.sentences)} sentences, {p.emoji_count} emoji/emoticons. "
f"“Current mood” weights recent sentences more heavily.*"
)
def make_figure(p: TextProfile):
n = len(p.sentences)
x = np.arange(1, n + 1)
lit = [r.literal for r in p.sentences]
inn = [r.intended for r in p.sentences]
fig, (a1, a2) = plt.subplots(1, 2, figsize=(10, 3.6), gridspec_kw={"width_ratios": [2, 1]})
a1.axhline(0, color="#999", lw=0.8)
a1.plot(x, lit, "--o", color="#9ca3af", label="Literal (surface)")
a1.plot(x, inn, "-o", color="#4f46e5", label="Intended")
sx = [i + 1 for i, r in enumerate(p.sentences) if r.is_sarcastic]
if sx:
a1.scatter(sx, [inn[i - 1] for i in sx], s=200, facecolors="none", edgecolors="#dc2626",
linewidths=2, label="Sarcasm/irony")
a1.set_ylim(-1.05, 1.05)
a1.set_xticks(x if n <= 15 else a1.get_xticks())
a1.set_xlabel("Sentence")
a1.set_ylabel("Sentiment")
a1.set_title("Sentiment trajectory")
a1.legend(fontsize=8, loc="best")
labels = list(p.emotion_mean)
a2.barh(labels, [p.emotion_mean[k] for k in labels], color="#6366f1")
a2.set_xlim(0, 1)
a2.set_title("Average emotion")
fig.tight_layout()
return fig
def to_dataframe(p: TextProfile) -> pd.DataFrame:
rows = []
for i, r in enumerate(p.sentences, 1):
signals = list(r.cues)
rows.append({
"#": i, "Sentence": r.text, "Literal": round(r.literal, 2), "Intended": round(r.intended, 2),
"Label": f"{ICON[r.label]} {r.label}", "Sarcasm": f"{r.sarcasm:.0%}",
"Emoji": " ".join(r.emojis), "Emotion": r.emotion, "Signals": "; ".join(signals),
})
return pd.DataFrame(rows)
def explanation(p: TextProfile, emoji_aware: bool):
tokens = []
for r in p.sentences[:MAX_EXPLAINED]:
tokens += detector.explain(r.text, emoji_aware) + [("\n", None)]
return tokens
def analyze_ui(text, sarcasm_aware, emoji_aware):
text = (text or "").strip()
if not text:
raise gr.Error("Please paste some text written by one person.")
p = detector.analyze(text[:MAX_CHARS], sarcasm_aware=sarcasm_aware, emoji_aware=emoji_aware)
return summary_md(p), make_figure(p), to_dataframe(p), explanation(p, emoji_aware)
with gr.Blocks(title="SentimentDetector") as demo:
gr.Markdown(
"# 🎭 SentimentDetector\n"
"Sentiment analysis that reads **irony, sarcasm and emojis** - not just words. "
"Paste several sentences written by one person to see their sentiment trajectory.\n\n"
"Toggle the switches to run an **ablation** and see what each component contributes."
)
with gr.Row():
with gr.Column(scale=5):
inp = gr.Textbox(lines=8, label="Text written by one person",
placeholder="Oh great, another Monday. 🙄 ...")
with gr.Row():
sarc = gr.Checkbox(True, label="Sarcasm-aware")
emo = gr.Checkbox(True, label="Emoji-aware")
btn = gr.Button("Analyze", variant="primary")
gr.Examples(EXAMPLES, inputs=[inp, sarc, emo])
with gr.Column(scale=5):
summary = gr.Markdown()
plot = gr.Plot(label="Trajectory")
table = gr.Dataframe(label="Per-sentence breakdown", interactive=False, wrap=True)
hl = gr.HighlightedText(
label="Which words drive the literal sentiment? (leave-one-word-out occlusion)",
color_map={"pushes positive": "#86efac", "pushes negative": "#fca5a5"},
combine_adjacent=False, show_legend=True)
btn.click(analyze_ui, [inp, sarc, emo], [summary, plot, table, hl])
gr.Markdown(
"**Limitations.** English only; models were trained on tweets; sarcasm detection is inherently "
"ambiguous and fused here from a classifier plus heuristic cues - treat outputs as probabilistic, "
"not as ground truth. The word-influence view explains the *literal* model read, not the sarcasm layer. "
"Do not use this to profile people without their consent."
)
if __name__ == "__main__":
detector.warmup()
demo.launch()