Download app.py from spoortishankar/SentimentDetector: direct link, hf CLI and curl.
- Browser
- Download file 5.79 kB
-
https://huggingface.co/spaces/spoortishankar/SentimentDetector/resolve/main/app.py
- Command line
-
hf download hf://spaces/spoortishankar/SentimentDetector/app.py
-
curl -L -o app.py https://huggingface.co/spaces/spoortishankar/SentimentDetector/resolve/main/app.py
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() |