"""HTML views for AI-provider connections and operational insights (#650).""" from __future__ import annotations from html import escape from webui.insights_loader import ( CONNECTION_DECLARED_AVAILABLE, CONNECTION_DECLARED_UNAVAILABLE, InsightsSnapshot, ProviderSnapshot, SEVERITY_CRITICAL, SEVERITY_INFO, SEVERITY_UNPROVEN, SEVERITY_WARN, ) from webui.layout import render_page _SEVERITY_CSS = { SEVERITY_CRITICAL: "badge-blocked", SEVERITY_WARN: "badge-health-degraded", SEVERITY_INFO: "badge-health-ok", SEVERITY_UNPROVEN: "badge-health-unproven", } _CONN_CSS = { CONNECTION_DECLARED_AVAILABLE: "badge-health-ok", CONNECTION_DECLARED_UNAVAILABLE: "badge-health-degraded", "registry_unavailable": "badge-blocked", } def _badge(text: str, css: str) -> str: return f'{escape(text)}' def _limits_card(lines: list[str], *, title: str) -> str: items = "".join(f"
  • {escape(line)}
  • " for line in lines) return f"""

    {escape(title)}

    Advisory surface only — no review, merge, close, or provider mutation is available here.

    """ def render_providers_page(snapshot: ProviderSnapshot) -> str: """Render the AI-provider connections page.""" if not snapshot.ok: body = f"""

    AI provider connections

    Phase 4 read-only provider status (#650).

    Provider registry unavailable: {escape(snapshot.fetch_error or "registry could not be loaded")}. No connection table is rendered — an empty table would claim that no providers are configured.
    {_limits_card([ "connection_status reflects the worker registry declaration only", "no API keys or credential material are loaded or rendered", "live executable health is not probed on this surface", ], title="Interpretation limits")} """ return render_page(title="Providers", body_html=body) rows = [] for provider in snapshot.providers: models = ( ", ".join(f"{escape(m)}" for m in provider.models) if provider.models else 'none declared' ) rows.append( "" f"{escape(provider.provider_id)}" f"{escape(provider.display_name)}" f"{escape(provider.vendor)}" f"{escape(provider.executable)}" f"{_badge(provider.connection_status, _CONN_CSS.get(provider.connection_status, 'badge-health-skipped'))}" f"{provider.worker_count} " f"({provider.enabled_worker_count} enabled)" f"{models}" "" ) table = ( "".join(rows) if rows else 'No providers declared in the registry.' ) body = f"""

    AI provider connections

    Phase 4 read-only provider status (#650). Registry revision {escape(str(snapshot.registry_revision))}. Declared status only — secrets never load.

    Declared connections

    {table}
    ProviderNameVendorExecutable ConnectionWorkersModels (declared)

    {escape(snapshot.providers[0].probe_limit if snapshot.providers else "")}

    {_limits_card([ "connection_status reflects the worker registry declaration only", "no API keys or credential material are loaded or rendered", "live executable health is not probed on this surface (#800 owns that)", ], title="Interpretation limits")}

    Related: Operational insights · Analytics

    """ return render_page(title="Providers", body_html=body) def _evidence_list(insight) -> str: items = "".join( f"
  • {escape(ref.kind)}:{escape(ref.ref)} — " f"{escape(ref.detail)}
  • " for ref in insight.evidence ) return f'' def render_insights_page(snapshot: InsightsSnapshot) -> str: """Render the operational insights page.""" if not snapshot.ok and not snapshot.insights: body = f"""

    Operational insights

    Phase 4 evidence-backed insights (#650).

    Insights unavailable: {escape(snapshot.fetch_error or "no sources ran")}.
    {_limits_card([ "insights are advisory only and never authorize merge, review, or close", "an insight without evidence refs is refused rather than emitted", ], title="Interpretation limits")} """ return render_page(title="Insights", body_html=body) source_bits = [] if snapshot.sources_used: source_bits.append( "sources used: " + ", ".join(f"{escape(s)}" for s in snapshot.sources_used) ) if snapshot.sources_unavailable: missing = "; ".join( f"{escape(item.get('source', '?'))}: {escape(item.get('reason', ''))}" for item in snapshot.sources_unavailable ) source_bits.append(f"sources unavailable: {missing}") cards = [] for insight in snapshot.insights: cards.append( f"""

    {_badge(insight.severity, _SEVERITY_CSS.get(insight.severity, "badge-health-skipped"))} {escape(insight.title)}

    {escape(insight.kind)} · confidence {escape(insight.confidence)} · {_badge("advisory only", "badge-health-skipped")} · {_badge("no action claimed", "badge-health-ok")}

    {escape(insight.summary)}

    Evidence

    {_evidence_list(insight)}
    """ ) if not cards: cards.append( '

    No insights met the ' "evidence threshold in the loaded sources. That is not a claim " "that the fleet is healthy — only that no qualifying pattern was " "found.

    " ) body = f"""

    Operational insights

    Phase 4 evidence-backed insights (#650). Derived only from durable console evidence; never invents policy or completes workflow actions.

    {" · ".join(source_bits) if source_bits else ""}

    {"".join(cards)} {_limits_card([ "insights are advisory only and never authorize merge, review, or close", "an insight without evidence refs is refused rather than emitted", "a missing source is listed as unavailable, not as an empty success", "insights never claim a workflow action completed", ], title="Interpretation limits")}

    Related: Provider connections · Traffic · System health · Analytics

    """ return render_page(title="Insights", body_html=body)