Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
extracted_keywords = [kw[0] for kw in keyword_results]
|
| 53 |
|
| 54 |
return {
|
| 55 |
-
"sentiment_score":
|
| 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
|
| 64 |
|
| 65 |
-
with st.spinner("Waking up NLP models
|
| 66 |
_load_nlp_models()
|
| 67 |
|
| 68 |
-
st.markdown("###
|
|
|
|
| 69 |
|
| 70 |
col1, col2 = st.columns(2)
|
| 71 |
|
| 72 |
with col1:
|
| 73 |
-
st.subheader("Source A
|
| 74 |
-
st.
|
| 75 |
-
|
|
|
|
|
|
|
| 76 |
|
| 77 |
with col2:
|
| 78 |
-
st.subheader("Source B
|
| 79 |
-
st.
|
| 80 |
-
|
|
|
|
|
|
|
| 81 |
|
| 82 |
st.divider()
|
| 83 |
|
| 84 |
if should_analyze_a or should_analyze_b:
|
| 85 |
-
st.markdown("###
|
| 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(
|
| 91 |
with res_col1:
|
| 92 |
-
st.
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
with st.spinner("Analyzing Source B..."):
|
| 98 |
-
results_b = analyze_article(
|
| 99 |
with res_col2:
|
| 100 |
-
st.
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|