Add pipeline status box to show PR tool progress in the UI
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 <noreply@anthropic.com>cora-start
parent
7153e65ae6
commit
7864ec6f17
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue