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"
Safety Risk Index
", 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)