From 7864ec6f1756a1867c089b2d4254931f33f58faf Mon Sep 17 00:00:00 2001 From: PeninsulaInd Date: Mon, 16 Feb 2026 16:41:33 -0600 Subject: [PATCH] Add pipeline status box to show PR tool progress in the UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The press release pipeline takes 2-4 minutes per run with no feedback. This adds a DB-polled status box (gr.Timer every 3s) that shows the current step (e.g. "Step 3/4: Writing press release 1/2 — headline...") and auto-hides when the pipeline completes. Co-Authored-By: Claude Opus 4.6 --- cheddahbot/tools/press_release.py | 30 +++++++++++++++++++++++++----- cheddahbot/ui.py | 17 +++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/cheddahbot/tools/press_release.py b/cheddahbot/tools/press_release.py index d05237f..ffd92d5 100644 --- a/cheddahbot/tools/press_release.py +++ b/cheddahbot/tools/press_release.py @@ -35,6 +35,12 @@ _HEADLINES_FILE = _SKILLS_DIR / "headlines.md" SONNET_CLI_MODEL = "sonnet" +def _set_status(ctx: dict | None, message: str) -> None: + """Write pipeline progress to the DB so the UI can poll it.""" + if ctx and "db" in ctx: + ctx["db"].kv_set("pipeline:status", message) + + # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- @@ -305,13 +311,17 @@ def write_press_releases( companies_file = _load_file_if_exists(_COMPANIES_FILE) headlines_ref = _load_file_if_exists(_HEADLINES_FILE) - # Ensure output directory - _OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + # Ensure output directory (company subfolder) + company_slug = _slugify(company_name) + output_dir = _OUTPUT_DIR / company_slug + output_dir.mkdir(parents=True, exist_ok=True) today = datetime.now().strftime("%Y-%m-%d") cost_log: list[dict] = [] # ── Step 1: Generate 7 headlines (chat brain) ───────────────────────── + log.info("[PR Pipeline] Step 1/4: Generating 7 headlines for %s...", company_name) + _set_status(ctx, f"Step 1/4: Generating 7 headlines for {company_name}...") step_start = time.time() headline_prompt = _build_headline_prompt(topic, company_name, url, lsi_terms, headlines_ref) messages = [ @@ -330,10 +340,12 @@ def write_press_releases( # Save all 7 headline candidates to file slug_base = _slugify(f"{company_name}-{topic}") - headlines_file = _OUTPUT_DIR / f"{slug_base}_{today}_headlines.txt" + headlines_file = output_dir / f"{slug_base}_{today}_headlines.txt" headlines_file.write_text(headlines_raw.strip(), encoding="utf-8") # ── Step 2: AI judge picks best 2 (chat brain) ─────────────────────── + log.info("[PR Pipeline] Step 2/4: AI judge selecting best 2 headlines...") + _set_status(ctx, "Step 2/4: AI judge selecting best 2 headlines...") step_start = time.time() judge_prompt = _build_judge_prompt(headlines_raw, headlines_ref) messages = [ @@ -355,9 +367,12 @@ def write_press_releases( winners = winners[:2] # ── Step 3: Write 2 press releases (execution brain × 2) ───────────── + log.info("[PR Pipeline] Step 3/4: Writing 2 press releases...") pr_texts: list[str] = [] pr_files: list[str] = [] for i, headline in enumerate(winners): + log.info("[PR Pipeline] Writing PR %d/2: %s", i + 1, headline[:60]) + _set_status(ctx, f"Step 3/4: Writing press release {i+1}/2 — {headline[:60]}...") step_start = time.time() pr_prompt = _build_pr_prompt( headline, topic, company_name, url, lsi_terms, @@ -384,14 +399,17 @@ def write_press_releases( # Save PR to file slug = _slugify(headline) filename = f"{slug}_{today}.txt" - filepath = _OUTPUT_DIR / filename + filepath = output_dir / filename filepath.write_text(clean_result, encoding="utf-8") pr_files.append(str(filepath)) # ── Step 4: Generate 2 JSON-LD schemas (Sonnet + WebSearch) ─────────── + log.info("[PR Pipeline] Step 4/4: Generating 2 JSON-LD schemas...") schema_texts: list[str] = [] schema_files: list[str] = [] for i, pr_text in enumerate(pr_texts): + log.info("[PR Pipeline] Schema %d/2 for: %s", i + 1, winners[i][:60]) + _set_status(ctx, f"Step 4/4: Generating schema {i+1}/2...") step_start = time.time() schema_prompt = _build_schema_prompt(pr_text, company_name, url, schema_skill) exec_tools = "WebSearch,WebFetch" @@ -422,12 +440,14 @@ def write_press_releases( # Save schema to file slug = _slugify(winners[i]) filename = f"{slug}_{today}_schema.json" - filepath = _OUTPUT_DIR / filename + filepath = output_dir / filename filepath.write_text(schema_json or result, encoding="utf-8") schema_files.append(str(filepath)) # ── Build final output ──────────────────────────────────────────────── + _set_status(ctx, "") # Clear status — pipeline complete total_elapsed = sum(c["elapsed_s"] for c in cost_log) + log.info("[PR Pipeline] Complete for %s — %.0fs total", company_name, total_elapsed) output_parts = [] for i in range(2): diff --git a/cheddahbot/ui.py b/cheddahbot/ui.py index 41e9644..4bd46f8 100644 --- a/cheddahbot/ui.py +++ b/cheddahbot/ui.py @@ -77,6 +77,12 @@ def create_ui(agent: Agent, config: Config, llm: LLMAdapter, elem_classes=["contain"], ) + pipeline_status = gr.Markdown( + value="", + visible=False, + elem_classes=["contain"], + ) + with gr.Row(elem_classes=["contain"]): msg_input = gr.MultimodalTextbox( placeholder="Type a message... (attach files, use mic, or camera)", @@ -210,6 +216,13 @@ def create_ui(agent: Agent, config: Config, llm: LLMAdapter, except Exception as e: return None, f"Voice chat error: {e}" + def poll_pipeline_status(): + """Poll the DB for pipeline progress updates.""" + status = agent.db.kv_get("pipeline:status") + if status: + return gr.update(value=f"⏳ {status}", visible=True) + return gr.update(value="", visible=False) + def poll_notifications(): """Poll the notification bus for pending messages.""" if not notification_bus: @@ -244,6 +257,10 @@ def create_ui(agent: Agent, config: Config, llm: LLMAdapter, [voice_output, voice_status], ) + # Pipeline status polling timer (every 3 seconds) + status_timer = gr.Timer(3) + status_timer.tick(poll_pipeline_status, None, [pipeline_status]) + # Notification polling timer (every 10 seconds) if notification_bus: notification_bus.subscribe("gradio", lambda msg, cat: None) # Register listener