feat(ui): show ai loop log source tags
Some checks failed
CD Pipeline / workflow-shape (push) Has been cancelled
CD Pipeline / cancel-stale-cd (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Has been cancelled
CD Pipeline / cancel-stale-cd (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
This commit is contained in:
@@ -1013,6 +1013,36 @@ type ReportSourceHealthResponse = {
|
||||
};
|
||||
};
|
||||
|
||||
type AiLoopLogSourceTag = {
|
||||
tag_key?: string | null;
|
||||
tag_value?: string | null;
|
||||
metadata_only?: boolean | null;
|
||||
raw_output_allowed?: boolean | null;
|
||||
secret_value_allowed?: boolean | null;
|
||||
};
|
||||
|
||||
type AiLoopLogSourceContract = {
|
||||
tag_key?: string | null;
|
||||
purpose?: string | null;
|
||||
};
|
||||
|
||||
type PriorityWorkOrderResponse = {
|
||||
summary?: {
|
||||
ai_loop_current_blocker_id?: string | null;
|
||||
ai_loop_current_blocker_log_source_tag_count?: number | null;
|
||||
ai_loop_current_blocker_log_source_tag_keys?: string[] | null;
|
||||
ai_loop_log_source_grouping_key_count?: number | null;
|
||||
ai_loop_log_source_grouping_keys?: string[] | null;
|
||||
ai_loop_log_source_tagging_contract_count?: number | null;
|
||||
} | null;
|
||||
in_progress_or_blocked_in_priority_order?: Array<{
|
||||
evidence?: {
|
||||
ai_loop_current_blocker_log_source_tags?: AiLoopLogSourceTag[] | null;
|
||||
ai_loop_log_source_tagging_contract?: AiLoopLogSourceContract[] | null;
|
||||
} | null;
|
||||
}>;
|
||||
};
|
||||
|
||||
type Telemetry = {
|
||||
quality: AutomationQualitySummary | null;
|
||||
governanceEvents: GovernanceEventsResponse | null;
|
||||
@@ -1034,6 +1064,7 @@ type Telemetry = {
|
||||
incidentTimeline: IncidentTimelineResponse | null;
|
||||
aiRouteStatus: AiRouteStatusResponse | null;
|
||||
reportSourceHealth: ReportSourceHealthResponse | null;
|
||||
priorityWorkOrder: PriorityWorkOrderResponse | null;
|
||||
};
|
||||
|
||||
type AutomationAssetLedgerKey = "km" | "playbook" | "script" | "schedule" | "verifier";
|
||||
@@ -7729,6 +7760,153 @@ function DriftFingerprintPanel({
|
||||
);
|
||||
}
|
||||
|
||||
function tagDisplayLabel(
|
||||
key: string,
|
||||
labels: Record<string, string>
|
||||
): string {
|
||||
return labels[key] ?? key;
|
||||
}
|
||||
|
||||
function AiLoopLogSourceTagsPanel({
|
||||
priority,
|
||||
loading,
|
||||
}: {
|
||||
priority: PriorityWorkOrderResponse | null;
|
||||
loading: boolean;
|
||||
}) {
|
||||
const t = useTranslations("awooop.workItems.aiLoopLogSources");
|
||||
const evidence = priority?.in_progress_or_blocked_in_priority_order?.find(
|
||||
(item) => item.evidence?.ai_loop_current_blocker_log_source_tags?.length
|
||||
)?.evidence;
|
||||
const tags = (evidence?.ai_loop_current_blocker_log_source_tags ?? []).filter(
|
||||
(tag) => Boolean(tag?.tag_key)
|
||||
);
|
||||
const contracts = evidence?.ai_loop_log_source_tagging_contract ?? [];
|
||||
const groupingKeys = priority?.summary?.ai_loop_log_source_grouping_keys ?? [];
|
||||
const summary = priority?.summary;
|
||||
const labelMap: Record<string, string> = {
|
||||
project_id: t("tagLabels.projectId"),
|
||||
product: t("tagLabels.product"),
|
||||
site_or_route: t("tagLabels.siteOrRoute"),
|
||||
service: t("tagLabels.service"),
|
||||
package: t("tagLabels.package"),
|
||||
tool: t("tagLabels.tool"),
|
||||
source_system: t("tagLabels.sourceSystem"),
|
||||
runtime_component: t("tagLabels.runtimeComponent"),
|
||||
signal_lane: t("tagLabels.signalLane"),
|
||||
evidence_boundary: t("tagLabels.evidenceBoundary"),
|
||||
};
|
||||
const metrics = [
|
||||
{
|
||||
key: "tags",
|
||||
icon: Fingerprint,
|
||||
label: t("metrics.tags"),
|
||||
value: summary?.ai_loop_current_blocker_log_source_tag_count ?? tags.length,
|
||||
},
|
||||
{
|
||||
key: "groups",
|
||||
icon: Network,
|
||||
label: t("metrics.groups"),
|
||||
value: summary?.ai_loop_log_source_grouping_key_count ?? groupingKeys.length,
|
||||
},
|
||||
{
|
||||
key: "contract",
|
||||
icon: ListChecks,
|
||||
label: t("metrics.contract"),
|
||||
value: summary?.ai_loop_log_source_tagging_contract_count ?? contracts.length,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section
|
||||
className="border border-[#e0ddd4] bg-white"
|
||||
data-testid="ai-loop-log-source-tags-panel"
|
||||
>
|
||||
<div className="grid gap-px bg-[#e0ddd4] lg:grid-cols-[minmax(0,1.1fr)_minmax(0,1.6fr)]">
|
||||
<div className="bg-white p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Database className="h-5 w-5 text-[#4A90D9]" aria-hidden="true" />
|
||||
<div className="min-w-0">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-[0.08em] text-[#9a968c]">
|
||||
{t("eyebrow")}
|
||||
</p>
|
||||
<h3 className="mt-1 text-base font-semibold tracking-normal text-[#141413]">
|
||||
{t("title")}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-3 text-xs leading-5 text-[#77736a]">
|
||||
{t("subtitle")}
|
||||
</p>
|
||||
<div className="mt-4 grid gap-px border border-[#e0ddd4] bg-[#e0ddd4] sm:grid-cols-3">
|
||||
{metrics.map((metric) => {
|
||||
const Icon = metric.icon;
|
||||
return (
|
||||
<div key={metric.key} className="bg-[#faf9f3] p-3">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-[11px] font-semibold text-[#77736a]">
|
||||
{metric.label}
|
||||
</span>
|
||||
<Icon className="h-3.5 w-3.5 text-[#4A90D9]" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="mt-2 font-mono text-lg font-semibold text-[#141413]">
|
||||
{loading ? "--" : metric.value}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-3 border border-[#d8d3c7] bg-[#faf9f3] px-3 py-2 font-mono text-[11px] leading-5 text-[#5f5b52]">
|
||||
{t("blocker", {
|
||||
value: summary?.ai_loop_current_blocker_id ?? "--",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-4">
|
||||
{tags.length > 0 ? (
|
||||
<div className="grid gap-2 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{tags.map((tag) => {
|
||||
const key = String(tag.tag_key ?? "");
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className="min-w-0 border border-[#d8d3c7] bg-[#faf9f3] px-3 py-2"
|
||||
>
|
||||
<div className="text-[10px] font-semibold uppercase tracking-[0.08em] text-[#9a968c]">
|
||||
{tagDisplayLabel(key, labelMap)}
|
||||
</div>
|
||||
<div className="mt-1 break-all font-mono text-[11px] leading-5 text-[#141413]">
|
||||
{tag.tag_value || "--"}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="border border-[#d8d3c7] bg-[#faf9f3] px-3 py-6 text-center text-xs text-[#77736a]">
|
||||
{loading ? t("loading") : t("empty")}
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-3 flex flex-wrap gap-1.5">
|
||||
{groupingKeys.map((key) => (
|
||||
<span
|
||||
key={key}
|
||||
className="border border-[#c9d8ea] bg-[#eef5ff] px-2 py-0.5 font-mono text-[11px] text-[#1f5b9b]"
|
||||
>
|
||||
{tagDisplayLabel(key, labelMap)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<p className="mt-3 text-[11px] leading-5 text-[#77736a]">
|
||||
{t("boundary")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AwoooPWorkItemsPage() {
|
||||
const t = useTranslations("awooop.workItems");
|
||||
const locale = useLocale();
|
||||
@@ -7767,6 +7945,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
incidentTimeline: null,
|
||||
aiRouteStatus: null,
|
||||
reportSourceHealth: null,
|
||||
priorityWorkOrder: null,
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
||||
@@ -7816,6 +7995,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
projectId
|
||||
);
|
||||
const reportSourceHealthUrl = `${API_BASE}/api/v1/agents/agent-report-source-health`;
|
||||
const priorityWorkOrderUrl = `${API_BASE}/api/v1/agents/awoooi-priority-work-order-readback`;
|
||||
|
||||
const [
|
||||
quality,
|
||||
@@ -7836,6 +8016,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
callbackReplies,
|
||||
aiRouteStatus,
|
||||
reportSourceHealth,
|
||||
priorityWorkOrder,
|
||||
] = await Promise.all([
|
||||
fetchJson<AutomationQualitySummary>(qualityUrl, 15000),
|
||||
fetchJson<GovernanceEventsResponse>(governanceEventsUrl),
|
||||
@@ -7855,6 +8036,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
fetchJson<CallbackRepliesWorkItemResponse>(callbackRepliesUrl, 12000),
|
||||
fetchJson<AiRouteStatusResponse>(aiRouteStatusUrl, 12000),
|
||||
fetchJson<ReportSourceHealthResponse>(reportSourceHealthUrl, 12000),
|
||||
fetchJson<PriorityWorkOrderResponse>(priorityWorkOrderUrl, 12000),
|
||||
]);
|
||||
|
||||
const statusChainIncidentId = selectStatusChainIncidentId(
|
||||
@@ -7903,6 +8085,7 @@ export default function AwoooPWorkItemsPage() {
|
||||
incidentTimeline,
|
||||
aiRouteStatus,
|
||||
reportSourceHealth,
|
||||
priorityWorkOrder,
|
||||
});
|
||||
setLastUpdated(new Date());
|
||||
setLoading(false);
|
||||
@@ -7996,6 +8179,11 @@ export default function AwoooPWorkItemsPage() {
|
||||
|
||||
<AutonomousRuntimeReceiptPanel mode="compact" />
|
||||
|
||||
<AiLoopLogSourceTagsPanel
|
||||
priority={telemetry.priorityWorkOrder}
|
||||
loading={loading}
|
||||
/>
|
||||
|
||||
<OperatorSopRail
|
||||
telemetry={telemetry}
|
||||
workItems={workItems}
|
||||
|
||||
Reference in New Issue
Block a user