feat(awooop): surface degraded ai route lanes
All checks were successful
CD Pipeline / tests (push) Successful in 1m25s
Code Review / ai-code-review (push) Successful in 12s
CD Pipeline / build-and-deploy (push) Successful in 3m37s
CD Pipeline / post-deploy-checks (push) Successful in 1m44s

This commit is contained in:
Your Name
2026-05-25 13:24:53 +08:00
parent 19d306c720
commit ed3e658578
8 changed files with 455 additions and 17 deletions

View File

@@ -428,6 +428,23 @@ interface AiRouteHealthItem {
checked?: boolean;
}
interface AiRouteLaneItem {
priority?: number | null;
provider_name?: string | null;
role?: string | null;
runtime?: string | null;
url?: string | null;
health_status?: string | null;
reason?: string | null;
action_required?: boolean;
}
interface AiRouteOperatorAction {
human_required?: boolean;
action?: string | null;
reason?: string | null;
}
interface AiRouteStatusResponse {
schema_version: string;
workload_type: string;
@@ -440,6 +457,10 @@ interface AiRouteStatusResponse {
route_source: string;
route_error?: string | null;
health: Record<string, AiRouteHealthItem>;
lane_mode?: string | null;
active_lane?: AiRouteLaneItem | null;
skipped_lanes?: AiRouteLaneItem[];
operator_action?: AiRouteOperatorAction | null;
checked_at: string;
}
@@ -2005,6 +2026,30 @@ function aiRouteRoleLabelKey(role?: string | null) {
return "roles.ollama";
}
function aiRouteLaneModeLabelKey(mode?: string | null) {
if (
mode === "primary" ||
mode === "degraded_failover" ||
mode === "cloud_fallback" ||
mode === "unavailable"
) {
return `laneModes.${mode}`;
}
return "laneModes.unknown";
}
function aiRouteOperatorActionLabelKey(action?: string | null) {
if (
action === "monitor" ||
action === "repair_skipped_primary_lane" ||
action === "restore_ollama_lanes" ||
action === "inspect_ai_router"
) {
return `operatorActions.${action}`;
}
return "operatorActions.unknown";
}
function AiRouteStatusPanel({
status,
error,
@@ -2016,6 +2061,15 @@ function AiRouteStatusPanel({
const policy = status?.policy_order ?? [];
const selectedProvider = status?.selected_provider ?? null;
const selectedModel = status?.selected_model ?? null;
const laneMode = status?.lane_mode ?? null;
const laneModeKey = aiRouteLaneModeLabelKey(laneMode);
const operatorActionKey = aiRouteOperatorActionLabelKey(status?.operator_action?.action);
const skippedLanes = status?.skipped_lanes ?? [];
const skippedProviderSet = new Set(
skippedLanes
.map((lane) => lane.provider_name)
.filter((provider): provider is string => Boolean(provider))
);
const checkedAt = status?.checked_at
? new Date(status.checked_at).toLocaleTimeString("zh-TW", {
hour: "2-digit",
@@ -2033,7 +2087,14 @@ function AiRouteStatusPanel({
<p className="text-xs text-[#77736a]">{t("subtitle")}</p>
</div>
</div>
<span className="border border-[#9bb6d9] bg-[#eef5ff] px-2 py-0.5 text-xs font-semibold text-[#1f5b9b]">
<span
className={cn(
"border px-2 py-0.5 text-xs font-semibold",
laneMode === "primary"
? "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]"
: "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]"
)}
>
{selectedProvider
? t("selected", { provider: selectedProvider })
: t("selectedEmpty")}
@@ -2050,7 +2111,28 @@ function AiRouteStatusPanel({
</div>
) : (
<>
<div className="grid gap-px bg-[#e0ddd4] md:grid-cols-3">
{laneMode && laneMode !== "primary" && (
<div className="flex items-start gap-3 border-b border-[#e0ddd4] bg-[#fff7e8] px-4 py-3 text-sm text-[#6d4707]">
<TriangleAlert className="mt-0.5 h-4 w-4 shrink-0" aria-hidden="true" />
<div className="min-w-0">
<p className="font-semibold text-[#141413]">
{t(laneModeKey as never)}
</p>
<p className="mt-1 leading-5">
{t("degradedSummary", {
active: selectedProvider ?? "--",
skipped: skippedLanes
.map((lane) => lane.provider_name)
.filter(Boolean)
.join(" -> ") || "--",
action: t(operatorActionKey as never),
})}
</p>
</div>
</div>
)}
<div className="grid gap-px bg-[#e0ddd4] md:grid-cols-4">
<div className="bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{t("fields.workload")}</p>
<p className="mt-2 font-mono text-sm font-semibold text-[#141413]">
@@ -2060,6 +2142,15 @@ function AiRouteStatusPanel({
{t("fields.checkedAt", { time: checkedAt })}
</p>
</div>
<div className="bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{t("fields.laneMode")}</p>
<p className="mt-2 text-sm font-semibold text-[#141413]">
{t(laneModeKey as never)}
</p>
<p className="mt-2 text-xs leading-5 text-[#5f5b52]">
{t(operatorActionKey as never)}
</p>
</div>
<div className="bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{t("fields.primary")}</p>
<p className="mt-2 truncate font-mono text-sm font-semibold text-[#141413]">
@@ -2090,6 +2181,7 @@ function AiRouteStatusPanel({
const healthKey = aiRouteHealthLabelKey(health?.status);
const roleKey = aiRouteRoleLabelKey(item.role);
const isSelected = selectedProvider === item.provider_name;
const isSkipped = skippedProviderSet.has(item.provider_name);
const latency = typeof health?.latency_ms === "number"
? `${health.latency_ms.toFixed(1)}ms`
: "--";
@@ -2110,12 +2202,18 @@ function AiRouteStatusPanel({
"shrink-0 border px-2 py-0.5 text-xs font-semibold",
isSelected
? "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]"
: isSkipped
? "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]"
: item.runtime === "cloud"
? "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]"
: "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]"
)}
>
{isSelected ? t("badges.active") : t("badges.standby")}
{isSelected
? t("badges.active")
: isSkipped
? t("badges.skipped")
: t("badges.standby")}
</span>
</div>
<div className="mt-3 space-y-1 text-xs leading-5 text-[#5f5b52]">
@@ -2124,6 +2222,9 @@ function AiRouteStatusPanel({
<p className="truncate font-mono text-[#77736a]">
{item.url || t("fields.noUrl")}
</p>
<p className="line-clamp-2 text-[#77736a]">
{health?.reason || item.reason || "--"}
</p>
</div>
</article>
);

View File

@@ -120,6 +120,12 @@ interface AiRouteStatusResponse {
route_reason?: string | null
route_error?: string | null
health?: Record<string, AiRouteHealthItem>
lane_mode?: string | null
skipped_lanes?: Array<{ provider_name?: string | null }>
operator_action?: {
action?: string | null
human_required?: boolean
} | null
}
interface EvidenceSnapshot {
@@ -185,6 +191,18 @@ function routeHealthLabelKey(status?: string | null) {
return 'routeHealth.unknown'
}
function routeLaneModeLabelKey(mode?: string | null) {
if (
mode === 'primary' ||
mode === 'degraded_failover' ||
mode === 'cloud_fallback' ||
mode === 'unavailable'
) {
return `routeLaneMode.${mode}`
}
return 'routeLaneMode.unknown'
}
function providerDisplayName(provider?: string | null) {
switch (provider) {
case 'ollama_gcp_a':
@@ -353,6 +371,10 @@ export function AutomationEvidenceCard() {
const primaryProvider = route?.policy_order?.[0]?.provider_name ?? null
const primaryStatus = primaryProvider ? route?.health?.[primaryProvider]?.status : null
const selectedProvider = providerDisplayName(route?.selected_provider)
const laneMode = route?.lane_mode ?? null
const skippedLanes = route?.skipped_lanes
?.map((lane) => providerDisplayName(lane.provider_name))
.join(' -> ')
const fallback = route?.fallback_chain
?.map((item) => item.provider_name)
.map(providerDisplayName)
@@ -366,8 +388,14 @@ export function AutomationEvidenceCard() {
primaryStatus: t(routeHealthLabelKey(primaryStatus) as never),
fallback: fallback || t('routeNoFallback'),
})
const laneDetail = laneMode && laneMode !== 'primary'
? t('routeLaneDetail', {
mode: t(routeLaneModeLabelKey(laneMode) as never),
skipped: skippedLanes || '--',
})
: null
const routeDetail = route?.route_reason && !route.route_error
? `${routeSummary}${t('routeReasonSeparator')}${t('routeReason', { reason: route.route_reason })}`
? `${routeSummary}${laneDetail ? t('routeReasonSeparator') + laneDetail : ''}${t('routeReasonSeparator')}${t('routeReason', { reason: route.route_reason })}`
: routeSummary
return {