GLAkavya commited on
Commit
ba49331
·
verified ·
1 Parent(s): 4a0fc18

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +116 -401
app.py CHANGED
@@ -1,430 +1,145 @@
1
- # app.py
2
- # ------------------------------------------------------------
3
- # Social Media Sentiment Analyzer (Gemini + HF)
4
- # - Posts can be generated by Gemini (toggle)
5
- # - Sentiment via Gemini or HF Transformers (toggle)
6
- # - Pretty Plotly charts + animated background
7
- #
8
- # Requires (in requirements.txt):
9
- # gradio>=4.36.1
10
- # plotly>=5.22.0
11
- # transformers>=4.41.2
12
- # torch --extra-index-url https://download.pytorch.org/whl/cpu
13
- # google-generativeai>=0.7.2
14
- # pandas
15
- # ------------------------------------------------------------
16
-
17
  import os
18
- import json
19
  import random
20
- import re
21
- from typing import List, Tuple, Dict
22
-
23
  import gradio as gr
24
- import pandas as pd
25
  import plotly.express as px
 
 
26
 
27
- # --- Optional Gemini import (handled gracefully) ---
28
- GEMINI_AVAILABLE = True
29
- try:
30
- import google.generativeai as genai # type: ignore
31
- except Exception:
32
- GEMINI_AVAILABLE = False
33
-
34
- # --- Optional HF Transformers sentiment pipeline (CPU friendly) ---
35
- HF_AVAILABLE = True
36
- try:
37
- from transformers import pipeline
38
- except Exception:
39
- HF_AVAILABLE = False
40
-
41
 
42
- # ------------------ Config ------------------
 
 
 
 
43
 
44
- MAX_POSTS = 50
45
- DEFAULT_MODEL_HF = "distilbert-base-uncased-finetuned-sst-2-english"
46
- GEMINI_MODEL_FAST = "gemini-1.5-flash"
47
 
48
- GEMINI_KEY = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
49
- if GEMINI_AVAILABLE and GEMINI_KEY:
50
- genai.configure(api_key=GEMINI_KEY)
51
-
52
-
53
- # ------------------ Utilities ------------------
54
-
55
- def clean_posts_list(text: str, n: int) -> List[str]:
56
- """
57
- Try to parse a JSON array of strings; if not, split lines or bullets.
58
- Ensures length <= n and removes duplicates while keeping order.
59
- """
60
- text = text.strip()
61
- posts: List[str] = []
62
 
63
- # Try JSON array
64
- if text.startswith("["):
65
  try:
66
- arr = json.loads(text)
67
- if isinstance(arr, list):
68
- posts = [str(x).strip() for x in arr]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  except Exception:
70
- posts = []
71
-
72
- # If empty, try to parse as numbered/bulleted lines
73
- if not posts:
74
- lines = [ln.strip() for ln in re.split(r"[\r\n]+", text) if ln.strip()]
75
- cleaned = []
76
- for ln in lines:
77
- ln = re.sub(r"^[\-\*\d\.\)\s]+", "", ln) # strip bullets/nums
78
- if ln:
79
- cleaned.append(ln)
80
- posts = cleaned
81
-
82
- # Deduplicate while preserving order
83
- seen = set()
84
- unique = []
85
- for p in posts:
86
- if p not in seen:
87
- seen.add(p)
88
- unique.append(p)
89
-
90
- # Trim length and empty values
91
- unique = [p for p in unique if p.strip()][:n]
92
- return unique
93
-
94
-
95
- def generate_posts_gemini(hashtag: str, n: int) -> Tuple[List[str], str]:
96
- """
97
- Ask Gemini to create n short, realistic social posts.
98
- Returns (posts, info_message). If fails, returns ([], reason).
99
- """
100
- if not (GEMINI_AVAILABLE and GEMINI_KEY):
101
- return [], "Gemini unavailable (missing package or key)."
102
-
103
- prompt = f"""
104
- You are a social media copy expert.
105
-
106
- Generate {n} diverse, realistic, short social posts about the topic {hashtag}.
107
- Constraints:
108
- - sound like real posts/tweets (casual, short, natural)
109
- - include some emojis and variety in sentiment (positive, negative, neutral)
110
- - avoid hate speech, slurs, or unsafe content
111
- - return ONLY a JSON array of strings, no extra text
112
-
113
- Example:
114
- ["Love {hashtag}! 🚀", "Not sure about {hashtag}… 🤔", "This {hashtag} launch was underwhelming 😕"]
115
- """
116
-
117
- try:
118
- model = genai.GenerativeModel(GEMINI_MODEL_FAST)
119
- resp = model.generate_content(prompt)
120
- text = resp.text or ""
121
- posts = clean_posts_list(text, n)
122
- if posts:
123
- return posts, f"Generated {len(posts)} posts via Gemini."
124
- return [], "Gemini responded but parsing returned no posts."
125
- except Exception as e:
126
- return [], f"Gemini error: {e}"
127
-
128
-
129
- def generate_posts_fallback(hashtag: str, n: int) -> List[str]:
130
- """
131
- Local lightweight fallback templates.
132
- """
133
- templates = [
134
- f"I love {hashtag}! It's amazing ❤️",
135
- f"I'm disappointed with {hashtag} 💔",
136
- f"{hashtag} totally failed expectations 😠",
137
- f"Not sure how I feel about {hashtag} 🤔",
138
- f"People are talking about {hashtag} everywhere 🌍",
139
- f"{hashtag} campaign is the best thing this year 🎉",
140
- f"Super excited about {hashtag} 🔥",
141
- f"{hashtag} is the worst thing ever 😡",
142
- f"Mixed feelings about {hashtag} today 😶‍🌫️",
143
- f"Curious where {hashtag} goes next 👀",
144
- ]
145
- # sample with replacement for diversity
146
- return random.sample(templates, k=min(len(templates), n)) if n <= len(templates) else random.choices(templates, k=n)
147
-
148
-
149
- def analyze_sentiment_hf(posts: List[str]) -> List[Dict]:
150
- """
151
- HF pipeline sentiment: POSITIVE/NEGATIVE with score
152
- (Neutral simulated lightly based on score band).
153
- """
154
- if not HF_AVAILABLE:
155
- # If transformers not available, return neutral placeholders
156
- return [{"sentiment": "NEUTRAL", "confidence": 0.5} for _ in posts]
157
-
158
- nlp = pipeline("sentiment-analysis", model=DEFAULT_MODEL_HF)
159
- results = nlp(posts)
160
- out = []
161
- for r in results:
162
- label = r["label"].upper()
163
- score = float(r["score"])
164
- # Project a basic neutral band to make visuals richer
165
- if 0.45 < score < 0.55:
166
- sent = "NEUTRAL"
167
- conf = 0.5
168
- else:
169
- sent = "POSITIVE" if label.startswith("POS") else "NEGATIVE"
170
- conf = score
171
- out.append({"sentiment": sent, "confidence": round(conf, 2)})
172
- return out
173
-
174
-
175
- def analyze_sentiment_gemini(posts: List[str]) -> List[Dict]:
176
- """
177
- Gemini multi-class sentiment with confidence 0..1.
178
- """
179
- if not (GEMINI_AVAILABLE and GEMINI_KEY):
180
- return [{"sentiment": "NEUTRAL", "confidence": 0.5} for _ in posts]
181
-
182
- prompt = f"""
183
- Classify sentiment of each post as one of: POSITIVE, NEGATIVE, NEUTRAL.
184
- Return JSON array of objects with fields: sentiment, confidence (0..1).
185
- No extra text.
186
-
187
- Posts:
188
- {json.dumps(posts, ensure_ascii=False, indent=2)}
189
- Expected JSON schema:
190
- [{{"sentiment":"POSITIVE|NEGATIVE|NEUTRAL","confidence":0.87}}, ...]
191
- """
192
- try:
193
- model = genai.GenerativeModel(GEMINI_MODEL_FAST)
194
- resp = model.generate_content(prompt)
195
- text = resp.text or ""
196
- # Find JSON array robustly
197
- match = re.search(r"\[[\s\S]+\]", text)
198
- if match:
199
- arr = json.loads(match.group(0))
200
- clean = []
201
- for i, it in enumerate(arr[:len(posts)]):
202
- s = str(it.get("sentiment", "NEUTRAL")).upper()
203
- if s not in {"POSITIVE", "NEGATIVE", "NEUTRAL"}:
204
- s = "NEUTRAL"
205
- c = float(it.get("confidence", 0.5))
206
- c = max(0.0, min(1.0, c))
207
- clean.append({"sentiment": s, "confidence": round(c, 2)})
208
- # If Gemini returned fewer rows, pad neutrals
209
- while len(clean) < len(posts):
210
- clean.append({"sentiment": "NEUTRAL", "confidence": 0.5})
211
- return clean
212
- except Exception:
213
- pass
214
- # fallback neutrals
215
- return [{"sentiment": "NEUTRAL", "confidence": 0.5} for _ in posts]
216
-
217
-
218
- def build_plot(df: pd.DataFrame, vis: str, hashtag: str):
219
- """
220
- Build nice Plotly figure.
221
- """
222
- vis = (vis or "Bar").lower()
223
- # Count per sentiment
224
- counts = df["Sentiment"].value_counts().reindex(["POSITIVE", "NEUTRAL", "NEGATIVE"], fill_value=0)
225
- count_df = counts.reset_index()
226
- count_df.columns = ["Sentiment", "Count"]
227
-
228
- if vis == "pie":
229
- fig = px.pie(
230
- count_df, values="Count", names="Sentiment",
231
- title=f"Sentiment Distribution for {hashtag}",
232
- hole=0.45
233
- )
234
- fig.update_traces(textposition="inside", pull=[0.03, 0.03, 0.03])
235
- elif vis == "line":
236
- # rolling positive ratio
237
- map_vals = df["Sentiment"].map({"POSITIVE": 1, "NEUTRAL": 0.5, "NEGATIVE": 0})
238
- roll = map_vals.rolling(window=max(3, min(10, len(df)//3)), min_periods=1).mean()
239
- fig = px.line(
240
- x=list(range(1, len(df)+1)), y=roll,
241
- labels={"x": "Post Index", "y": "Rolling Sentiment (0..1)"},
242
- title=f"Sentiment Rolling Trend for {hashtag}"
243
- )
244
- else:
245
- fig = px.bar(
246
- count_df, x="Sentiment", y="Count",
247
- title=f"Sentiment Distribution for {hashtag}"
248
- )
249
-
250
- fig.update_layout(
251
- paper_bgcolor="rgba(0,0,0,0)",
252
- plot_bgcolor="rgba(0,0,0,0)",
253
- font=dict(size=14),
254
- title_x=0.02,
255
- hovermode="x unified",
256
- margin=dict(l=40, r=20, t=60, b=40),
257
- )
258
- return fig
259
-
260
-
261
- # ------------------ Main callback ------------------
262
-
263
- def run_analysis(
264
- hashtag: str,
265
- n_posts: int,
266
- vis_type: str,
267
- use_gemini_posts: bool,
268
- use_gemini_analysis: bool
269
- ):
270
- hashtag = hashtag.strip()
271
- if not hashtag:
272
- return (
273
- gr.update(value=pd.DataFrame([])),
274
- gr.update(value=None),
275
- "⚠️ Please enter a hashtag.",
276
- "—", 0, 0
277
- )
278
-
279
- n_posts = max(5, min(MAX_POSTS, int(n_posts or 20)))
280
-
281
- # 1) Generate posts
282
- posts = []
283
- info_posts = ""
284
- gemini_count = 0
285
-
286
- if use_gemini_posts:
287
- posts, info_posts = generate_posts_gemini(hashtag, n_posts)
288
- gemini_count = len(posts)
289
-
290
- if len(posts) < n_posts:
291
- # Top up with fallback to avoid looking repetitive if Gemini returned few
292
- remaining = n_posts - len(posts)
293
- posts += generate_posts_fallback(hashtag, remaining)
294
- info_posts += f" | Fallback added: {remaining}"
295
-
296
- # 2) Sentiment
297
- if use_gemini_analysis:
298
- analysis = analyze_sentiment_gemini(posts)
299
- analysis_engine = "Gemini"
300
- else:
301
- analysis = analyze_sentiment_hf(posts)
302
- analysis_engine = "HF Transformers"
303
-
304
- # 3) DataFrame
305
- df = pd.DataFrame({
306
- "Post": posts,
307
- "Sentiment": [a["sentiment"] for a in analysis],
308
- "Confidence": [a["confidence"] for a in analysis],
309
- })
310
-
311
- # 4) Plot
312
- fig = build_plot(df, vis_type, hashtag)
313
-
314
- # 5) Status
315
- status = f"Generated {len(posts)} posts · {gemini_count} via Gemini · Analyzed with {analysis_engine}"
316
- return df, fig, status, analysis_engine, gemini_count, len(posts) - gemini_count
317
-
318
-
319
- # ------------------ UI ------------------
320
-
321
- THEME = gr.themes.Soft(
322
- primary_hue="indigo",
323
- neutral_hue="slate",
324
- ).set(
325
- button_primary_background_fill="*primary_600",
326
- button_primary_background_fill_hover="*primary_700",
327
- )
328
-
329
  CUSTOM_CSS = """
330
- /* Starry gradient background */
331
- body { background: radial-gradient(1200px 600px at 60% -10%, rgba(0,255,255,0.15), transparent 60%),
332
- radial-gradient(900px 400px at 20% -10%, rgba(255,0,255,0.12), transparent 60%),
333
- linear-gradient(160deg, #0b1020, #100a25 35%, #0a0f2d 70%); }
334
- #root { background: transparent !important; }
335
-
336
- .starfield, .planet {
337
- position: fixed; inset: 0; pointer-events:none; z-index: -1;
338
  }
339
- .starfield::before, .starfield::after {
340
- content: ""; position: absolute; inset: 0;
341
- background-image:
342
- radial-gradient(2px 2px at 20% 30%, rgba(255,255,255,.6) 50%, transparent 51%),
343
- radial-gradient(1.5px 1.5px at 70% 60%, rgba(255,255,255,.4) 50%, transparent 51%),
344
- radial-gradient(1.7px 1.7px at 40% 80%, rgba(255,255,255,.5) 50%, transparent 51%),
345
- radial-gradient(1.4px 1.4px at 90% 20%, rgba(255,255,255,.35) 50%, transparent 51%);
346
- animation: twinkle 6s infinite ease-in-out alternate;
347
  }
348
- @keyframes twinkle { from {opacity:.4} to {opacity:1} }
349
- .planet::before{
350
- content:""; position:absolute; width:220px; height:220px; right:8%; top:12%;
351
- background: radial-gradient(circle at 30% 30%, #4ef4d7, #2b7dff 40%, #2339a1 70%, #0d1130 80%);
352
- border-radius:50%; filter: blur(0.3px) drop-shadow(0 0 18px rgba(70,180,255,.25));
353
- animation: floaty 10s ease-in-out infinite;
354
  }
355
- @keyframes floaty { 50% { transform: translateY(12px) translateX(-8px) } }
356
-
357
- .gradio-container { max-width: 1100px !important; margin: 0 auto; }
358
- .header-title { font-size: 2.1rem; font-weight: 800; letter-spacing: .5px; color: #d9f0ff; }
359
- .header-sub { color: #bcd7ff; opacity: .85; }
360
-
361
- .card {
362
- border: 1px solid rgba(255,255,255,.08);
363
- background: rgba(255,255,255,.05);
364
- backdrop-filter: blur(10px);
365
- border-radius: 18px;
366
- transition: transform .2s ease, box-shadow .2s ease;
367
  }
368
- .card:hover { transform: translateY(-2px); box-shadow: 0 18px 40px rgba(0,0,0,.25); }
369
-
370
- label, .label { color:#eaf2ff !important; font-weight:600; }
371
-
372
- .status-badge{
373
- padding:.4rem .7rem; border-radius:999px; background:rgba(0,0,0,.35); color:#e6f7ff;
374
- display:inline-flex; gap:.5rem; align-items:center; border:1px solid rgba(255,255,255,.12)
 
 
 
375
  }
376
  """
377
 
378
- with gr.Blocks(theme=THEME, css=CUSTOM_CSS, title="Social Media Sentiment Analyzer") as demo:
379
- gr.HTML('<div class="starfield"></div><div class="planet"></div>')
380
- gr.Markdown(
381
- """
382
- <div class="header-title">🚀 Social Media Sentiment Analyzer</div>
383
- <div class="header-sub">Stream-like posts • Analyze moods • Visualize trends — with Gemini & HF</div>
384
- """
385
- )
386
 
387
  with gr.Row():
388
- with gr.Column(scale=5, elem_classes=["card"]):
389
- hashtag = gr.Textbox(label="Enter Hashtag", placeholder="#YourTopic", value="#gla university")
390
- n_posts = gr.Slider(5, MAX_POSTS, value=20, step=1, label="Number of Posts (max 50)")
391
- vis_type = gr.Dropdown(["Bar", "Pie", "Line"], value="Bar", label="Choose Visualization")
392
- use_gemini_posts = gr.Checkbox(value=True, label="Generate Posts with Gemini")
393
- use_gemini_analysis = gr.Checkbox(value=False, label="Use Gemini for Sentiment (else HF)")
394
-
395
- run_btn = gr.Button("🔎 Run Analysis", variant="primary")
396
-
397
- status = gr.Markdown("Ready.")
398
- stats_row = gr.Markdown("", visible=False)
399
-
400
- with gr.Column(scale=7, elem_classes=["card"]):
401
  posts_table = gr.Dataframe(
402
- headers=["Post", "Sentiment", "Confidence"], wrap=True, height=420, interactive=False
 
 
403
  )
404
- plot = gr.Plot(label="Visualization")
405
-
406
- hidden_engine = gr.State(value="—")
407
- hidden_gemini_count = gr.State(value=0)
408
- hidden_fallback_count = gr.State(value=0)
409
-
410
- def _status_text(status_str, engine, gcount, fcount):
411
- stats_md = f"""
412
- <span class="status-badge">🔧 Engine: <b>{engine}</b></span>
413
- <span class="status-badge">✨ Gemini posts: <b>{gcount}</b></span>
414
- <span class="status-badge">🧩 Fallback posts: <b>{fcount}</b></span>
415
- """
416
- return gr.update(value=f"**{status_str}**"), gr.update(value=stats_md, visible=True)
417
 
418
  run_btn.click(
419
- fn=run_analysis,
420
- inputs=[hashtag, n_posts, vis_type, use_gemini_posts, use_gemini_analysis],
421
- outputs=[posts_table, plot, status, hidden_engine, hidden_gemini_count, hidden_fallback_count]
422
- ).then(
423
- fn=_status_text,
424
- inputs=[status, hidden_engine, hidden_gemini_count, hidden_fallback_count],
425
- outputs=[status, stats_row]
426
  )
427
 
428
- # ------------------ Launch ------------------
429
  if __name__ == "__main__":
430
  demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
 
2
  import random
 
 
 
3
  import gradio as gr
 
4
  import plotly.express as px
5
+ from transformers import pipeline
6
+ import google.generativeai as genai
7
 
8
+ # ----------------- CONFIG -----------------
9
+ # Load Gemini API key from environment secrets
10
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ if GEMINI_API_KEY:
13
+ genai.configure(api_key=GEMINI_API_KEY)
14
+ gemini_model = genai.GenerativeModel("gemini-1.5-flash")
15
+ else:
16
+ gemini_model = None
17
 
18
+ # Hugging Face sentiment pipeline
19
+ sentiment_pipeline = pipeline("sentiment-analysis")
 
20
 
21
+ # ----------------- FUNCTIONS -----------------
22
+ def generate_posts(hashtag: str, num_posts: int, use_gemini: bool):
23
+ posts = []
24
+ source = "huggingface"
 
 
 
 
 
 
 
 
 
 
25
 
26
+ if use_gemini and gemini_model:
27
+ prompt = f"Generate {num_posts} realistic social media posts about {hashtag}. Use emojis. Tone should vary (positive, negative, neutral)."
28
  try:
29
+ response = gemini_model.generate_content(prompt)
30
+ text = response.text
31
+ posts = [line.strip("-• ") for line in text.split("\n") if line.strip()][:num_posts]
32
+ source = "gemini"
33
+ except Exception as e:
34
+ posts = [f"⚠️ Gemini API failed: {e}. Falling back to Hugging Face."]
35
+ source = "huggingface"
36
+
37
+ if not posts: # fallback
38
+ template = [
39
+ f"Not sure how I feel about {hashtag} 🤔",
40
+ f"{hashtag} totally failed expectations 😠",
41
+ f"People are talking about {hashtag} everywhere 🌍",
42
+ f"{hashtag} campaign is the best thing this year 🎉",
43
+ f"I'm disappointed with {hashtag} 💔",
44
+ f"Super excited about {hashtag} 🔥",
45
+ f"I love {hashtag}! It's amazing ❤️",
46
+ ]
47
+ posts = random.choices(template, k=num_posts)
48
+
49
+ return posts, source
50
+
51
+
52
+ def analyze_sentiments(hashtag, num_posts, visualization, use_gemini):
53
+ num_posts = min(num_posts, 50) # cap at 50
54
+
55
+ posts, source = generate_posts(hashtag, num_posts, use_gemini)
56
+
57
+ results = []
58
+ for post in posts:
59
+ try:
60
+ result = sentiment_pipeline(post[:512])[0]
61
+ results.append((post, result["label"], round(result["score"], 2)))
62
  except Exception:
63
+ results.append((post, "NEUTRAL", 0.5))
64
+
65
+ # Visualization
66
+ sentiments = [r[1] for r in results]
67
+ fig = None
68
+ if visualization == "Bar":
69
+ fig = px.bar(x=sentiments, title=f"Sentiment Distribution for {hashtag}", labels={"x": "Sentiment", "y": "Count"})
70
+ elif visualization == "Pie":
71
+ fig = px.pie(names=sentiments, title=f"Sentiment Share for {hashtag}")
72
+ elif visualization == "Line":
73
+ fig = px.line(y=[1 if s == "POSITIVE" else -1 if s == "NEGATIVE" else 0 for s in sentiments],
74
+ title=f"Sentiment Rolling Trend for {hashtag}")
75
+
76
+ summary_text = f"✅ Posts generated by **{source.upper()}**\nTotal Posts: {len(results)}"
77
+ return results, fig, summary_text
78
+
79
+ # ----------------- UI -----------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  CUSTOM_CSS = """
81
+ #col-left {
82
+ background: rgba(25, 25, 25, 0.85);
83
+ padding: 20px;
84
+ border-radius: 16px;
85
+ box-shadow: 0 0 20px rgba(0,0,0,0.4);
86
+ animation: fadeInLeft 1s ease;
 
 
87
  }
88
+ #col-right {
89
+ background: rgba(15, 15, 15, 0.85);
90
+ padding: 20px;
91
+ border-radius: 16px;
92
+ box-shadow: 0 0 20px rgba(0,0,0,0.4);
93
+ animation: fadeInRight 1s ease;
 
 
94
  }
95
+ .posts-table {
96
+ max-height: 420px;
97
+ overflow-y: auto;
 
 
 
98
  }
99
+ @keyframes fadeInLeft {
100
+ from {opacity: 0; transform: translateX(-30px);}
101
+ to {opacity: 1; transform: translateX(0);}
 
 
 
 
 
 
 
 
 
102
  }
103
+ @keyframes fadeInRight {
104
+ from {opacity: 0; transform: translateX(30px);}
105
+ to {opacity: 1; transform: translateX(0);}
106
+ }
107
+ button {
108
+ transition: all 0.2s ease;
109
+ }
110
+ button:hover {
111
+ transform: scale(1.05);
112
+ box-shadow: 0 0 12px #ff6600;
113
  }
114
  """
115
 
116
+ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft()) as demo:
117
+ gr.HTML("<h1 style='text-align:center; color:#FF6600;'>🚀 Social Media Sentiment Analyzer</h1>")
118
+ gr.HTML("<p style='text-align:center;'>Stream posts • Analyze moods • Visualize trends</p>")
 
 
 
 
 
119
 
120
  with gr.Row():
121
+ with gr.Column(elem_id="col-left"):
122
+ hashtag = gr.Textbox(label="Enter Hashtag", value="#gla")
123
+ num_posts = gr.Slider(5, 50, value=20, step=1, label="Number of Posts")
124
+ visualization = gr.Dropdown(choices=["Bar", "Pie", "Line"], value="Bar", label="Choose Visualization")
125
+ use_gemini = gr.Checkbox(label="Use Gemini Advanced Analysis", value=False)
126
+ run_btn = gr.Button("🔍 Run Analysis", variant="primary")
127
+
128
+ with gr.Column(elem_id="col-right"):
 
 
 
 
 
129
  posts_table = gr.Dataframe(
130
+ headers=["Post", "Sentiment", "Confidence"],
131
+ interactive=False,
132
+ elem_classes=["posts-table"]
133
  )
134
+ plot_out = gr.Plot()
135
+ summary = gr.Markdown()
 
 
 
 
 
 
 
 
 
 
 
136
 
137
  run_btn.click(
138
+ analyze_sentiments,
139
+ inputs=[hashtag, num_posts, visualization, use_gemini],
140
+ outputs=[posts_table, plot_out, summary]
 
 
 
 
141
  )
142
 
143
+ # ----------------- MAIN -----------------
144
  if __name__ == "__main__":
145
  demo.launch()