File size: 12,021 Bytes
f563372
 
 
 
2bdf2b8
f563372
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9ba62b4
f563372
 
 
 
9ba62b4
 
f563372
 
 
9ba62b4
 
 
 
 
 
 
 
f563372
 
9ba62b4
f563372
 
2bdf2b8
 
f563372
 
 
 
 
 
 
 
 
 
 
9ba62b4
 
 
 
 
 
 
f563372
 
 
 
9ba62b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f563372
2bdf2b8
 
 
9ba62b4
2bdf2b8
 
 
 
 
 
 
 
 
f563372
9ba62b4
f563372
 
9ba62b4
 
 
 
 
 
f563372
 
9ba62b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
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