File size: 7,558 Bytes
9884451 | 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 | """briefing-32 β Gradio app entry for Hugging Face Spaces.
Build Small Hackathon submission (Backyard AI track):
A small-model down-port of ~/ai-news-agent. The production version uses
Groq Llama-3.3-70B; this version fits the same workflow under 32B params
using Qwen3-32B via Hugging Face Inference Providers.
Same pipeline as the every-2-hours cron the maker has running on a laptop:
fetch RSS / HN / arXiv / GitHub -> two-pass relevance filter + ranker ->
readable digest. Gradio is the delivery surface here instead of WhatsApp.
"""
from __future__ import annotations
import os
import time
from typing import Any
import gradio as gr
import pandas as pd
from config import (
DEFAULT_BASE_URL,
DEFAULT_MODEL,
MIN_NEW_ITEMS,
PER_SOURCE_CAP,
)
from digest import make_digest
from fetch import fetch_all
from rank import RankerConfig, rank_pipeline
# ---------------------------------------------------------------------------
# Core pipeline (callable from Gradio + scripts/cli.py)
# ---------------------------------------------------------------------------
def run_briefing(
window_hours: int,
enabled_sources: list[str],
model: str,
hf_token: str,
) -> dict[str, Any]:
"""Fetch -> filter -> rank -> digest. Returns everything for the UI."""
since_ts = time.time() - window_hours * 3600
enabled = set(enabled_sources) if enabled_sources else {"rss", "hn", "arxiv", "github"}
t0 = time.perf_counter()
raw = fetch_all(since_ts, enabled=enabled)
fetch_latency = time.perf_counter() - t0
cfg = RankerConfig(
base_url=DEFAULT_BASE_URL,
model=model or DEFAULT_MODEL,
api_key=hf_token or "",
)
result = rank_pipeline(raw, cfg=cfg)
digest = ""
if result.after_rank >= MIN_NEW_ITEMS:
digest = make_digest(result.items, cfg=cfg)
elif result.after_rank > 0:
digest = make_digest(result.items, cfg=cfg)
return {
"digest": digest or "_(no high-signal items in window)_",
"items": result.items,
"raw_count": result.raw_count,
"after_filter": result.after_filter,
"after_rank": result.after_rank,
"fetch_latency": fetch_latency,
"filter_latency": result.filter_latency,
"rank_latency": result.rank_latency,
"model": cfg.model,
}
# ---------------------------------------------------------------------------
# Gradio glue
# ---------------------------------------------------------------------------
def _items_to_df(items: list[dict]) -> pd.DataFrame:
if not items:
return pd.DataFrame(columns=["score", "source", "title", "reason", "url"])
rows = [
{
"score": it.get("score", 0),
"source": it.get("source", ""),
"title": it.get("title", ""),
"reason": it.get("reason", ""),
"url": it.get("url", ""),
}
for it in items
]
return pd.DataFrame(rows)
def _stats_md(result: dict[str, Any]) -> str:
return (
f"**Model:** `{result['model']}` \n"
f"**Raw items fetched:** {result['raw_count']} \n"
f"**Survived filter:** {result['after_filter']} \n"
f"**Survived rank (score β₯ 6):** {result['after_rank']} \n"
f"**Fetch latency:** {result['fetch_latency']:.1f}s \n"
f"**Filter latency:** {result['filter_latency']:.1f}s \n"
f"**Rank latency:** {result['rank_latency']:.1f}s \n"
f"**Total LLM time:** {result['filter_latency'] + result['rank_latency']:.1f}s"
)
def _gradio_handler(window_hours, sources, model, hf_token):
try:
result = run_briefing(
window_hours=int(window_hours),
enabled_sources=list(sources or []),
model=(model or DEFAULT_MODEL).strip(),
hf_token=(hf_token or "").strip(),
)
except Exception as e:
return (
f"**Error:** `{e}`\n\nMake sure `HF_TOKEN` is set in Space secrets "
f"or pasted into the sidebar.",
pd.DataFrame(),
"_no run yet_",
)
return result["digest"], _items_to_df(result["items"]), _stats_md(result)
# Custom theme β "Off-Brand" bonus badge target.
THEME = gr.themes.Soft(
primary_hue="orange",
secondary_hue="slate",
neutral_hue="zinc",
).set(
body_background_fill="#0b1220",
body_text_color="#e2e8f0",
block_background_fill="#111827",
block_border_width="1px",
block_border_color="#1f2937",
button_primary_background_fill="#f97316",
button_primary_text_color="#0b1220",
)
with gr.Blocks(theme=THEME, title="briefing-32 Β· Build Small entry") as demo:
gr.Markdown(
"""
# briefing-32
**A 32B-class AI-news briefing the maker runs every 2 hours.**
Build Small Hackathon entry (Backyard AI track). Down-ported from the
production `ai-news-agent` cron (Groq Llama-3.3-70B β WhatsApp) onto
Qwen3-32B served by Hugging Face Inference Providers.
Pipeline: RSS + HN + arXiv + GitHub β cheap relevance filter β
graded 0β10 ranker β readable digest. Two open-weight model calls,
no 70B cloud round-trip required.
"""
)
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Controls")
window_hours = gr.Slider(
minimum=1, maximum=72, value=2, step=1,
label="Window (hours back)",
info="Production runs every 2hr β match that for the authentic story.",
)
sources = gr.CheckboxGroup(
choices=["rss", "hn", "arxiv", "github"],
value=["rss", "hn", "arxiv", "github"],
label="Sources",
)
model = gr.Textbox(
value=DEFAULT_MODEL,
label="Model (β€32B params)",
info="Default Qwen3-32B. Swap to Qwen3-30B-A3B for faster MoE inference.",
)
hf_token = gr.Textbox(
label="HF_TOKEN (optional β reads env if blank)",
placeholder="hf_β¦",
type="password",
)
run_btn = gr.Button("Run briefing", variant="primary")
gr.Markdown("### Run stats")
stats = gr.Markdown("_no run yet_")
with gr.Column(scale=2):
gr.Markdown("### Digest")
digest = gr.Markdown(
value="_Click **Run briefing** to fetch the last N hours of AI news, "
"rank it on a β€32B model, and render a readable briefing._"
)
gr.Markdown("### Ranked items")
items_df = gr.Dataframe(
headers=["score", "source", "title", "reason", "url"],
value=pd.DataFrame(columns=["score", "source", "title", "reason", "url"]),
wrap=True,
interactive=False,
)
run_btn.click(
_gradio_handler,
inputs=[window_hours, sources, model, hf_token],
outputs=[digest, items_df, stats],
)
gr.Markdown(
"""
---
*Build Small Hackathon Β· Backyard AI track. Apache 2.0.*
Code: [github.com/MukundaKatta/briefing-32](https://github.com/MukundaKatta/briefing-32)
"""
)
if __name__ == "__main__":
demo.queue(max_size=8).launch(
server_name=os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0"),
server_port=int(os.environ.get("PORT", "7860")),
)
|