173 lines
5.6 KiB
Python
173 lines
5.6 KiB
Python
"""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('<p class="text-muted">ClickUp not configured</p>')
|
|
|
|
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'<p class="text-err">Error: {e}</p>')
|
|
|
|
# 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('<div class="pipeline-stats">')
|
|
for status in pipeline_statuses:
|
|
count = total_counts.get(status, 0)
|
|
html_parts.append(
|
|
f'<div class="pipeline-stat">'
|
|
f'<div class="stat-count">{count}</div>'
|
|
f'<div class="stat-label">{status}</div>'
|
|
f'</div>'
|
|
)
|
|
html_parts.append('</div>')
|
|
|
|
# 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'<div class="pipeline-group"><h4>{cat_name} ({len(all_cat_tasks)})</h4>')
|
|
html_parts.append('<table class="task-table"><thead><tr>'
|
|
'<th>Task</th><th>Customer</th><th>Status</th><th>Due</th>'
|
|
'</tr></thead><tbody>')
|
|
|
|
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'<a href="{url}" target="_blank">{name}</a>' if url else name
|
|
)
|
|
|
|
html_parts.append(
|
|
f'<tr><td>{name_cell}</td><td>{customer}</td>'
|
|
f'<td><span class="status-badge {status_class}">{status}</span></td>'
|
|
f'<td>{due_display}</td></tr>'
|
|
)
|
|
|
|
html_parts.append('</tbody></table></div>')
|
|
|
|
if not html_parts:
|
|
return HTMLResponse('<p class="text-muted">No active pipeline tasks</p>')
|
|
|
|
return HTMLResponse('\n'.join(html_parts))
|