| import json, textwrap, time |
| from datetime import datetime |
| import gradio as gr |
|
|
| |
| 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/"] |
| ] |
| } |
| ] |
|
|
| |
| 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 ( |
| '<link rel="stylesheet" href="style.css">' |
| '<div class="zen-ticker shell">' |
| f' <div class="zen-ticker__track" id="zenTicker" data-speed="38">{text}</div>' |
| "</div>" |
| '<script src="script.js"></script>' |
| ) |
|
|
| def build_prompter_html(text, speed=40, is_playing=True): |
| playing = "true" if is_playing else "false" |
| safe = text.replace("&","&").replace("<","<").replace(">",">") |
| return ( |
| '<link rel="stylesheet" href="style.css">' |
| '<div class="prompter">' |
| ' <div class="prompter__viewport">' |
| ' <div class="prompter__scroll" id="prompterScroll"' |
| f' data-speed="{speed}"' |
| f' data-playing="{playing}">' |
| f' <pre class="prompter__text">{safe}</pre>' |
| ' </div>' |
| ' </div>' |
| ' <div class="prompter__controls">' |
| ' <button id="pPlayPause" class="btn">⏯</button>' |
| ' <button id="pSlower" class="btn">–</button>' |
| ' <button id="pFaster" class="btn">+</button>' |
| ' <button id="pFullscreen" class="btn">⤢</button>' |
| f' <span class="prompter__speed" id="pSpeed">{speed}px/s</span>' |
| ' </div>' |
| '</div>' |
| '<script src="script.js"></script>' |
| ) |
|
|
| 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): |
| |
| 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) |
|
|
| |
| with gr.Blocks(css="style.css", title="ZEN Weekly Live Radar PRO") as demo: |
| gr.HTML('<link rel="stylesheet" href="style.css"><script src="script.js"></script>') |
|
|
| 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]]))) |
|
|
| |
| 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, |
| gr.update(value=rows), |
| gr.update(maximum=mx, value=0), |
| sel_md, |
| prom_html, |
| tick_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) |
|
|