feat(web): surface AI automation production proof
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 37s
CD Pipeline / build-and-deploy (push) Successful in 4m41s
CD Pipeline / post-deploy-checks (push) Successful in 1m43s
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 37s
CD Pipeline / build-and-deploy (push) Successful in 4m41s
CD Pipeline / post-deploy-checks (push) Successful in 1m43s
This commit is contained in:
@@ -3878,8 +3878,8 @@
|
||||
"riskTitle": "Controlled risk lanes",
|
||||
"on": "ON",
|
||||
"off": "OFF",
|
||||
"ownerRequired": "owner review required",
|
||||
"ownerNotRequired": "owner review not required",
|
||||
"ownerRequired": "Controlled evidence review required",
|
||||
"ownerNotRequired": "Controlled evidence review cleared",
|
||||
"criticalBreakGlass": "critical break-glass",
|
||||
"criticalReview": "critical review"
|
||||
},
|
||||
@@ -11585,6 +11585,20 @@
|
||||
"playbook": "PlayBook",
|
||||
"telegram": "Telegram"
|
||||
},
|
||||
"proof": {
|
||||
"deploy": "Production deploy",
|
||||
"deployDetail": "CD / readback: {status}",
|
||||
"runtime": "Runtime DB",
|
||||
"runtimeDetail": "marker: {marker}",
|
||||
"workItems": "Work complete",
|
||||
"workItemsDetail": "{percent}% complete",
|
||||
"sources": "Log sources",
|
||||
"sourcesDetail": "Project, product, site, service, package, and tool",
|
||||
"events": "Classified events",
|
||||
"eventsDetail": "24h {recent}",
|
||||
"ok": "ok",
|
||||
"degraded": "degraded"
|
||||
},
|
||||
"recent": "24h {count}",
|
||||
"missing": "{count} missing",
|
||||
"closedDetail": "required stages ok",
|
||||
|
||||
@@ -3878,8 +3878,8 @@
|
||||
"riskTitle": "Controlled risk lanes",
|
||||
"on": "ON",
|
||||
"off": "OFF",
|
||||
"ownerRequired": "owner review required",
|
||||
"ownerNotRequired": "owner review not required",
|
||||
"ownerRequired": "受控證據複核需要補齊",
|
||||
"ownerNotRequired": "受控證據複核已免除",
|
||||
"criticalBreakGlass": "critical break-glass",
|
||||
"criticalReview": "critical review"
|
||||
},
|
||||
@@ -11585,6 +11585,20 @@
|
||||
"playbook": "PlayBook",
|
||||
"telegram": "Telegram"
|
||||
},
|
||||
"proof": {
|
||||
"deploy": "正式部署",
|
||||
"deployDetail": "CD / readback:{status}",
|
||||
"runtime": "Runtime DB",
|
||||
"runtimeDetail": "marker:{marker}",
|
||||
"workItems": "工作完成",
|
||||
"workItemsDetail": "完成度 {percent}%",
|
||||
"sources": "Log 來源",
|
||||
"sourcesDetail": "專案 / 產品 / 網站 / 服務 / 套件 / 工具",
|
||||
"events": "分類事件",
|
||||
"eventsDetail": "近 24h {recent}",
|
||||
"ok": "ok",
|
||||
"degraded": "degraded"
|
||||
},
|
||||
"recent": "近 24h {count}",
|
||||
"missing": "缺 {count} 節點",
|
||||
"closedDetail": "required stages ok",
|
||||
|
||||
@@ -9,9 +9,11 @@ import {
|
||||
Bot,
|
||||
BookOpenCheck,
|
||||
CheckCircle2,
|
||||
Database,
|
||||
Gauge,
|
||||
ListChecks,
|
||||
RefreshCw,
|
||||
Rocket,
|
||||
Send,
|
||||
ShieldCheck,
|
||||
SlidersHorizontal,
|
||||
@@ -182,6 +184,22 @@ type RuntimeControlPayload = {
|
||||
rollups?: Record<string, number | string | boolean | null | undefined> | null;
|
||||
};
|
||||
|
||||
type PriorityWorkOrderPayload = {
|
||||
status?: string | null;
|
||||
summary?: {
|
||||
latest_successful_deploy_marker?: string | null;
|
||||
latest_successful_deployed_source_sha?: string | null;
|
||||
latest_successful_deployed_source_short_sha?: string | null;
|
||||
ai_loop_current_blocker_deploy_marker_readback_required?: boolean | null;
|
||||
ai_loop_current_blocker_deploy_marker_resolved_by_production_readback?: boolean | null;
|
||||
} | null;
|
||||
mainline_execution_state?: {
|
||||
current_main_cd_run_id?: string | null;
|
||||
current_main_cd_run_status?: string | null;
|
||||
current_main_latest_source_sha?: string | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
type PanelMode = "full" | "compact";
|
||||
type Tone = "ok" | "warn" | "neutral";
|
||||
type WorkFilter = "all" | "completed" | "active" | "pending" | "blocked";
|
||||
@@ -262,6 +280,23 @@ async function fetchRuntimeControl(): Promise<RuntimeControlPayload | null> {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPriorityWorkOrder(): Promise<PriorityWorkOrderPayload | null> {
|
||||
const controller = new AbortController();
|
||||
const timeout = window.setTimeout(() => controller.abort(), 12_000);
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/v1/agents/awoooi-priority-work-order-readback`, {
|
||||
cache: "no-store",
|
||||
signal: controller.signal,
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
return (await response.json()) as PriorityWorkOrderPayload;
|
||||
} catch {
|
||||
return null;
|
||||
} finally {
|
||||
window.clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
export function AutonomousRuntimeReceiptPanel({
|
||||
mode = "full",
|
||||
}: {
|
||||
@@ -270,6 +305,7 @@ export function AutonomousRuntimeReceiptPanel({
|
||||
const t = useTranslations("awooop.autonomousRuntime");
|
||||
const locale = useLocale();
|
||||
const [payload, setPayload] = useState<RuntimeControlPayload | null>(null);
|
||||
const [priorityPayload, setPriorityPayload] = useState<PriorityWorkOrderPayload | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(false);
|
||||
const [updatedAt, setUpdatedAt] = useState<Date | null>(null);
|
||||
@@ -277,8 +313,12 @@ export function AutonomousRuntimeReceiptPanel({
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setLoading(true);
|
||||
const next = await fetchRuntimeControl();
|
||||
const [next, priority] = await Promise.all([
|
||||
fetchRuntimeControl(),
|
||||
fetchPriorityWorkOrder(),
|
||||
]);
|
||||
setPayload(next);
|
||||
setPriorityPayload(priority);
|
||||
setError(next === null);
|
||||
setUpdatedAt(next ? new Date() : null);
|
||||
setLoading(false);
|
||||
@@ -510,6 +550,64 @@ export function AutonomousRuntimeReceiptPanel({
|
||||
const workCompletionPercent = workTotal > 0
|
||||
? Math.min(100, Math.round((workCompleted / workTotal) * 100))
|
||||
: 0;
|
||||
const prioritySummary = priorityPayload?.summary;
|
||||
const mainlineState = priorityPayload?.mainline_execution_state;
|
||||
const deployedSourceSha = prioritySummary?.latest_successful_deployed_source_sha
|
||||
?? mainlineState?.current_main_latest_source_sha
|
||||
?? null;
|
||||
const deployedSourceShortSha = prioritySummary?.latest_successful_deployed_source_short_sha
|
||||
?? (deployedSourceSha ? deployedSourceSha.slice(0, 10) : null);
|
||||
const deployReadbackResolved = prioritySummary?.ai_loop_current_blocker_deploy_marker_resolved_by_production_readback === true;
|
||||
const proofCards = [
|
||||
{
|
||||
key: "deploy",
|
||||
label: t("proof.deploy"),
|
||||
value: deployedSourceShortSha ?? "--",
|
||||
detail: t("proof.deployDetail", {
|
||||
status: mainlineState?.current_main_cd_run_status ?? priorityPayload?.status ?? "--",
|
||||
}),
|
||||
icon: Rocket,
|
||||
tone: deployReadbackResolved ? "ok" as Tone : "warn" as Tone,
|
||||
},
|
||||
{
|
||||
key: "runtime",
|
||||
label: t("proof.runtime"),
|
||||
value: dbOk ? t("proof.ok") : t("proof.degraded"),
|
||||
detail: t("proof.runtimeDetail", {
|
||||
marker: shortRef(payload?.program_status?.deploy_readback_marker),
|
||||
}),
|
||||
icon: Database,
|
||||
tone: dbOk ? "ok" as Tone : "warn" as Tone,
|
||||
},
|
||||
{
|
||||
key: "work",
|
||||
label: t("proof.workItems"),
|
||||
value: `${numberValue(workCompleted)}/${numberValue(workTotal)}`,
|
||||
detail: t("proof.workItemsDetail", {
|
||||
percent: numberValue(workCompletionPercent),
|
||||
}),
|
||||
icon: ListChecks,
|
||||
tone: workTotal > 0 && workCompleted === workTotal ? "ok" as Tone : "warn" as Tone,
|
||||
},
|
||||
{
|
||||
key: "sources",
|
||||
label: t("proof.sources"),
|
||||
value: `${numberValue(rollups.live_log_active_source_family_count ?? logRollups.active_source_family_count)}/${numberValue(rollups.live_log_source_family_count ?? logRollups.source_family_count)}`,
|
||||
detail: t("proof.sourcesDetail"),
|
||||
icon: Bot,
|
||||
tone: toNumber(rollups.live_log_active_source_family_count ?? logRollups.active_source_family_count) > 0 ? "ok" as Tone : "warn" as Tone,
|
||||
},
|
||||
{
|
||||
key: "events",
|
||||
label: t("proof.events"),
|
||||
value: numberValue(rollups.live_log_classified_event_total ?? logRollups.classified_event_total),
|
||||
detail: t("proof.eventsDetail", {
|
||||
recent: numberValue(rollups.live_log_recent_classified_event_total ?? logRollups.recent_classified_event_total),
|
||||
}),
|
||||
icon: Activity,
|
||||
tone: toNumber(rollups.live_log_classified_event_total ?? logRollups.classified_event_total) > 0 ? "ok" as Tone : "neutral" as Tone,
|
||||
},
|
||||
];
|
||||
const visibleWorkItems = orderedWorkItems.filter((item) => matchesWorkFilter(item, workFilter));
|
||||
const orderedCompleted = orderedWorkItems.filter((item) => item.status === "completed").length;
|
||||
const orderedActive = orderedWorkItems.filter((item) => (
|
||||
@@ -574,6 +672,33 @@ export function AutonomousRuntimeReceiptPanel({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-testid="ai-automation-production-proof"
|
||||
className="grid gap-px border-b border-[#e0ddd4] bg-[#e0ddd4] md:grid-cols-5"
|
||||
>
|
||||
{proofCards.map((card) => {
|
||||
const Icon = card.icon;
|
||||
return (
|
||||
<div key={card.key} className="bg-white px-4 py-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{card.label}</p>
|
||||
<p className="mt-2 truncate font-mono text-xl font-semibold text-[#141413]">
|
||||
{card.value}
|
||||
</p>
|
||||
</div>
|
||||
<span className={cn("flex h-8 w-8 shrink-0 items-center justify-center border", toneClass(card.tone))}>
|
||||
<Icon className="h-4 w-4" aria-hidden="true" />
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-2 line-clamp-2 text-xs leading-5 text-[#5f5b52]">
|
||||
{card.detail}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-px bg-[#e0ddd4] md:grid-cols-5 xl:grid-cols-12">
|
||||
<div className="bg-white px-4 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{t("metrics.loop")}</p>
|
||||
|
||||
Reference in New Issue
Block a user