import json, textwrap, time from datetime import datetime import gradio as gr # ----------------- SAMPLE WEEK (edit live on-air) ----------------- STORIES = [ { "title": "Microsoft 365 Premium launches ($19.99/mo) with integrated Copilot", "date": "2025-10-01", "category": "Agents & Productivity", "summary": "Microsoft bundles Office + Copilot Pro into a single individual plan with higher usage limits and advanced features (Researcher, Analyst, Actions). Copilot Pro sunsets. Agents require Azure + metered credits.", "talk_track": [ "Copilot becomes the personal default.", "Premium unlocks higher usage ceilings.", "Agents: mind the Azure/Copilot credit metering." ], "why_it_matters": "Mainstreaming AI assistants with clear pricing signals; agent workflows move into production norms.", "sources": [ ["Reuters", "https://www.reuters.com/technology/microsoft-launches-ai-powered-365-premium-bundle-1999-per-month-2025-10-01/"], ["Microsoft pricing", "https://www.microsoft.com/en-us/microsoft-365-copilot/pricing"] ] }, { "title": "Google 2K Nest cams for Gemini for Home + Home Premium subscription", "date": "2025-10-01", "category": "Edge & Devices", "summary": "New Nest cams (wider FOV, better low light) and descriptive AI alerts + daily Home Brief. Google Home Premium replaces Nest Aware tiers.", "talk_track": [ "First hardware built for Gemini for Home.", "Events → summaries → routines.", "Sub changes: Home Premium instead of Nest Aware." ], "why_it_matters": "House-scale agents: sensor streams become structured inputs for daily briefings and automation.", "sources": [ ["The Verge", "https://www.theverge.com/"] ] }, { "title": "OpenAI updates GPT-5 Instant behavior for crisis-support signals", "date": "2025-10-03", "category": "Models & Alignment", "summary": "Behavioral tuning for GPT-5 Instant improves distress detection and de-escalation, guided by experts.", "talk_track": [ "Alignment over raw capability.", "Important for support, education, first-touch contexts." ], "why_it_matters": "Stability + safety polish for high-throughput deployments.", "sources": [ ["OpenAI release notes", "https://help.openai.com/en/articles/9624314-model-release-notes"] ] }, { "title": "Anthropic policy: personal Claude chats used for training (opt-out)", "date": "2025-10-01", "category": "Policy & Privacy", "summary": "Non-enterprise Claude users are opted-in for training; opt-out available in settings. Enterprise/edu/gov excluded.", "talk_track": [ "Flip the privacy toggle before sensitive work.", "Audit retention + compliance posture." ], "why_it_matters": "Data governance becomes a weekly habit, not a yearly policy read.", "sources": [ ["Tom’s Guide explainer", "https://www.tomsguide.com/"] ] }, { "title": "NVIDIA: Cosmos world models + Newton physics + Isaac GR00T updates", "date": "2025-10-02", "category": "Robotics & Physical AI", "summary": "Open world-model family, open Newton physics in Isaac Lab, updated GR00T reasoning to accelerate robotics R&D.", "talk_track": [ "Synthetic data + open physics to speed loops.", "Bridging sim-to-real for smaller labs." ], "why_it_matters": "Lower barrier to realistic robot learning → more pilots in 2026.", "sources": [ ["NVIDIA Newsroom", "https://nvidianews.nvidia.com/"] ] } ] # ----------------- Helpers ----------------- def all_categories(): return sorted(list({s["category"] for s in STORIES})) def fmt_story(story: dict) -> str: if not story: return "No story selected." date = datetime.fromisoformat(story["date"]).strftime("%b %d, %Y") bullets = "\n".join([f"- {b}" for b in story.get("talk_track", [])]) srcs = "\n".join([f"- [{t}]({u})" for t, u in story.get("sources", [])]) return ( f"### {story['title']}\n" f"**Date:** {date} **Category:** {story['category']}\n\n" f"**Summary:** {story['summary']}\n\n" f"**On-air notes:**\n{bullets}\n\n" f"**Why it matters:** {story['why_it_matters']}\n\n" f"**Sources:**\n{srcs}\n" ) def teleprompter_text(story: dict) -> str: text = f"{story['title']}\n\n{story['summary']}\n\nWhy it matters: {story['why_it_matters']}" return textwrap.fill(text, width=90) def list_rows(items): return [[s["date"], s["category"], s["title"]] for s in items] def filter_and_search(category, query): items = STORIES if category and category != "All": items = [s for s in items if s["category"] == category] if query: q = query.lower().strip() items = [s for s in items if q in s["title"].lower() or q in s["summary"].lower()] return items def build_ticker_html(text): return ( '' '
' f'
{text}
' "
" '' ) def build_prompter_html(text, speed=40, is_playing=True): playing = "true" if is_playing else "false" safe = text.replace("&","&").replace("<","<").replace(">",">") return ( '' '
' '
' '
' f'
{safe}
' '
' '
' '
' ' ' ' ' ' ' ' ' f' {speed}px/s' '
' '
' '' ) def pick(idx, items): if not items: return ("No stories here.", build_prompter_html(""), build_ticker_html(""), 0) i = max(0, min(int(idx), len(items) - 1)) story = items[i] headlines = " • ".join([s["title"] for s in items[:12]]) return ( fmt_story(story), build_prompter_html(teleprompter_text(story)), build_ticker_html(headlines), i ) def add_story(json_blob): # Robust try/except — this is where the earlier paste got cut try: obj = json.loads(json_blob) if not isinstance(obj, dict): raise ValueError("JSON must be an object") if "title" not in obj or not obj["title"]: raise ValueError("Missing 'title'") if "summary" not in obj or not obj["summary"]: raise ValueError("Missing 'summary'") obj.setdefault("date", datetime.utcnow().date().isoformat()) obj.setdefault("category", "Misc") obj.setdefault("talk_track", []) obj.setdefault("why_it_matters", "") obj.setdefault("sources", []) STORIES.insert(0, obj) return "✅ Added.", ["All"] + all_categories() except Exception as e: return f"❌ {e}", ["All"] + all_categories() def countdown(minutes): try: total = int(float(minutes) * 60) except Exception: total = 0 total = max(0, min(total, 90 * 60)) for t in range(total, -1, -1): mm, ss = divmod(t, 60) yield f"{mm:02d}:{ss:02d}" time.sleep(1) # --------------------------- UI ---------------------------- with gr.Blocks(css="style.css", title="ZEN Weekly Live Radar PRO") as demo: gr.HTML('') with gr.Row(elem_classes="row-top"): gr.Markdown("### 🛰️ ZEN Weekly Live Radar PRO — Paste stories, run teleprompter, scroll ticker") with gr.Row(): with gr.Column(scale=1, elem_classes="card"): category = gr.Dropdown(choices=["All"] + all_categories(), value="All", label="Category") query = gr.Textbox(label="Search", placeholder="Filter titles & summaries…") list_state = gr.State([]) table = gr.Dataframe(headers=["Date","Category","Title"], interactive=False) slider = gr.Slider(label="Select", minimum=0, maximum=max(0, len(STORIES)-1), step=1, value=0) refresh = gr.Button("Refresh") gr.Markdown("#### Add a story (JSON)") template = { "title": "Sample: Your breaking item", "date": datetime.utcnow().date().isoformat(), "category": "Breaking", "summary": "One-line explainer.", "talk_track": ["Point 1", "Point 2"], "why_it_matters": "The punchline.", "sources": [["Source Name", "https://example.com"]] } gr.Code(value=json.dumps(template, indent=2), language="json", label="Template") blob = gr.Textbox(lines=10, placeholder="Paste JSON here…") add_btn = gr.Button("Add") add_status = gr.Markdown() gr.Markdown("#### On-air countdown") mins = gr.Number(label="Minutes", value=5) start = gr.Button("Start") time_left = gr.Textbox(label="Time left", value="05:00", interactive=False) with gr.Column(scale=2, elem_classes="card"): story_md = gr.Markdown(fmt_story(STORIES[0])) prompter_html = gr.HTML(build_prompter_html(teleprompter_text(STORIES[0]))) ticker_html = gr.HTML(build_ticker_html(" • ".join([s["title"] for s in STORIES[:12]]))) # --------------- Callbacks ---------------- def refresh_list(cat, q): items = filter_and_search(cat, q) rows = list_rows(items) mx = max(0, len(items) - 1) sel_md, prom_html, tick_html, _ = pick(0, items) return ( items, # list_state gr.update(value=rows), # table data gr.update(maximum=mx, value=0), # slider props sel_md, # story_md prom_html, # prompter_html tick_html # ticker_html ) def on_select(i, cat, q, items): items = filter_and_search(cat, q) sel_md, prom_html, tick_html, _ = pick(i, items) return sel_md, prom_html, tick_html refresh.click( refresh_list, inputs=[category, query], outputs=[list_state, table, slider, story_md, prompter_html, ticker_html] ) category.change( refresh_list, inputs=[category, query], outputs=[list_state, table, slider, story_md, prompter_html, ticker_html] ) query.submit( refresh_list, inputs=[category, query], outputs=[list_state, table, slider, story_md, prompter_html, ticker_html] ) slider.release( on_select, inputs=[slider, category, query, list_state], outputs=[story_md, prompter_html, ticker_html] ) add_btn.click(add_story, [blob], [add_status, category]).then( refresh_list, [category, query], [list_state, table, slider, story_md, prompter_html, ticker_html] ) start.click(countdown, [mins], [time_left]) if __name__ == "__main__": demo.queue().launch(ssr_mode=False) # disable SSR for widest compatibility