From 7864ca24618225977470776cc1c972c1558b8255 Mon Sep 17 00:00:00 2001 From: PeninsulaInd Date: Sun, 15 Feb 2026 22:29:33 -0600 Subject: [PATCH] Wire NotificationBus into main and Gradio UI Create NotificationBus in __main__.py and inject into scheduler and UI. Gradio subscribes as the "gradio" listener with a 10-second polling timer that displays notifications in a banner above the chatbot. ClickUp status shown in the header bar. Co-Authored-By: Claude Opus 4.6 --- cheddahbot/__main__.py | 13 ++++++++++-- cheddahbot/ui.py | 45 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/cheddahbot/__main__.py b/cheddahbot/__main__.py index 3e92962..c6c1e63 100644 --- a/cheddahbot/__main__.py +++ b/cheddahbot/__main__.py @@ -58,17 +58,26 @@ def main(): except Exception as e: log.warning("Tool system not available: %s", e) + # Notification bus (UI-agnostic) + notification_bus = None + try: + from .notifications import NotificationBus + log.info("Initializing notification bus...") + notification_bus = NotificationBus(db) + except Exception as e: + log.warning("Notification bus not available: %s", e) + # Phase 3+: Scheduler try: from .scheduler import Scheduler log.info("Starting scheduler...") - scheduler = Scheduler(config, db, agent) + scheduler = Scheduler(config, db, agent, notification_bus=notification_bus) scheduler.start() except Exception as e: log.warning("Scheduler not available: %s", e) log.info("Launching Gradio UI on %s:%s...", config.host, config.port) - app, css = create_ui(agent, config, llm) + app, css = create_ui(agent, config, llm, notification_bus=notification_bus) app.launch( server_name=config.host, server_port=config.port, diff --git a/cheddahbot/ui.py b/cheddahbot/ui.py index 06c50b0..41e9644 100644 --- a/cheddahbot/ui.py +++ b/cheddahbot/ui.py @@ -13,16 +13,26 @@ if TYPE_CHECKING: from .agent import Agent from .config import Config from .llm import LLMAdapter + from .notifications import NotificationBus log = logging.getLogger(__name__) _CSS = """ .contain { max-width: 900px; margin: auto; } footer { display: none !important; } +.notification-banner { + background: #1a1a2e; + border: 1px solid #16213e; + border-radius: 8px; + padding: 10px 16px; + margin-bottom: 8px; + font-size: 0.9em; +} """ -def create_ui(agent: Agent, config: Config, llm: LLMAdapter) -> gr.Blocks: +def create_ui(agent: Agent, config: Config, llm: LLMAdapter, + notification_bus: NotificationBus | None = None) -> gr.Blocks: """Build and return the Gradio app.""" available_models = llm.list_chat_models() @@ -30,15 +40,24 @@ def create_ui(agent: Agent, config: Config, llm: LLMAdapter) -> gr.Blocks: current_model = llm.current_model exec_status = "available" if llm.is_execution_brain_available() else "unavailable" + clickup_status = "enabled" if config.clickup.enabled else "disabled" with gr.Blocks(title="CheddahBot") as app: gr.Markdown("# CheddahBot", elem_classes=["contain"]) gr.Markdown( f"*Chat Brain:* `{current_model}`  |  " - f"*Execution Brain (Claude Code CLI):* `{exec_status}`", + f"*Execution Brain (Claude Code CLI):* `{exec_status}`  |  " + f"*ClickUp:* `{clickup_status}`", elem_classes=["contain"], ) + # -- Notification banner -- + notification_display = gr.Markdown( + value="", + visible=False, + elem_classes=["contain", "notification-banner"], + ) + with gr.Row(elem_classes=["contain"]): model_dropdown = gr.Dropdown( choices=model_choices, @@ -191,6 +210,22 @@ def create_ui(agent: Agent, config: Config, llm: LLMAdapter) -> gr.Blocks: except Exception as e: return None, f"Voice chat error: {e}" + def poll_notifications(): + """Poll the notification bus for pending messages.""" + if not notification_bus: + return gr.update(value="", visible=False) + + messages = notification_bus.get_pending("gradio") + if not messages: + return gr.update() # No change + + # Format notifications as markdown + lines = [] + for msg in messages[-5:]: # Show last 5 notifications max + lines.append(f"**Notification:** {msg}") + banner = "\n\n".join(lines) + return gr.update(value=banner, visible=True) + # -- Wire events -- model_dropdown.change(on_model_change, [model_dropdown], None) @@ -209,6 +244,12 @@ def create_ui(agent: Agent, config: Config, llm: LLMAdapter) -> gr.Blocks: [voice_output, voice_status], ) + # Notification polling timer (every 10 seconds) + if notification_bus: + notification_bus.subscribe("gradio", lambda msg, cat: None) # Register listener + timer = gr.Timer(10) + timer.tick(poll_notifications, None, [notification_display]) + # Load conversation list on app start app.load(_load_conversations, None, [conv_list])