Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -6,20 +6,19 @@ import json
|
|
| 6 |
import time
|
| 7 |
|
| 8 |
# --- Page Config ---
|
| 9 |
-
st.set_page_config(page_title="Risk Command Center", page_icon="π¨", layout="wide")
|
| 10 |
|
| 11 |
-
# ---
|
| 12 |
st.markdown("""
|
| 13 |
<style>
|
| 14 |
div[data-testid="stMetricValue"] { font-size: 24px; }
|
| 15 |
-
.risk-card {
|
| 16 |
-
.
|
| 17 |
-
.
|
| 18 |
-
.low-risk { border-left: 5px solid #09ab3b; background-color: #e6f9ec; }
|
| 19 |
</style>
|
| 20 |
""", unsafe_allow_html=True)
|
| 21 |
|
| 22 |
-
# --- κ΅κ° μ€μ (Dual Stream
|
| 23 |
COUNTRY_CONFIG = {
|
| 24 |
"π Asia Pacific": {"South Korea": "kr-kr", "Japan": "jp-jp", "China": "cn-zh", "India": "in-en", "Australia": "au-en"},
|
| 25 |
"π½ North America": {"USA": "us-en", "Canada": "ca-en"},
|
|
@@ -27,79 +26,100 @@ COUNTRY_CONFIG = {
|
|
| 27 |
"π Middle East": {"Israel": "il-en", "Saudi Arabia": "sa-ar", "Turkey": "tr-tr"}
|
| 28 |
}
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
# --- Sidebar (Control Panel) ---
|
| 31 |
with st.sidebar:
|
| 32 |
-
st.header("ποΈ
|
| 33 |
-
|
|
|
|
| 34 |
|
| 35 |
-
st.subheader("Target
|
| 36 |
-
region = st.selectbox("
|
| 37 |
country = st.selectbox("Country", list(COUNTRY_CONFIG[region].keys()))
|
| 38 |
region_code = COUNTRY_CONFIG[region][country]
|
| 39 |
|
| 40 |
st.markdown("---")
|
| 41 |
-
refresh_btn = st.button("
|
| 42 |
-
|
| 43 |
-
st.info(f"Monitoring: {country}\nMode: Dual-Stream (En + Local)")
|
| 44 |
|
| 45 |
-
# --- Main
|
| 46 |
st.title(f"π¨ Risk Command Center: {country}")
|
| 47 |
|
| 48 |
if not api_key:
|
| 49 |
-
st.
|
| 50 |
st.stop()
|
| 51 |
|
| 52 |
if refresh_btn:
|
| 53 |
client = InferenceClient(api_key=api_key)
|
| 54 |
|
| 55 |
-
# [
|
| 56 |
status_text = st.empty()
|
| 57 |
progress_bar = st.progress(0)
|
| 58 |
|
| 59 |
all_news = []
|
| 60 |
try:
|
| 61 |
-
status_text.text("π‘
|
| 62 |
with DDGS() as ddgs:
|
| 63 |
-
# Global
|
| 64 |
for r in ddgs.news(f"{country} controversy", region="wt-wt", safesearch="off", max_results=5):
|
| 65 |
-
r['
|
| 66 |
all_news.append(r)
|
| 67 |
progress_bar.progress(30)
|
| 68 |
|
| 69 |
-
# Local
|
| 70 |
status_text.text(f"π‘ Intercepting Local Signals ({region_code})...")
|
| 71 |
-
for r in ddgs.news(f"{country}", region=region_code, safesearch="off", max_results=
|
| 72 |
-
r['
|
| 73 |
all_news.append(r)
|
| 74 |
progress_bar.progress(60)
|
| 75 |
|
| 76 |
except Exception as e:
|
| 77 |
-
st.error(f"
|
| 78 |
st.stop()
|
| 79 |
|
| 80 |
df = pd.DataFrame(all_news).drop_duplicates(subset=['title'])
|
| 81 |
|
| 82 |
-
# [
|
| 83 |
-
status_text.text("π§ Qwen-72B is
|
| 84 |
|
| 85 |
-
news_feed = "\n".join([f"[{row['
|
| 86 |
|
|
|
|
| 87 |
system_prompt = f"""
|
| 88 |
-
You are a
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
OUTPUT FORMAT
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
"""
|
| 104 |
|
| 105 |
try:
|
|
@@ -109,11 +129,12 @@ if refresh_btn:
|
|
| 109 |
{"role": "system", "content": system_prompt},
|
| 110 |
{"role": "user", "content": news_feed}
|
| 111 |
],
|
| 112 |
-
max_tokens=
|
| 113 |
-
temperature=0.
|
| 114 |
)
|
| 115 |
content = response.choices[0].message.content.replace("```json", "").replace("```", "").strip()
|
| 116 |
-
|
|
|
|
| 117 |
progress_bar.progress(100)
|
| 118 |
time.sleep(0.5)
|
| 119 |
status_text.empty()
|
|
@@ -123,48 +144,67 @@ if refresh_btn:
|
|
| 123 |
st.error(f"Analysis Failed: {e}")
|
| 124 |
st.stop()
|
| 125 |
|
| 126 |
-
# --- [
|
| 127 |
|
| 128 |
-
# 1
|
| 129 |
-
total_risk = sum([r['risk_score'] for r in risks]) // 3
|
| 130 |
kpi1, kpi2, kpi3 = st.columns(3)
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
-
# 2
|
|
|
|
| 139 |
cols = st.columns(3)
|
| 140 |
|
| 141 |
-
for i, risk in enumerate(
|
| 142 |
with cols[i]:
|
| 143 |
-
# 리μ€ν¬ λ 벨μ λ°λ₯Έ μ€νμΌλ§
|
| 144 |
score = risk['risk_score']
|
| 145 |
-
|
| 146 |
-
emoji = "π΄" if score >= 75 else "π " if score >= 50 else "π’"
|
| 147 |
|
| 148 |
-
# μΉ΄λ λμμΈ (Container)
|
| 149 |
with st.container(border=True):
|
| 150 |
-
st.markdown(f"
|
| 151 |
-
st.markdown(f"
|
| 152 |
-
st.caption(f"
|
| 153 |
-
st.markdown("
|
| 154 |
-
st.info(risk['summary'])
|
| 155 |
-
st.markdown(f"**π‘οΈ Action:** {risk['action']}")
|
| 156 |
|
| 157 |
-
# 3
|
| 158 |
st.divider()
|
| 159 |
-
|
| 160 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
|
| 162 |
else:
|
| 163 |
-
|
| 164 |
-
st.info("π μ¬μ΄λλ°μμ 'Refresh Dashboard' λ²νΌμ λλ¬ λͺ¨λν°λ§μ μμνμΈμ.")
|
| 165 |
-
st.markdown("""
|
| 166 |
-
### Dashboard Capabilities
|
| 167 |
-
* **Global + Local Dual Scan:** μμ΄κΆ λ΄μ€(μΈμ )μ νμ§ μΈμ΄ λ΄μ€λ₯Ό λμ λΆμ.
|
| 168 |
-
* **Top 3 Risk Clustering:** λ¨μΌ μ΄μκ° μλ, μ€μλ μ μμ 3κ° μ΄μ λ³λ ¬ νμΆ.
|
| 169 |
-
* **Dashboard View:** μΉ΄λν UIλ‘ νλμ 리μ€ν¬ νμ
.
|
| 170 |
-
""")
|
|
|
|
| 6 |
import time
|
| 7 |
|
| 8 |
# --- Page Config ---
|
| 9 |
+
st.set_page_config(page_title="Risk Command Center Pro", page_icon="π¨", layout="wide")
|
| 10 |
|
| 11 |
+
# --- CSS (μΉ΄λ + 리ν¬νΈ μ€νμΌ) ---
|
| 12 |
st.markdown("""
|
| 13 |
<style>
|
| 14 |
div[data-testid="stMetricValue"] { font-size: 24px; }
|
| 15 |
+
.risk-card { background-color: #f9f9f9; padding: 15px; border-radius: 10px; border: 1px solid #ddd; margin-bottom: 10px; }
|
| 16 |
+
.report-box { background-color: #eef2f5; padding: 20px; border-radius: 10px; margin-top: 10px; }
|
| 17 |
+
.h-risk { color: #ff4b4b; font-weight: bold; }
|
|
|
|
| 18 |
</style>
|
| 19 |
""", unsafe_allow_html=True)
|
| 20 |
|
| 21 |
+
# --- κ΅κ° μ€μ (Dual Stream) ---
|
| 22 |
COUNTRY_CONFIG = {
|
| 23 |
"π Asia Pacific": {"South Korea": "kr-kr", "Japan": "jp-jp", "China": "cn-zh", "India": "in-en", "Australia": "au-en"},
|
| 24 |
"π½ North America": {"USA": "us-en", "Canada": "ca-en"},
|
|
|
|
| 26 |
"π Middle East": {"Israel": "il-en", "Saudi Arabia": "sa-ar", "Turkey": "tr-tr"}
|
| 27 |
}
|
| 28 |
|
| 29 |
+
# --- 1. API Key Auto-Load (Secrets) ---
|
| 30 |
+
try:
|
| 31 |
+
# Streamlit Cloud Settings > Secretsμ μ μ₯λ ν€λ₯Ό κ°μ Έμ΅λλ€.
|
| 32 |
+
api_key = st.secrets["HF_KEY"]
|
| 33 |
+
except (FileNotFoundError, KeyError):
|
| 34 |
+
# λ‘컬 ν
μ€νΈκ±°λ ν€κ° μμ κ²½μ°λ₯Ό λλΉν΄ μλ μ
λ ₯μ°½ λ¨κ²¨λ (μ νμ¬ν)
|
| 35 |
+
api_key = st.sidebar.text_input("HF Token (Secrets not found)", type="password")
|
| 36 |
+
|
| 37 |
# --- Sidebar (Control Panel) ---
|
| 38 |
with st.sidebar:
|
| 39 |
+
st.header("ποΈ Command Center")
|
| 40 |
+
if api_key:
|
| 41 |
+
st.success("π Security Key Loaded")
|
| 42 |
|
| 43 |
+
st.subheader("Target Scope")
|
| 44 |
+
region = st.selectbox("Region", list(COUNTRY_CONFIG.keys()))
|
| 45 |
country = st.selectbox("Country", list(COUNTRY_CONFIG[region].keys()))
|
| 46 |
region_code = COUNTRY_CONFIG[region][country]
|
| 47 |
|
| 48 |
st.markdown("---")
|
| 49 |
+
refresh_btn = st.button("π Run Full Analysis", type="primary", use_container_width=True)
|
| 50 |
+
st.caption("Mode: Deep Intelligence (Global + Local)")
|
|
|
|
| 51 |
|
| 52 |
+
# --- Main Logic ---
|
| 53 |
st.title(f"π¨ Risk Command Center: {country}")
|
| 54 |
|
| 55 |
if not api_key:
|
| 56 |
+
st.error("π« HF_KEY not found in Secrets. Please add it in Streamlit Settings.")
|
| 57 |
st.stop()
|
| 58 |
|
| 59 |
if refresh_btn:
|
| 60 |
client = InferenceClient(api_key=api_key)
|
| 61 |
|
| 62 |
+
# [Step 1] Dual-Stream Data Collection
|
| 63 |
status_text = st.empty()
|
| 64 |
progress_bar = st.progress(0)
|
| 65 |
|
| 66 |
all_news = []
|
| 67 |
try:
|
| 68 |
+
status_text.text("π‘ Scanning Global Spectrum...")
|
| 69 |
with DDGS() as ddgs:
|
| 70 |
+
# 1. Global Stream
|
| 71 |
for r in ddgs.news(f"{country} controversy", region="wt-wt", safesearch="off", max_results=5):
|
| 72 |
+
r['stream'] = 'Global π'
|
| 73 |
all_news.append(r)
|
| 74 |
progress_bar.progress(30)
|
| 75 |
|
| 76 |
+
# 2. Local Stream
|
| 77 |
status_text.text(f"π‘ Intercepting Local Signals ({region_code})...")
|
| 78 |
+
for r in ddgs.news(f"{country}", region=region_code, safesearch="off", max_results=7):
|
| 79 |
+
r['stream'] = 'Local π '
|
| 80 |
all_news.append(r)
|
| 81 |
progress_bar.progress(60)
|
| 82 |
|
| 83 |
except Exception as e:
|
| 84 |
+
st.error(f"Intel Collection Failed: {e}")
|
| 85 |
st.stop()
|
| 86 |
|
| 87 |
df = pd.DataFrame(all_news).drop_duplicates(subset=['title'])
|
| 88 |
|
| 89 |
+
# [Step 2] Qwen Deep Analysis (One-Shot Complex Prompt)
|
| 90 |
+
status_text.text("π§ Qwen-72B is processing Deep Intelligence Report...")
|
| 91 |
|
| 92 |
+
news_feed = "\n".join([f"[{row['stream']}] {row['title']} ({row['source']})" for i, row in df.iterrows()])
|
| 93 |
|
| 94 |
+
# ν둬ννΈ: 리μ€νΈ(Cards)μ 리ν¬νΈ(Deep Analysis)λ₯Ό ν λ²μ μμ²
|
| 95 |
system_prompt = f"""
|
| 96 |
+
You are a Strategic Risk Analyst. Analyze the news for {country}.
|
| 97 |
+
Generate a JSON object with two parts: "top_risks" (List of 3) and "deep_report" (Object).
|
| 98 |
+
|
| 99 |
+
OUTPUT FORMAT (Strict JSON):
|
| 100 |
+
{{
|
| 101 |
+
"viral_velocity": 0-100 (Score based on number of sources and urgency),
|
| 102 |
+
"top_risks": [
|
| 103 |
+
{{
|
| 104 |
+
"title": "Short Headline",
|
| 105 |
+
"risk_score": 0-100,
|
| 106 |
+
"platform": "TikTok/YouTube/Meta",
|
| 107 |
+
"violation": "Hate/Violence/Misinfo...",
|
| 108 |
+
"action": "Brief Action"
|
| 109 |
+
}},
|
| 110 |
+
... (3 items)
|
| 111 |
+
],
|
| 112 |
+
"deep_report": {{
|
| 113 |
+
"executive_summary": "Comprehensive summary of the situation (3-4 sentences).",
|
| 114 |
+
"local_vs_global": "Contrast between local and global media tone.",
|
| 115 |
+
"platform_strategy": {{
|
| 116 |
+
"TikTok": "Specific moderation advice",
|
| 117 |
+
"YouTube": "Specific moderation advice",
|
| 118 |
+
"Meta": "Specific moderation advice"
|
| 119 |
+
}},
|
| 120 |
+
"critical_keywords": ["Tag1", "Tag2"]
|
| 121 |
+
}}
|
| 122 |
+
}}
|
| 123 |
"""
|
| 124 |
|
| 125 |
try:
|
|
|
|
| 129 |
{"role": "system", "content": system_prompt},
|
| 130 |
{"role": "user", "content": news_feed}
|
| 131 |
],
|
| 132 |
+
max_tokens=2500,
|
| 133 |
+
temperature=0.25
|
| 134 |
)
|
| 135 |
content = response.choices[0].message.content.replace("```json", "").replace("```", "").strip()
|
| 136 |
+
data = json.loads(content)
|
| 137 |
+
|
| 138 |
progress_bar.progress(100)
|
| 139 |
time.sleep(0.5)
|
| 140 |
status_text.empty()
|
|
|
|
| 144 |
st.error(f"Analysis Failed: {e}")
|
| 145 |
st.stop()
|
| 146 |
|
| 147 |
+
# --- [UI Render] ---
|
| 148 |
|
| 149 |
+
# [Section 1] KPI Board (νμ μ± μ¦ν μ§ν λΆν!)
|
|
|
|
| 150 |
kpi1, kpi2, kpi3 = st.columns(3)
|
| 151 |
+
|
| 152 |
+
# Velocity κ²μ΄μ§
|
| 153 |
+
vel_score = data['viral_velocity']
|
| 154 |
+
vel_color = "inverse" if vel_score > 70 else "normal"
|
| 155 |
+
vel_label = "π₯ Viral Outbreak" if vel_score > 80 else "π Climbing" if vel_score > 50 else "π’ Stable"
|
| 156 |
+
|
| 157 |
+
kpi1.metric("Viral Velocity", f"{vel_score}/100", vel_label, delta_color=vel_color)
|
| 158 |
+
kpi2.metric("Active Sources", f"{len(df)} Channels")
|
| 159 |
+
kpi3.metric("Primary Risk", data['top_risks'][0]['violation'])
|
| 160 |
|
| 161 |
+
# [Section 2] 3-Card Overview
|
| 162 |
+
st.subheader("β‘ High-Priority Threats (Top 3)")
|
| 163 |
cols = st.columns(3)
|
| 164 |
|
| 165 |
+
for i, risk in enumerate(data['top_risks']):
|
| 166 |
with cols[i]:
|
|
|
|
| 167 |
score = risk['risk_score']
|
| 168 |
+
border_color = "#ff4b4b" if score >= 80 else "#ffa500" if score >= 50 else "#4caf50"
|
|
|
|
| 169 |
|
|
|
|
| 170 |
with st.container(border=True):
|
| 171 |
+
st.markdown(f"<h3 style='color:{border_color}'>{risk['risk_score']} <span style='font-size:16px; color:gray'>/ 100</span></h3>", unsafe_allow_html=True)
|
| 172 |
+
st.markdown(f"**{risk['title']}**")
|
| 173 |
+
st.caption(f"{risk['platform']} | {risk['violation']}")
|
| 174 |
+
st.markdown(f"π‘οΈ *{risk['action']}*")
|
|
|
|
|
|
|
| 175 |
|
| 176 |
+
# [Section 3] Deep Intelligence Report (μμΈ λΆμ λΆν!)
|
| 177 |
st.divider()
|
| 178 |
+
st.markdown("### π Deep Intelligence Report")
|
| 179 |
+
|
| 180 |
+
report = data['deep_report']
|
| 181 |
+
|
| 182 |
+
# νμΌλ‘ κΉλνκ² μ 리
|
| 183 |
+
tab1, tab2, tab3, tab4 = st.tabs(["π Executive Summary", "βοΈ Local vs Global", "π‘οΈ Platform Strategy", "π° Evidence"])
|
| 184 |
+
|
| 185 |
+
with tab1:
|
| 186 |
+
st.info(report['executive_summary'])
|
| 187 |
+
st.markdown("#### π¨ Critical Keywords")
|
| 188 |
+
st.write(" ".join([f"`#{k}`" for k in report['critical_keywords']]))
|
| 189 |
+
|
| 190 |
+
with tab2:
|
| 191 |
+
st.success(f"π‘ **Insight:** {report['local_vs_global']}")
|
| 192 |
+
st.caption("This analysis compares the tone of domestic media versus international coverage.")
|
| 193 |
+
|
| 194 |
+
with tab3:
|
| 195 |
+
p_cols = st.columns(3)
|
| 196 |
+
with p_cols[0]:
|
| 197 |
+
st.markdown("**π΅ TikTok Strategy**")
|
| 198 |
+
st.warning(report['platform_strategy'].get('TikTok', '-'))
|
| 199 |
+
with p_cols[1]:
|
| 200 |
+
st.markdown("**βΆοΈ YouTube Strategy**")
|
| 201 |
+
st.warning(report['platform_strategy'].get('YouTube', '-'))
|
| 202 |
+
with p_cols[2]:
|
| 203 |
+
st.markdown("**βΎοΈ Meta Strategy**")
|
| 204 |
+
st.warning(report['platform_strategy'].get('Meta', '-'))
|
| 205 |
+
|
| 206 |
+
with tab4:
|
| 207 |
+
st.dataframe(df[['stream', 'title', 'source', 'date']], use_container_width=True, hide_index=True)
|
| 208 |
|
| 209 |
else:
|
| 210 |
+
st.info("π Ready to Scan. Press 'Run Full Analysis' in the sidebar.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|