Download app.py from ZENLLC/ZENLive: direct link, hf CLI and curl.
- Browser
- Download file 12 kB
-
https://huggingface.co/spaces/ZENLLC/ZENLive/resolve/main/app.py
- Command line
-
hf download hf://spaces/ZENLLC/ZENLive/app.py
-
curl -L -o app.py https://huggingface.co/spaces/ZENLLC/ZENLive/resolve/main/app.py
12 kB
| 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 ( | |
| '<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): | |
| # 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('<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]]))) | |
| # --------------- 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 | |