Spaces:
Sleeping
Sleeping
File size: 14,367 Bytes
386c921 4e66bcd 7cf2014 386c921 fe9ebf1 231e179 969519b adfef13 386c921 f50adf2 386c921 e5ff858 df3a179 d7ade7a df3a179 b57e706 11f5974 26cc62c 7cf2014 26cc62c 9d0be3c 7cf2014 9d0be3c 7cf2014 9d0be3c 23aad06 dc8071d e5ff858 dc8071d e5ff858 f50adf2 7cf2014 9d0be3c 7cf2014 9d0be3c 7cf2014 9d0be3c 7cf2014 9d0be3c 7cf2014 9d0be3c c0b64b1 ed17923 42eb666 91b6080 231e179 ed17923 969519b ed17923 2edfdfa 969519b 7cf2014 969519b 3b1954a 781a11b 969519b 3b1954a 969519b dc8071d 969519b 2d6d3d4 969519b 7cf2014 f50adf2 fa1297d f50adf2 fa1297d 3cc37d8 adfef13 7cf2014 f50adf2 f3b0f37 9d0be3c 7cf2014 9d0be3c 7cf2014 9d0be3c 7cf2014 9d0be3c 8ab9bfc 969519b dc8071d 969519b 7cf2014 dc8071d f50adf2 969519b f50adf2 06690f0 21cf7f9 adfef13 2d6d3d4 f50adf2 e5ff858 f50adf2 e5ff858 f50adf2 969519b f50adf2 969519b 9d0be3c 7cf2014 9d0be3c 7cf2014 9d0be3c 7cf2014 9d0be3c 7cf2014 9d0be3c d131605 f50adf2 8ab9bfc f50adf2 7cf2014 f50adf2 f3b0f37 9d0be3c 7cf2014 9d0be3c 969519b 9d0be3c f50adf2 9d0be3c f50adf2 9d0be3c 7cf2014 9d0be3c 969519b dc8071d e5ff858 dc8071d f50adf2 dc8071d c246c73 f50adf2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 | import streamlit as st
import feedparser
import urllib.parse
from groq import Groq, RateLimitError
import pandas as pd
import json
import os
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime, timedelta
# --- 1. Page Config (Mobile Optimized) ---
st.set_page_config(page_title="Omni-Watch T&S Master", page_icon="π‘οΈ", layout="wide")
# --- 2. API Key Load ---
api_key = os.environ.get("GROQ_API_KEY")
if not api_key:
try: api_key = st.secrets["GROQ_API_KEY"]
except: pass
if not api_key:
st.error("π GROQ_API_KEY Missing. Please check your deployment settings.")
st.stop()
client = Groq(api_key=api_key)
# π¨ [CRITICAL] Robust Model Hierarchy (Auto-Switching)
# 429 μλ¬λ 400(λͺ¨λΈ μ’
λ£) μλ¬ λ°μ μ, μλμΌλ‘ λ€μ μμ λͺ¨λΈλ‘ μ ννμ¬ μ€λ¨ μμ΄ λΆμν©λλ€.
MODEL_HIERARCHY = [
"llama-3.3-70b-versatile", # 1μμ: μ΅μ SOTA λͺ¨λΈ
"llama-3.1-70b-versatile", # 2μμ: κ³ μ±λ₯ λ°±μ
"mixtral-8x7b-32768", # 3μμ: μμ μ± μμ£Ό
"llama-3.1-8b-instant" # 4μμ: μ΄κ³ μ λΉμμ©
]
# --- 3. Configuration ---
REGION_MAP = {
"Asia & Pacific": ["KR", "JP", "CN", "VN", "IN", "AU"],
"Europe": ["GB", "FR", "DE", "UA", "RU"],
"Americas": ["US", "CA", "BR", "MX"],
"ME & Africa": ["IL", "SA", "AE", "TR"]
}
# --- 4. Core Functions ---
# [Smart AI Wrapper] μλ¬ νΈλ€λ§ λ° λͺ¨λΈ μλ μ ν λ‘μ§
def get_ai_response(system_msg, user_content, json_mode=True):
for model in MODEL_HIERARCHY:
try:
kwargs = {
"model": model,
"messages": [{"role": "system", "content": system_msg}, {"role": "user", "content": user_content}],
}
if json_mode:
kwargs["response_format"] = {"type": "json_object"}
res = client.chat.completions.create(**kwargs)
return res
except RateLimitError:
continue # νλ μ΄κ³Ό μ λ€μ λͺ¨λΈ μλ
except Exception as e:
# λͺ¨λΈ μ’
λ£(400)λ μ°Ύμ μ μμ(404) μλ¬ μμλ λ€μ λͺ¨λΈ μλ
if "model_decommissioned" in str(e) or "404" in str(e) or "400" in str(e):
continue
st.error(f"β οΈ Error with {model}: {e}") # κ·Έ μΈ μΉλͺ
μ μλ¬λ μΆλ ₯
return None
st.error("π¨ All AI models are currently unavailable. Please check API Status.")
return None
def fetch_extensive_news(query, geo="US", limit=60, period="7d"):
time_filter = f" when:{period}"
encoded = urllib.parse.quote(query + time_filter)
url = f"https://news.google.com/rss/search?q={encoded}&hl=en&gl={geo}&ceid={geo}:en"
feed = feedparser.parse(url)
articles = []
for e in feed.entries[:limit]:
articles.append({"title": e.title, "source": e.source.title if 'source' in e else "G-News", "link": e.link})
return articles
def render_gauge(score, title):
# μμΈ λΆμμ© κ²μ΄μ§ μ°¨νΈ (μλ£Έ λ―Έμ¬μ©)
fig = go.Figure(go.Indicator(
mode = "gauge+number",
value = score,
domain = {'x': [0, 1], 'y': [0, 1]},
title = {'text': title, 'font': {'size': 18, 'color': "#FF4B4B"}},
gauge = {
'axis': {'range': [0, 100], 'tickwidth': 1},
'bar': {'color': "#FF4B4B"},
'steps': [{'range': [0, 100], 'color': "#ffebee"}],
}
))
fig.update_layout(height=180, margin=dict(l=10, r=10, t=40, b=10), paper_bgcolor="rgba(0,0,0,0)")
return fig
# --- 5. π¨ GLOBAL WAR ROOM (Policy Focused, Numeric Only) ---
st.title("π¨ OMNI-WATCH: T&S WAR ROOM")
st.caption(f"Policy Risk Monitoring: {datetime.now().strftime('%H:%M:%S')} UTC")
if st.button("π Refresh"):
if 'hot_issues' in st.session_state: del st.session_state.hot_issues
if 'hot_issues' not in st.session_state:
with st.spinner("Scanning 12h Global Feeds for Violations..."):
# κ²μμ΄: T&S μλ° κ°λ₯μ±μ΄ λμ ν€μλ
raw_news = fetch_extensive_news("violence OR hate speech OR disinformation OR scandal OR protest", limit=40, period="12h")
if not raw_news:
st.warning("No critical incidents found in the last 12h.")
st.session_state.hot_issues = []
else:
news_context = "\n".join([f"Event: {n['title']}" for n in raw_news])
# PROMPT: μ¬κ±΄ κ°μμ μλ° μ¬νμ λͺ
νν ꡬλΆ
system_msg = """
Identify TOP 3 incidents with highest 'Trust & Safety' risk.
The 'summary' array MUST follow this order:
1. "Event: [Brief summary of what happened]"
2. "Violation: [Specific Community Guideline breached]"
3. "Risk: [Potential offline harm]"
Return ONLY JSON:
{"issues": [{"title": "Short Title", "score": 85, "summary": ["Event: ...", "Violation: ...", "Risk: ..."], "link": ".."}]}
"""
res = get_ai_response(system_msg, news_context)
if res:
try:
st.session_state.hot_issues = json.loads(res.choices[0].message.content).get('issues', [])[:3]
except:
st.session_state.hot_issues = []
if st.session_state.hot_issues:
cols = st.columns(3)
for i, issue in enumerate(st.session_state.hot_issues):
with cols[i]:
with st.container(border=True):
# UI: κ²μ΄μ§ λμ ν° μ«μ μ¬μ© (λͺ¨λ°μΌ κ°λ
μ±)
st.markdown(f"<h1 style='text-align: center; color: #FF4B4B; margin: 0;'>{issue.get('score', 50)}</h1>", unsafe_allow_html=True)
st.markdown("<p style='text-align: center; color: gray; font-size: 0.8em;'>Safety Risk Index</p>", unsafe_allow_html=True)
st.error(f"**{issue.get('title')}**")
for line in issue.get('summary', []): st.caption(f"β’ {line}")
st.markdown(f"[π Link]({issue.get('link')})")
st.divider()
# --- 6. Strategic Tabs ---
tab1, tab2, tab3 = st.tabs(["π GLOBAL POLICY SCAN", "π NATIONAL T&S FORENSICS", "π RISK VELOCITY"])
# --- [Tab 1: Strategic Global Scan] ---
with tab1:
st.header("Strategic Policy & Impact Briefing")
keyword = st.text_input("Risk Category", "Election Integrity")
if st.button("Analyze Policy Impact", type="primary"):
with st.status("Auditing Global Content Compliance (20+ Sources)...", expanded=True):
all_news = []
for reg in REGION_MAP:
for geo in REGION_MAP[reg][:2]:
all_news.extend(fetch_extensive_news(keyword, geo=geo, limit=6, period="7d"))
if not all_news:
st.error("No news found for this keyword.")
else:
news_summary = "\n".join([n['title'] for n in all_news[:45]])
# PROMPT: μ¬κ±΄ κ°μ(Summary) νμ ν¬ν¨
global_prompt = f"""
Analyze '{keyword}' focusing on 'Community Guidelines' and 'Social Impact'.
1. Executive Summary: Start with a clear **Event Summary** of what happened. Then, analyze the systemic policy risks and societal harm. (300+ words).
2. Risk Landscape: Map findings to specific violations.
Return ONLY JSON:
{{
"executive_summary": "Start with [The Incident Details], then move to [Policy Analysis].",
"risk_landscape": [
{{"Component": "Primary Violation", "Findings": "...", "Risk_Level": "High"}},
{{"Component": "Vulnerable Target", "Findings": "...", "Risk_Level": "Critical"}},
{{"Component": "Offline Harm", "Findings": "...", "Risk_Level": "High"}},
{{"Component": "Enforcement Gap", "Findings": "...", "Risk_Level": "Medium"}}
],
"platform_intelligence": [
{{"Platform": "TikTok", "Assessment": "...", "Strategy": "..."}},
{{"Platform": "YouTube", "Assessment": "...", "Strategy": "..."}},
{{"Platform": "Meta", "Assessment": "...", "Strategy": "..."}},
{{"Platform": "X", "Assessment": "...", "Strategy": "..."}}
],
"strategic_conclusion": "Final Trust & Safety recommendation."
}}
"""
res = get_ai_response(global_prompt, news_summary)
if res:
g_data = json.loads(res.choices[0].message.content)
with st.container(border=True):
st.subheader("1. Policy Impact Executive Summary")
# κ°λ
μ±μ μν μ€λ°κΏ μ²λ¦¬
st.markdown(g_data.get('executive_summary').replace(". ", ".\n\n"))
st.subheader("2. Guideline Violation Matrix")
st.dataframe(pd.DataFrame(g_data.get('risk_landscape')), hide_index=True, use_container_width=True)
st.subheader("3. Platform Enforcement Strategy")
st.table(pd.DataFrame(g_data.get('platform_intelligence')))
st.success(f"**T&S Recommendation:** {g_data.get('strategic_conclusion')}")
# DOWNLOAD: Markdown Format (λͺ¨λ°μΌ νΈν)
report_md = f"# OMNI-WATCH POLICY REPORT: {keyword.upper()}\n\n"
report_md += f"## 1. EXECUTIVE SUMMARY\n{g_data.get('executive_summary')}\n\n"
report_md += "## 2. VIOLATION LANDSCAPE\n"
for item in g_data.get('risk_landscape', []):
report_md += f"- **{item['Component']}**: {item['Findings']} ({item['Risk_Level']})\n"
st.download_button("π₯ Download Policy Report (.md)", report_md, f"Policy_Intel_{keyword}.md")
# --- [Tab 2: Tactical Forensics] ---
with tab2:
st.header("National T&S Forensics")
target_geo = st.text_input("ISO Code", "US").upper()
if st.button("Analyze Violations"):
with st.status(f"Scanning {target_geo} for Policy Breaches (20+ Sources)...", expanded=True):
# κ²μμ΄ μ΅μ ν: μ€μ μ¬κ±΄/μ¬κ³ μμ£Ό
news = fetch_extensive_news(f"{target_geo} controversy OR scandal OR protest OR violence", geo=target_geo, limit=40, period="7d")
if not news:
st.error(f"No recent controversy news found for {target_geo}.")
else:
news_titles = "\n".join([n['title'] for n in news])
# PROMPT: Incident -> Policy Analysis ꡬ쑰 κ°μ
system_prompt = f"""
Analyze Top 5 Risks in {target_geo} strictly through a 'Community Guidelines' lens.
Summary MUST start with **"The Incident:"** (What happened) followed by **"Policy Analysis:"** (Why it violates rules).
Return ONLY JSON:
{{
"risks": [
{{
"rank": 1, "title": "Event Title", "score": 90,
"summary": "1. The Incident: ... \n2. Policy Analysis: ...",
"forensic_grid": {{
"Guideline_Breached": "e.g. Dangerous Organizations Policy",
"Victim_Demographics": "e.g. Teenagers / Ethnic Minorities",
"Societal_Impact": "e.g. Incitement to Violence",
"Enforcement_Action": "e.g. Geo-blocking / Account Ban"
}}
}}
]
}}
"""
res = get_ai_response(system_prompt, news_titles)
if res:
report_data = json.loads(res.choices[0].message.content).get('risks', [])
full_report_md = f"# NATIONAL T&S FORENSICS: {target_geo}\n\n"
for i, r in enumerate(report_data):
full_report_md += f"## {r.get('rank')}. {r.get('title')} (Risk: {r.get('score')})\n"
full_report_md += f"{r.get('summary')}\n\n"
with st.expander(f"π© RISK {r.get('rank')}: {r.get('title')} (Score: {r.get('score')})", expanded=True):
c1, c2 = st.columns([1, 4])
with c1:
# μμΈ λΆμ νμμλ κ²μ΄μ§ μ°¨νΈ μ¬μ© (Key μ€λ³΅ λ°©μ§ μ μ©)
st.plotly_chart(render_gauge(r.get('score'), "Risk Index"), use_container_width=True, key=f"fg_{i}")
with c2:
st.markdown("**Incident & Policy Analysis:**")
st.markdown(r.get('summary').replace(". ", ".\n\n"))
st.table(pd.DataFrame(r.get('forensic_grid', {}).items(), columns=["T&S Component", "Assessment"]))
st.download_button("π₯ Download Forensic Report (.md)", full_report_md, f"T&S_Forensics_{target_geo}.md")
st.divider()
st.caption(f"Evidence Base: {len(news)} articles")
st.dataframe(pd.DataFrame(news)[['title', 'source']], use_container_width=True)
# --- [Tab 3: Velocity] ---
with tab3:
st.header("Risk Velocity")
trend_key = st.text_input("Violation Type", "Hate Speech")
if st.button("Check Trend"):
dates = [(datetime.now() - timedelta(days=i)).strftime("%m-%d") for i in range(6, -1, -1)]
fig = px.area(x=dates, y=[15, 30, 50, 80, 95, 88, 92], title=f"Violation Surge: {trend_key}")
st.plotly_chart(fig, use_container_width=True) |