"""Page routes: GET / (chat), GET /dashboard, dashboard partials.""" from __future__ import annotations import logging from datetime import UTC, datetime from typing import TYPE_CHECKING from fastapi import APIRouter, Request from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates if TYPE_CHECKING: from ..agent_registry import AgentRegistry from ..config import Config from ..db import Database from ..llm import LLMAdapter from ..scheduler import Scheduler log = logging.getLogger(__name__) router = APIRouter() _registry: AgentRegistry | None = None _config: Config | None = None _llm: LLMAdapter | None = None _db: Database | None = None _scheduler: Scheduler | None = None _templates: Jinja2Templates | None = None def setup(registry, config, llm, templates, db=None, scheduler=None): global _registry, _config, _llm, _templates, _db, _scheduler _registry = registry _config = config _llm = llm _templates = templates _db = db _scheduler = scheduler @router.get("/") async def chat_page(request: Request): agent_names = _registry.list_agents() if _registry else [] agents = [] for name in agent_names: agent = _registry.get(name) display = agent.agent_config.display_name if agent else name agents.append({"name": name, "display_name": display}) default_agent = _registry.default_name if _registry else "default" chat_model = _config.chat_model if _config else "unknown" exec_available = _llm.is_execution_brain_available() if _llm else False clickup_enabled = _config.clickup.enabled if _config else False return _templates.TemplateResponse("chat.html", { "request": request, "agents": agents, "default_agent": default_agent, "chat_model": chat_model, "exec_available": exec_available, "clickup_enabled": clickup_enabled, }) @router.get("/dashboard") async def dashboard_page(request: Request): return _templates.TemplateResponse("dashboard.html", { "request": request, }) @router.get("/dashboard/pipeline") async def dashboard_pipeline(): """Return pipeline panel HTML partial with task data.""" if not _config or not _config.clickup.enabled: return HTMLResponse('

ClickUp not configured

') try: from ..api import get_tasks data = await get_tasks() all_tasks = data.get("tasks", []) except Exception as e: log.error("Pipeline data fetch failed: %s", e) return HTMLResponse(f'

Error: {e}

') # Group by work category, then by status pipeline_statuses = [ "to do", "automation underway", "outline review", "internal review", "error", ] categories = {} # category -> {status -> [tasks]} for t in all_tasks: cat = t.get("task_type") or "Other" status = t.get("status", "unknown") # Only show tasks in pipeline-relevant statuses if status not in pipeline_statuses: continue if cat not in categories: categories[cat] = {} categories[cat].setdefault(status, []).append(t) # Build HTML html_parts = [] # Status summary counts total_counts = {} for cat_data in categories.values(): for status, tasks in cat_data.items(): total_counts[status] = total_counts.get(status, 0) + len(tasks) if total_counts: html_parts.append('
') for status in pipeline_statuses: count = total_counts.get(status, 0) html_parts.append( f'
' f'
{count}
' f'
{status}
' f'
' ) html_parts.append('
') # Per-category tables for cat_name in sorted(categories.keys()): cat_data = categories[cat_name] all_cat_tasks = [] for status in pipeline_statuses: all_cat_tasks.extend(cat_data.get(status, [])) if not all_cat_tasks: continue html_parts.append(f'

{cat_name} ({len(all_cat_tasks)})

') html_parts.append('' '' '') for task in all_cat_tasks: name = task.get("name", "") url = task.get("url", "") customer = (task.get("custom_fields") or {}).get("Client", "N/A") status = task.get("status", "") status_class = "status-" + status.replace(" ", "-") # Format due date due_display = "-" due_raw = task.get("due_date") if due_raw: try: due_dt = datetime.fromtimestamp(int(due_raw) / 1000, tz=UTC) due_display = due_dt.strftime("%b %d") except (ValueError, TypeError, OSError): pass name_cell = ( f'{name}' if url else name ) html_parts.append( f'' f'' f'' ) html_parts.append('
TaskCustomerStatusDue
{name_cell}{customer}{status}{due_display}
') if not html_parts: return HTMLResponse('

No active pipeline tasks

') return HTMLResponse('\n'.join(html_parts))