fix(alerts): surface telegram AI automation coverage
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m19s
CD Pipeline / build-and-deploy (push) Successful in 5m33s
CD Pipeline / post-deploy-checks (push) Successful in 6m28s

This commit is contained in:
ogt
2026-07-10 02:18:49 +08:00
parent f29bc594ea
commit 6d49e4eb93
5 changed files with 408 additions and 6 deletions

View File

@@ -1,17 +1,55 @@
"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslations } from "next-intl";
import {
Activity,
ArrowRight,
BellRing,
Bot,
CheckCircle2,
Database,
Gauge,
ListChecks,
RefreshCw,
Send,
ShieldCheck,
TriangleAlert,
} from "lucide-react";
import { AutonomousRuntimeReceiptPanel } from "@/components/awooop/autonomous-runtime-receipt-panel";
import { Link } from "@/i18n/routing";
import { getRuntimeApiBaseUrl } from "@/lib/runtime-api-base";
import { cn } from "@/lib/utils";
type TelegramMonitoringCoveragePayload = {
status?: string | null;
active_blockers?: string[] | null;
operator_answer?: {
all_telegram_monitoring_alerts_fully_audited?: boolean | null;
all_telegram_monitoring_alert_surfaces_have_db_or_log_receipt?: boolean | null;
all_telegram_monitoring_alerts_ai_agent_automation_ready?: boolean | null;
all_known_telegram_egress_routes_controlled?: boolean | null;
km_rag_mcp_playbook_ai_agent_context_ready?: boolean | null;
manual_default_terminal_state_allowed?: boolean | null;
critical_break_glass_exception_only?: boolean | null;
} | null;
summary?: {
monitoring_surface_count?: number | null;
monitoring_live_evidence_received_count?: number | null;
monitoring_live_receipt_gap_count?: number | null;
known_direct_send_gap_count?: number | null;
runtime_alert_operation_log_total_7d?: number | null;
runtime_telegram_sent_event_count_7d?: number | null;
runtime_km_converted_event_count_7d?: number | null;
effective_ai_alert_context_receipt_total?: number | null;
effective_ai_alert_context_ready_total?: number | null;
telegram_monitoring_audit_completion_percent?: number | null;
ai_controlled_gap_queue_count?: number | null;
full_coverage_ready?: boolean | null;
} | null;
};
const API_BASE = getRuntimeApiBaseUrl();
const NAV_ITEMS = [
{
@@ -31,8 +69,164 @@ const NAV_ITEMS = [
},
] as const;
function formatNumber(value: unknown): string {
const numeric = Number(value ?? 0);
if (!Number.isFinite(numeric)) return "0";
return new Intl.NumberFormat("zh-TW").format(numeric);
}
function formatPercent(value: unknown): string {
const numeric = Number(value ?? 0);
if (!Number.isFinite(numeric)) return "0%";
return `${numeric.toFixed(numeric % 1 === 0 ? 0 : 1)}%`;
}
function StatCard({
label,
value,
detail,
tone,
icon: Icon,
}: {
label: string;
value: string;
detail: string;
tone: "ok" | "warn" | "neutral";
icon: typeof ShieldCheck;
}) {
return (
<div className="min-w-0 border border-[#e5e0d5] bg-white px-3 py-3">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<p className="truncate text-xs font-semibold text-[#77736a]">{label}</p>
<p className="mt-1 whitespace-nowrap font-mono text-lg font-semibold text-[#141413]">
{value}
</p>
</div>
<span
className={cn(
"flex h-8 w-8 shrink-0 items-center justify-center border",
tone === "ok" && "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]",
tone === "warn" && "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
tone === "neutral" && "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]",
)}
>
<Icon className="h-4 w-4" aria-hidden="true" />
</span>
</div>
<p className="mt-2 line-clamp-2 text-xs leading-5 text-[#5f5b52]">{detail}</p>
</div>
);
}
export default function AwoooPAlertsPage() {
const t = useTranslations("awooop.alerts.aiLoop");
const [coverage, setCoverage] = useState<TelegramMonitoringCoveragePayload | null>(null);
const [coverageLoading, setCoverageLoading] = useState(true);
const refreshCoverage = useCallback(async () => {
setCoverageLoading(true);
const controller = new AbortController();
const timeout = window.setTimeout(() => controller.abort(), 12_000);
try {
const response = await fetch(
`${API_BASE}/api/v1/agents/telegram-alert-monitoring-coverage-readback`,
{
cache: "no-store",
signal: controller.signal,
},
);
if (response.ok) {
setCoverage((await response.json()) as TelegramMonitoringCoveragePayload);
}
} catch {
setCoverage(null);
} finally {
window.clearTimeout(timeout);
setCoverageLoading(false);
}
}, []);
useEffect(() => {
refreshCoverage();
const timer = window.setInterval(refreshCoverage, 30_000);
return () => window.clearInterval(timer);
}, [refreshCoverage]);
const summary = coverage?.summary ?? {};
const answer = coverage?.operator_answer ?? {};
const blockers = coverage?.active_blockers ?? [];
const ready =
summary.full_coverage_ready === true
|| coverage?.status === "telegram_alert_monitoring_coverage_ready";
const surfaceValue = coverageLoading
? "-- / --"
: `${formatNumber(summary.monitoring_live_evidence_received_count)} / ${formatNumber(summary.monitoring_surface_count)}`;
const aiContextValue = coverageLoading
? "-- / --"
: `${formatNumber(summary.effective_ai_alert_context_ready_total)} / ${formatNumber(summary.effective_ai_alert_context_receipt_total)}`;
const statCards = useMemo(
() => [
{
key: "coverage",
label: t("summary.coverage"),
value: surfaceValue,
detail: t("summary.coverageDetail", {
gaps: formatNumber(summary.monitoring_live_receipt_gap_count),
}),
tone: ready ? "ok" as const : "warn" as const,
icon: ShieldCheck,
},
{
key: "logs",
label: t("summary.logs"),
value: coverageLoading
? "--"
: formatNumber(summary.runtime_alert_operation_log_total_7d),
detail: t("summary.logsDetail", {
telegram: formatNumber(summary.runtime_telegram_sent_event_count_7d),
km: formatNumber(summary.runtime_km_converted_event_count_7d),
}),
tone: ready ? "ok" as const : "neutral" as const,
icon: Database,
},
{
key: "aiContext",
label: t("summary.aiContext"),
value: aiContextValue,
detail: t("summary.aiContextDetail"),
tone: answer.km_rag_mcp_playbook_ai_agent_context_ready ? "ok" as const : "warn" as const,
icon: Bot,
},
{
key: "direct",
label: t("summary.direct"),
value: coverageLoading ? "--" : formatNumber(summary.known_direct_send_gap_count),
detail: t("summary.directDetail"),
tone: answer.all_known_telegram_egress_routes_controlled ? "ok" as const : "warn" as const,
icon: Send,
},
{
key: "queue",
label: t("summary.queue"),
value: coverageLoading ? "--" : formatNumber(summary.ai_controlled_gap_queue_count),
detail: blockers[0] ?? t("summary.queueDetail"),
tone: blockers.length === 0 ? "ok" as const : "warn" as const,
icon: TriangleAlert,
},
{
key: "completion",
label: t("summary.completion"),
value: coverageLoading
? "--"
: formatPercent(summary.telegram_monitoring_audit_completion_percent),
detail: t("summary.completionDetail"),
tone: ready ? "ok" as const : "neutral" as const,
icon: Gauge,
},
],
[answer, blockers, coverageLoading, ready, summary, surfaceValue, aiContextValue, t],
);
return (
<main
@@ -78,9 +272,89 @@ export default function AwoooPAlertsPage() {
})}
</nav>
</div>
<div
data-testid="telegram-alert-ai-automation-command-strip"
className="border-t border-[#e0ddd4] bg-[#faf9f3] px-4 py-3"
>
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<span
className={cn(
"inline-flex items-center gap-1 border px-2 py-0.5 text-xs font-semibold",
ready
? "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]"
: "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
)}
>
<CheckCircle2 className="h-3.5 w-3.5" aria-hidden="true" />
{coverageLoading ? t("states.loading") : ready ? t("states.ready") : t("states.actioning")}
</span>
<span className="font-mono text-xs text-[#77736a]">
{coverage?.status ?? "telegram_alert_monitoring_coverage_readback"}
</span>
</div>
<p className="mt-1 text-xs leading-5 text-[#5f5b52]">
{t("operatorAnswer", {
manual: answer.manual_default_terminal_state_allowed === false
? t("proof.off")
: t("proof.review"),
breakGlass: answer.critical_break_glass_exception_only === true
? t("proof.criticalOnly")
: t("proof.review"),
})}
</p>
</div>
<button
type="button"
onClick={refreshCoverage}
disabled={coverageLoading}
className="inline-flex items-center gap-2 border border-[#d8d3c7] bg-white px-3 py-2 text-xs font-semibold text-[#141413] hover:border-[#d97757] disabled:opacity-60"
>
<RefreshCw className={cn("h-4 w-4", coverageLoading && "animate-spin")} aria-hidden="true" />
{t("refresh")}
</button>
</div>
<div className="mt-3 grid gap-2 md:grid-cols-3 xl:grid-cols-6">
{statCards.map((card) => (
<StatCard
key={card.key}
label={card.label}
value={card.value}
detail={card.detail}
tone={card.tone}
icon={card.icon}
/>
))}
</div>
</div>
</section>
<AutonomousRuntimeReceiptPanel mode="full" />
<section className="grid gap-3 md:grid-cols-3">
{NAV_ITEMS.map((item) => {
const Icon = item.icon;
return (
<Link
key={item.href}
href={item.href}
className="group flex min-w-0 items-center justify-between gap-3 border border-[#e0ddd4] bg-white px-4 py-3 text-[#141413] hover:border-[#d97757]"
>
<span className="flex min-w-0 items-center gap-3">
<span className="flex h-9 w-9 shrink-0 items-center justify-center border border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52] group-hover:border-[#d97757] group-hover:text-[#141413]">
<Icon className="h-4 w-4" aria-hidden="true" />
</span>
<span className="min-w-0">
<span className="block truncate text-sm font-semibold">{t(item.labelKey)}</span>
<span className="mt-0.5 block truncate text-xs text-[#77736a]">
{t(`details.${item.labelKey}` as never)}
</span>
</span>
</span>
<ArrowRight className="h-4 w-4 shrink-0 text-[#87867f] group-hover:text-[#d97757]" aria-hidden="true" />
</Link>
);
})}
</section>
</div>
</main>
);