feat(web): surface awooop agent evidence chain
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Activity, CheckCircle2, Link2, RadioTower, Route, ShieldAlert, TriangleAlert } from "lucide-react";
|
||||
import { Activity, BookOpenCheck, CheckCircle2, Link2, RadioTower, Route, ShieldAlert, TriangleAlert, Wrench } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -316,6 +316,110 @@ export function AwoooPStatusChainPanel({
|
||||
}),
|
||||
},
|
||||
] : [];
|
||||
const mcpGateway = chain.mcp?.gateway ?? {};
|
||||
const legacyMcp = chain.mcp?.legacy ?? {};
|
||||
const topTool = chain.mcp?.top_tools?.[0];
|
||||
const execution = chain.execution ?? {};
|
||||
const ansible = execution.ansible ?? {};
|
||||
const candidatePlaybook = ansible.candidate_playbooks?.[0];
|
||||
const selectedPlaybook = execution.playbook_paths?.[0]
|
||||
?? execution.playbook_ids?.[0]
|
||||
?? ansible.latest_playbook_path
|
||||
?? candidatePlaybook?.playbook_path
|
||||
?? candidatePlaybook?.catalog_id
|
||||
?? emptyLabel;
|
||||
const mcpGatewayTotal = mcpGateway.total ?? 0;
|
||||
const mcpGatewayProblemTotal = (mcpGateway.failed ?? 0) + (mcpGateway.blocked ?? 0);
|
||||
const executionTotal = execution.operation_total ?? 0;
|
||||
const sourceToolchainTone: SourceFlowTone = sourceCorrelation
|
||||
? (sourceLinkVerified ? "success" : (sourceVerificationBlocked ? "blocked" : "warning"))
|
||||
: "neutral";
|
||||
const toolchainItems = [
|
||||
{
|
||||
key: "mcp",
|
||||
Icon: RadioTower,
|
||||
tone: (mcpGatewayTotal > 0
|
||||
? (mcpGatewayProblemTotal > 0 ? "warning" : "success")
|
||||
: "neutral") as SourceFlowTone,
|
||||
label: t("toolchain.mcp"),
|
||||
value: t("toolchain.mcpValue", {
|
||||
success: mcpGateway.success ?? 0,
|
||||
total: mcpGatewayTotal,
|
||||
failed: mcpGateway.failed ?? 0,
|
||||
blocked: mcpGateway.blocked ?? 0,
|
||||
}),
|
||||
detail: t("toolchain.mcpDetail", {
|
||||
topTool: topTool?.tool_name ?? emptyLabel,
|
||||
firstClass: mcpGateway.first_class_total ?? 0,
|
||||
legacy: legacyMcp.total ?? 0,
|
||||
policy: mcpGateway.policy_enforced_total ?? 0,
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: "source",
|
||||
Icon: Link2,
|
||||
tone: sourceToolchainTone,
|
||||
label: t("toolchain.source"),
|
||||
value: t("toolchain.sourceValue", {
|
||||
status: sourceStatusLabels[sourceStatus] ?? valueOrEmpty(sourceStatus, emptyLabel),
|
||||
direct: directRefTotal,
|
||||
candidate: candidateTotal,
|
||||
applied: appliedLinkTotal,
|
||||
}),
|
||||
detail: t("toolchain.sourceDetail", {
|
||||
providers: sourceProviderSummary || emptyLabel,
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: "execution",
|
||||
Icon: Activity,
|
||||
tone: (executionTotal > 0
|
||||
? (String(execution.latest_status ?? "").toLowerCase().includes("fail") ? "blocked" : "success")
|
||||
: "neutral") as SourceFlowTone,
|
||||
label: t("toolchain.execution"),
|
||||
value: t("toolchain.executionValue", {
|
||||
executor: execution.latest_executor ?? emptyLabel,
|
||||
status: execution.latest_status ?? emptyLabel,
|
||||
}),
|
||||
detail: t("toolchain.executionDetail", {
|
||||
operation: execution.latest_operation_type ?? emptyLabel,
|
||||
action: execution.latest_action ?? emptyLabel,
|
||||
ops: executionTotal,
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: "playbook",
|
||||
Icon: Wrench,
|
||||
tone: (ansible.considered || (ansible.candidate_count ?? 0) > 0
|
||||
? (ansible.latest_status === "applied" ? "success" : "warning")
|
||||
: "neutral") as SourceFlowTone,
|
||||
label: t("toolchain.playbook"),
|
||||
value: selectedPlaybook,
|
||||
detail: t("toolchain.playbookDetail", {
|
||||
ansible: boolValue(ansible.considered, emptyLabel),
|
||||
candidates: ansible.candidate_count ?? 0,
|
||||
checkMode: valueOrEmpty(ansible.latest_check_mode, emptyLabel),
|
||||
status: ansible.latest_status ?? emptyLabel,
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: "learning",
|
||||
Icon: BookOpenCheck,
|
||||
tone: ((evidence.knowledge_entries ?? 0) > 0
|
||||
? ((evidence.auto_repair_records ?? 0) > 0 ? "success" : "warning")
|
||||
: "neutral") as SourceFlowTone,
|
||||
label: t("toolchain.learning"),
|
||||
value: t("toolchain.learningValue", {
|
||||
km: evidence.knowledge_entries ?? 0,
|
||||
autoRepair: evidence.auto_repair_records ?? 0,
|
||||
ops: evidence.operation_records ?? 0,
|
||||
}),
|
||||
detail: t("toolchain.learningDetail", {
|
||||
verification: valueOrEmpty(chain.verification, emptyLabel),
|
||||
nextStep: valueOrEmpty(chain.next_step, emptyLabel),
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className={cn("border border-[#e0ddd4] bg-white", className)}>
|
||||
@@ -388,6 +492,33 @@ export function AwoooPStatusChainPanel({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-[#e0ddd4] bg-[#faf9f3] px-4 py-2">
|
||||
<p className="text-xs font-semibold text-[#141413]">{t("toolchain.title")}</p>
|
||||
</div>
|
||||
<div className={cn("grid gap-px bg-[#e0ddd4]", compact ? "md:grid-cols-2" : "md:grid-cols-5")}>
|
||||
{toolchainItems.map((item) => (
|
||||
<div key={item.key} className="min-w-0 bg-white px-4 py-3">
|
||||
<div className="flex min-w-0 items-start gap-3">
|
||||
<span className={cn(
|
||||
"flex h-8 w-8 shrink-0 items-center justify-center border",
|
||||
sourceFlowToneClass(item.tone)
|
||||
)}>
|
||||
<item.Icon className="h-4 w-4" aria-hidden="true" />
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{item.label}</p>
|
||||
<p className="mt-1 truncate font-mono text-sm font-semibold text-[#141413]" title={item.value}>
|
||||
{item.value}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 truncate font-mono text-xs text-[#5f5b52]" title={item.detail}>
|
||||
{item.detail}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{sourceCorrelation && (
|
||||
<div>
|
||||
<div className={cn("grid gap-px bg-[#e0ddd4]", compact ? "grid-cols-1" : "md:grid-cols-3")}>
|
||||
|
||||
Reference in New Issue
Block a user