benjamin5607 commited on
Commit
11f5974
Β·
verified Β·
1 Parent(s): 9c01100

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +117 -77
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
- # --- Custom CSS (μΉ΄λ“œ λ””μžμΈ & λŒ€μ‹œλ³΄λ“œ λŠλ‚Œ) ---
12
  st.markdown("""
13
  <style>
14
  div[data-testid="stMetricValue"] { font-size: 24px; }
15
- .risk-card { border: 1px solid #ddd; padding: 15px; border-radius: 10px; margin-bottom: 10px; }
16
- .high-risk { border-left: 5px solid #ff4b4b; background-color: #ffeaea; }
17
- .med-risk { border-left: 5px solid #ffa500; background-color: #fff5e6; }
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("πŸŽ›οΈ Control Panel")
33
- api_key = st.text_input("HF Token", type="password", help="Hugging Face API Token")
 
34
 
35
- st.subheader("Target Region")
36
- region = st.selectbox("Continent", list(COUNTRY_CONFIG.keys()))
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("πŸ”„ Refresh Dashboard", type="primary", use_container_width=True)
42
-
43
- st.info(f"Monitoring: {country}\nMode: Dual-Stream (En + Local)")
44
 
45
- # --- Main Dashboard Area ---
46
  st.title(f"🚨 Risk Command Center: {country}")
47
 
48
  if not api_key:
49
- st.warning("⚠️ μ‚¬μ΄λ“œλ°”μ— Hugging Face Token을 μž…λ ₯ν•˜κ³  'Refresh'λ₯Ό λˆŒλŸ¬μ£Όμ„Έμš”.")
50
  st.stop()
51
 
52
  if refresh_btn:
53
  client = InferenceClient(api_key=api_key)
54
 
55
- # [Process 1] Data Collection (Global + Local)
56
  status_text = st.empty()
57
  progress_bar = st.progress(0)
58
 
59
  all_news = []
60
  try:
61
- status_text.text("πŸ“‘ Connecting to Global Satellites...")
62
  with DDGS() as ddgs:
63
- # Global Search
64
  for r in ddgs.news(f"{country} controversy", region="wt-wt", safesearch="off", max_results=5):
65
- r['type'] = 'Global'
66
  all_news.append(r)
67
  progress_bar.progress(30)
68
 
69
- # Local Search
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=6):
72
- r['type'] = 'Local'
73
  all_news.append(r)
74
  progress_bar.progress(60)
75
 
76
  except Exception as e:
77
- st.error(f"Data Collection Error: {e}")
78
  st.stop()
79
 
80
  df = pd.DataFrame(all_news).drop_duplicates(subset=['title'])
81
 
82
- # [Process 2] Qwen Multi-Issue Analysis
83
- status_text.text("🧠 Qwen-72B is profiling top 3 risks...")
84
 
85
- news_feed = "\n".join([f"[{row['type']}] {row['title']} ({row['date']})" for i, row in df.iterrows()])
86
 
 
87
  system_prompt = f"""
88
- You are a Head of Trust & Safety. Analyze the news for {country}.
89
- Identify the TOP 3 DISTINCT social media risks.
90
-
91
- OUTPUT FORMAT: Strictly a JSON LIST of 3 objects.
92
- [
93
- {{
94
- "title": "Short Headline (Max 10 words)",
95
- "risk_score": 0-100,
96
- "platform": "Primary Platform (TikTok/YouTube/Meta)",
97
- "violation": "Main Violation Type (e.g. Hate Speech)",
98
- "summary": "2 sentence briefing.",
99
- "action": "One specific moderation action."
100
- }},
101
- ... (Total 3 items)
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=2000,
113
- temperature=0.3
114
  )
115
  content = response.choices[0].message.content.replace("```json", "").replace("```", "").strip()
116
- risks = json.loads(content)
 
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
- # --- [Dashboard View] ---
127
 
128
- # 1. Top Level Metrics
129
- total_risk = sum([r['risk_score'] for r in risks]) // 3
130
  kpi1, kpi2, kpi3 = st.columns(3)
131
- kpi1.metric("Avg Risk Level", f"{total_risk}/100", delta="High" if total_risk > 60 else "Normal", delta_color="inverse")
132
- kpi2.metric("Active Signals", f"{len(df)} Articles")
133
- kpi3.metric("Primary Threat", risks[0]['violation'])
134
-
135
- st.divider()
136
- st.subheader("πŸ”₯ Top 3 Critical Issues")
 
 
 
137
 
138
- # 2. 3-Column Card Layout
 
139
  cols = st.columns(3)
140
 
141
- for i, risk in enumerate(risks):
142
  with cols[i]:
143
- # 리슀크 λ ˆλ²¨μ— λ”°λ₯Έ μŠ€νƒ€μΌλ§
144
  score = risk['risk_score']
145
- color_class = "high-risk" if score >= 75 else "med-risk" if score >= 50 else "low-risk"
146
- emoji = "πŸ”΄" if score >= 75 else "🟠" if score >= 50 else "🟒"
147
 
148
- # μΉ΄λ“œ λ””μžμΈ (Container)
149
  with st.container(border=True):
150
- st.markdown(f"**{emoji} Risk Score: {score}**")
151
- st.markdown(f"### {risk['title']}")
152
- st.caption(f"**{risk['platform']}** | {risk['violation']}")
153
- st.markdown("---")
154
- st.info(risk['summary'])
155
- st.markdown(f"**πŸ›‘οΈ Action:** {risk['action']}")
156
 
157
- # 3. Data Table (Bottom)
158
  st.divider()
159
- with st.expander("πŸ“° Raw Intelligence Feed (Evidence)", expanded=False):
160
- st.dataframe(df[['type', 'title', 'source', 'date']], use_container_width=True, hide_index=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.")