Files
Gitea-Tools/webui/insights_views.py
T
sysadminandClaude Opus 4.8 983e8ac2c7 feat(webui): AI-provider connections and evidence-backed insights (Closes #650)
Add Phase 4 advisory surfaces for declared AI-provider connection status
(no secrets, no live probe claims) and operational insights derived only
from durable traffic, health, provider, and analytics evidence.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-25 16:53:36 -04:00

197 lines
7.1 KiB
Python

"""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'<span class="badge {css}">{escape(text)}</span>'
def _limits_card(lines: list[str], *, title: str) -> str:
items = "".join(f"<li>{escape(line)}</li>" for line in lines)
return f"""<div class="prompt-card">
<h3>{escape(title)}</h3>
<ul class="reasons">{items}</ul>
<p class="muted">Advisory surface only — no review, merge, close, or provider
mutation is available here.</p>
</div>"""
def render_providers_page(snapshot: ProviderSnapshot) -> str:
"""Render the AI-provider connections page."""
if not snapshot.ok:
body = f"""<h2>AI provider connections</h2>
<p class="meta">Phase 4 read-only provider status (#650).</p>
<div class="health-card health-stale">
<strong>Provider registry unavailable:</strong>
{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.
</div>
{_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"<code>{escape(m)}</code>" for m in provider.models)
if provider.models
else '<span class="muted">none declared</span>'
)
rows.append(
"<tr>"
f"<td><code>{escape(provider.provider_id)}</code></td>"
f"<td>{escape(provider.display_name)}</td>"
f"<td>{escape(provider.vendor)}</td>"
f"<td><code>{escape(provider.executable)}</code></td>"
f"<td>{_badge(provider.connection_status, _CONN_CSS.get(provider.connection_status, 'badge-health-skipped'))}</td>"
f"<td>{provider.worker_count} "
f"({provider.enabled_worker_count} enabled)</td>"
f"<td>{models}</td>"
"</tr>"
)
table = (
"".join(rows)
if rows
else '<tr><td colspan="7" class="muted">No providers declared in the registry.</td></tr>'
)
body = f"""<h2>AI provider connections</h2>
<p class="meta">Phase 4 read-only provider status (#650). Registry revision
<code>{escape(str(snapshot.registry_revision))}</code>.
Declared status only — secrets never load.</p>
<div class="health-card">
<h3>Declared connections</h3>
<table class="registry">
<thead>
<tr>
<th>Provider</th><th>Name</th><th>Vendor</th><th>Executable</th>
<th>Connection</th><th>Workers</th><th>Models (declared)</th>
</tr>
</thead>
<tbody>{table}</tbody>
</table>
<p class="muted">{escape(snapshot.providers[0].probe_limit if snapshot.providers else "")}</p>
</div>
{_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")}
<p class="muted">Related: <a href="/insights">Operational insights</a> ·
<a href="/analytics">Analytics</a></p>
"""
return render_page(title="Providers", body_html=body)
def _evidence_list(insight) -> str:
items = "".join(
f"<li><code>{escape(ref.kind)}:{escape(ref.ref)}</code> — "
f"{escape(ref.detail)}</li>"
for ref in insight.evidence
)
return f'<ul class="reasons">{items}</ul>'
def render_insights_page(snapshot: InsightsSnapshot) -> str:
"""Render the operational insights page."""
if not snapshot.ok and not snapshot.insights:
body = f"""<h2>Operational insights</h2>
<p class="meta">Phase 4 evidence-backed insights (#650).</p>
<div class="health-card health-stale">
<strong>Insights unavailable:</strong>
{escape(snapshot.fetch_error or "no sources ran")}.
</div>
{_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"<code>{escape(s)}</code>" 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"""<div class="prompt-card">
<h3>{_badge(insight.severity, _SEVERITY_CSS.get(insight.severity, "badge-health-skipped"))}
{escape(insight.title)}</h3>
<p class="meta"><code>{escape(insight.kind)}</code> · confidence
<code>{escape(insight.confidence)}</code> ·
{_badge("advisory only", "badge-health-skipped")} ·
{_badge("no action claimed", "badge-health-ok")}</p>
<p>{escape(insight.summary)}</p>
<h4>Evidence</h4>
{_evidence_list(insight)}
</div>"""
)
if not cards:
cards.append(
'<div class="prompt-card"><p class="muted">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.</p></div>"
)
body = f"""<h2>Operational insights</h2>
<p class="meta">Phase 4 evidence-backed insights (#650). Derived only from
durable console evidence; never invents policy or completes workflow actions.</p>
<p class="muted">{" · ".join(source_bits) if source_bits else ""}</p>
{"".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")}
<p class="muted">Related: <a href="/providers">Provider connections</a> ·
<a href="/traffic">Traffic</a> · <a href="/system-health">System health</a> ·
<a href="/analytics">Analytics</a></p>
"""
return render_page(title="Insights", body_html=body)