ZENLive / app.py
ZENLLC's picture
Update app.py
9ba62b4 verified
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("&","&amp;").replace("<","&lt;").replace(">","&gt;")
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