NKessler commited on
Commit
308eef3
·
verified ·
1 Parent(s): d7e996b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -25
app.py CHANGED
@@ -1,4 +1,7 @@
 
1
  import typing
 
 
2
  import streamlit as st
3
  from transformers import pipeline
4
  import yake
@@ -6,7 +9,6 @@ import yake
6
  MAX_TEXT_LENGTH = 1500
7
  CANDIDATE_TONES = ["alarmist", "objective", "defensive", "optimistic", "critical"]
8
 
9
-
10
  ARTICLE_A = """
11
  Global leaders achieved a historic breakthrough today, signing a comprehensive climate accord aimed at drastically slashing carbon emissions by 2030. Environmental advocates are celebrating the mandate, which forces heavy-polluting industries to finally take accountability for their ecological damage. While corporations warn of transition costs, scientists emphasize that failing to act now would result in catastrophic, irreversible damage to our planet's fragile ecosystems.
12
  """
@@ -39,7 +41,6 @@ def _load_nlp_models() -> typing.Dict[str, typing.Any]:
39
 
40
  def analyze_article(text: str) -> dict:
41
  models = _load_nlp_models()
42
-
43
  safe_text = text[:MAX_TEXT_LENGTH]
44
 
45
  sentiment_result = models["sentiment"](safe_text)[0]
@@ -47,56 +48,138 @@ def analyze_article(text: str) -> dict:
47
  keyword_results = models["keyword"].extract_keywords(safe_text)
48
 
49
  is_positive = sentiment_result["label"] == "POSITIVE"
50
- sentiment_score = sentiment_result["score"] if is_positive else -sentiment_result["score"]
 
 
 
 
 
 
 
51
 
52
  extracted_keywords = [kw[0] for kw in keyword_results]
53
 
54
  return {
55
- "sentiment_score": round(sentiment_score, 2),
56
  "primary_tone": tone_result["labels"][0],
 
57
  "keywords": extracted_keywords,
58
  }
59
 
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  st.set_page_config(page_title="FrameVis MVP", layout="wide")
62
  st.title("FrameVis: Media Framing Analyzer")
63
- st.markdown("Compare how different news sources frame the same event using free, open-source NLP models.")
64
 
65
- with st.spinner("Waking up NLP models (this takes a moment on startup)..."):
66
  _load_nlp_models()
67
 
68
- st.markdown("### The Event: 2026 Global Climate Summit Accord")
 
69
 
70
  col1, col2 = st.columns(2)
71
 
72
  with col1:
73
- st.subheader("Source A (The 'Eco-Urgency' Frame)")
74
- st.info(ARTICLE_A)
75
- should_analyze_a = st.button("Analyze Source A")
 
 
76
 
77
  with col2:
78
- st.subheader("Source B (The 'Economic-Cost' Frame)")
79
- st.warning(ARTICLE_B)
80
- should_analyze_b = st.button("Analyze Source B")
 
 
81
 
82
  st.divider()
83
 
84
  if should_analyze_a or should_analyze_b:
85
- st.markdown("### Analysis Results")
86
  res_col1, res_col2 = st.columns(2)
87
 
88
- if should_analyze_a:
89
  with st.spinner("Analyzing Source A..."):
90
- results_a = analyze_article(ARTICLE_A)
91
  with res_col1:
92
- st.metric("Sentiment Score (-1 to 1)", results_a["sentiment_score"])
93
- st.write(f"**Primary Tone:** {results_a['primary_tone'].title()}")
94
- st.write(f"**Loaded Keywords:** {', '.join(results_a['keywords'])}")
95
-
96
- if should_analyze_b:
 
 
 
 
 
 
 
 
 
 
97
  with st.spinner("Analyzing Source B..."):
98
- results_b = analyze_article(ARTICLE_B)
99
  with res_col2:
100
- st.metric("Sentiment Score (-1 to 1)", results_b["sentiment_score"])
101
- st.write(f"**Primary Tone:** {results_b['primary_tone'].title()}")
102
- st.write(f"**Loaded Keywords:** {', '.join(results_b['keywords'])}")
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
  import typing
3
+
4
+ import plotly.graph_objects as go
5
  import streamlit as st
6
  from transformers import pipeline
7
  import yake
 
9
  MAX_TEXT_LENGTH = 1500
10
  CANDIDATE_TONES = ["alarmist", "objective", "defensive", "optimistic", "critical"]
11
 
 
12
  ARTICLE_A = """
13
  Global leaders achieved a historic breakthrough today, signing a comprehensive climate accord aimed at drastically slashing carbon emissions by 2030. Environmental advocates are celebrating the mandate, which forces heavy-polluting industries to finally take accountability for their ecological damage. While corporations warn of transition costs, scientists emphasize that failing to act now would result in catastrophic, irreversible damage to our planet's fragile ecosystems.
14
  """
 
41
 
42
  def analyze_article(text: str) -> dict:
43
  models = _load_nlp_models()
 
44
  safe_text = text[:MAX_TEXT_LENGTH]
45
 
46
  sentiment_result = models["sentiment"](safe_text)[0]
 
48
  keyword_results = models["keyword"].extract_keywords(safe_text)
49
 
50
  is_positive = sentiment_result["label"] == "POSITIVE"
51
+ sentiment_score = (
52
+ sentiment_result["score"] if is_positive else -sentiment_result["score"]
53
+ )
54
+
55
+ tone_scores = {
56
+ label: score
57
+ for label, score in zip(tone_result["labels"], tone_result["scores"])
58
+ }
59
 
60
  extracted_keywords = [kw[0] for kw in keyword_results]
61
 
62
  return {
63
+ "sentiment_score": sentiment_score,
64
  "primary_tone": tone_result["labels"][0],
65
+ "tone_scores": tone_scores,
66
  "keywords": extracted_keywords,
67
  }
68
 
69
 
70
+ def _create_sentiment_gauge(score: float, title: str) -> go.Figure:
71
+ fig = go.Figure(
72
+ go.Indicator(
73
+ mode="gauge+number",
74
+ value=score,
75
+ domain={"x": [0, 1], "y": [0, 1]},
76
+ title={"text": title, "font": {"size": 18}},
77
+ gauge={
78
+ "axis": {"range": [-1, 1], "tickwidth": 1},
79
+ "bar": {"color": "black"},
80
+ "steps": [
81
+ {"range": [-1, -0.3], "color": "lightpink"},
82
+ {"range": [-0.3, 0.3], "color": "lightgray"},
83
+ {"range": [0.3, 1], "color": "lightgreen"},
84
+ ],
85
+ },
86
+ )
87
+ )
88
+ fig.update_layout(height=250, margin=dict(l=20, r=20, t=40, b=20))
89
+ return fig
90
+
91
+
92
+ def _create_tone_bar_chart(tone_scores: typing.Dict[str, float]) -> go.Figure:
93
+ labels = list(tone_scores.keys())
94
+ values = list(tone_scores.values())
95
+
96
+ fig = go.Figure(go.Bar(x=values, y=labels, orientation="h", marker_color="royalblue"))
97
+ fig.update_layout(
98
+ title="Emotional Tone Distribution",
99
+ xaxis_title="Confidence",
100
+ yaxis_title="Tone",
101
+ height=250,
102
+ margin=dict(l=20, r=20, t=40, b=20),
103
+ yaxis={"categoryorder": "total ascending"},
104
+ )
105
+ return fig
106
+
107
+
108
+ def _highlight_keywords(text: str, keywords: typing.List[str]) -> str:
109
+ highlighted_text = text
110
+ for kw in keywords:
111
+ pattern = re.compile(rf"\b({re.escape(kw)})\b", re.IGNORECASE)
112
+ highlighted_text = pattern.sub(
113
+ r"<span style='background-color: #ffcc00; padding: 2px; border-radius: 3px;'>\1</span>",
114
+ highlighted_text,
115
+ )
116
+ return highlighted_text
117
+
118
+
119
  st.set_page_config(page_title="FrameVis MVP", layout="wide")
120
  st.title("FrameVis: Media Framing Analyzer")
121
+ st.markdown("Compare how different news sources frame the same event using NLP.")
122
 
123
+ with st.spinner("Waking up NLP models..."):
124
  _load_nlp_models()
125
 
126
+ st.markdown("### Input Articles")
127
+ st.markdown("Paste custom articles below or use the default samples to see the analysis.")
128
 
129
  col1, col2 = st.columns(2)
130
 
131
  with col1:
132
+ st.subheader("Source A")
133
+ user_article_a = st.text_area(
134
+ "Paste Article A Text:", value=ARTICLE_A.strip(), height=200
135
+ )
136
+ should_analyze_a = st.button("Analyze Source A", use_container_width=True)
137
 
138
  with col2:
139
+ st.subheader("Source B")
140
+ user_article_b = st.text_area(
141
+ "Paste Article B Text:", value=ARTICLE_B.strip(), height=200
142
+ )
143
+ should_analyze_b = st.button("Analyze Source B", use_container_width=True)
144
 
145
  st.divider()
146
 
147
  if should_analyze_a or should_analyze_b:
148
+ st.markdown("### Visual Analytics Results")
149
  res_col1, res_col2 = st.columns(2)
150
 
151
+ if should_analyze_a and user_article_a:
152
  with st.spinner("Analyzing Source A..."):
153
+ results_a = analyze_article(user_article_a)
154
  with res_col1:
155
+ st.plotly_chart(
156
+ _create_sentiment_gauge(results_a["sentiment_score"], "Sentiment"),
157
+ use_container_width=True,
158
+ )
159
+ st.plotly_chart(
160
+ _create_tone_bar_chart(results_a["tone_scores"]),
161
+ use_container_width=True,
162
+ )
163
+ st.markdown("**Highlighted Text (Loaded Keywords):**")
164
+ annotated_text = _highlight_keywords(
165
+ user_article_a, results_a["keywords"]
166
+ )
167
+ st.markdown(f"> {annotated_text}", unsafe_allow_html=True)
168
+
169
+ if should_analyze_b and user_article_b:
170
  with st.spinner("Analyzing Source B..."):
171
+ results_b = analyze_article(user_article_b)
172
  with res_col2:
173
+ st.plotly_chart(
174
+ _create_sentiment_gauge(results_b["sentiment_score"], "Sentiment"),
175
+ use_container_width=True,
176
+ )
177
+ st.plotly_chart(
178
+ _create_tone_bar_chart(results_b["tone_scores"]),
179
+ use_container_width=True,
180
+ )
181
+ st.markdown("**Highlighted Text (Loaded Keywords):**")
182
+ annotated_text = _highlight_keywords(
183
+ user_article_b, results_b["keywords"]
184
+ )
185
+ st.markdown(f"> {annotated_text}", unsafe_allow_html=True)