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:
@@ -9034,6 +9034,32 @@
|
||||
"watching": "觀察期",
|
||||
"blocked": "阻塞"
|
||||
},
|
||||
"aiLoopLogSources": {
|
||||
"eyebrow": "AI Loop source labels",
|
||||
"title": "LOG grouping and learning route",
|
||||
"subtitle": "Reads metadata-only tags for the current blocker from the priority work-order so KM, RAG, PlayBook, MCP, Verifier, and AI Agent share one source taxonomy.",
|
||||
"loading": "Loading LOG source labels",
|
||||
"empty": "No LOG source labels read back yet.",
|
||||
"blocker": "Current blocker: {value}",
|
||||
"boundary": "Metadata-only labels; no raw log read, no secret display, no writeback, and no runtime apply from this panel.",
|
||||
"metrics": {
|
||||
"tags": "Tags",
|
||||
"groups": "Groups",
|
||||
"contract": "Contract"
|
||||
},
|
||||
"tagLabels": {
|
||||
"projectId": "Project",
|
||||
"product": "Product",
|
||||
"siteOrRoute": "Site / route",
|
||||
"service": "Service",
|
||||
"package": "Package",
|
||||
"tool": "Tool",
|
||||
"sourceSystem": "Source system",
|
||||
"runtimeComponent": "Runtime",
|
||||
"signalLane": "Signal lane",
|
||||
"evidenceBoundary": "Evidence boundary"
|
||||
}
|
||||
},
|
||||
"operatorSop": {
|
||||
"eyebrow": "操作 SOP 判讀",
|
||||
"title": "AI 受控卡點與自動化缺口接手面板",
|
||||
|
||||
@@ -9034,6 +9034,32 @@
|
||||
"watching": "觀察期",
|
||||
"blocked": "阻塞"
|
||||
},
|
||||
"aiLoopLogSources": {
|
||||
"eyebrow": "AI Loop 來源貼標",
|
||||
"title": "LOG 分群與學習路由",
|
||||
"subtitle": "從 priority work-order 讀回 current blocker 的 metadata-only tags,讓 KM、RAG、PlayBook、MCP、Verifier 與 AI Agent 用同一組來源維度學習。",
|
||||
"loading": "讀取 LOG 來源貼標中",
|
||||
"empty": "尚未讀回 LOG 來源貼標。",
|
||||
"blocker": "Current blocker:{value}",
|
||||
"boundary": "只顯示 metadata-only 標籤;不讀 raw log、不顯示 secret、不觸發寫入或 runtime apply。",
|
||||
"metrics": {
|
||||
"tags": "Tags",
|
||||
"groups": "分群鍵",
|
||||
"contract": "Contract"
|
||||
},
|
||||
"tagLabels": {
|
||||
"projectId": "專案",
|
||||
"product": "產品",
|
||||
"siteOrRoute": "網站 / 路由",
|
||||
"service": "服務",
|
||||
"package": "套件",
|
||||
"tool": "工具",
|
||||
"sourceSystem": "來源系統",
|
||||
"runtimeComponent": "Runtime",
|
||||
"signalLane": "訊號 Lane",
|
||||
"evidenceBoundary": "證據邊界"
|
||||
}
|
||||
},
|
||||
"operatorSop": {
|
||||
"eyebrow": "操作 SOP 判讀",
|
||||
"title": "AI 受控卡點與自動化缺口接手面板",
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
## 2026-07-01 — 14:55 Work Items 顯示 AI Loop LOG source tags
|
||||
|
||||
**照主線修正的問題**:
|
||||
- API 已輸出 current blocker 的 LOG 來源貼標與分群 contract,但 AwoooP Work Items 尚未把這些 tags 顯示出來,使用者仍只能從長段 evidence 文字判斷 project / product / service / package / tool / source_system / signal_lane。
|
||||
- 本次在 `/awooop/work-items` telemetry 加入 `/api/v1/agents/awoooi-priority-work-order-readback`,新增 `AI Loop 來源貼標` 面板,直接顯示 current blocker、tag count、grouping keys、contract count 與 10 個 metadata-only source tags。
|
||||
- 這讓 Work Items 第一屏能看見 LOG 已依 project、product、site_or_route、service、package、tool、source_system、runtime_component、signal_lane、evidence_boundary 分群,後續 UI 可用同一 contract 做篩選或 chips,不再只靠大段描述文字。
|
||||
|
||||
**驗證**:
|
||||
- `python3.11 -m json.tool --indent 2 apps/web/messages/zh-TW.json >/dev/null && python3.11 -m json.tool --indent 2 apps/web/messages/en.json >/dev/null`:通過。
|
||||
- `python3.11 scripts/security/awooop-controlled-automation-copy-guard.py`:`AWOOOP_CONTROLLED_AUTOMATION_COPY_GUARD_OK`。
|
||||
- `python3.11 scripts/dev/awoooi-navigation-coverage-guard.py`:`AWOOOI_NAVIGATION_COVERAGE_GUARD_OK routes=47 nav_routes=46 nav_label_keys=50`。
|
||||
- `git diff --check`:通過。
|
||||
- `pnpm --dir apps/web typecheck`:未完成,本機 `node_modules` 仍缺 `typescript@5.9.3/.../bin/tsc`,不是 TypeScript 診斷。
|
||||
|
||||
**邊界**:只改 AwoooP Work Items UI、i18n 與 LOGBOOK;未使用 GitHub / `gh` / GitHub API;未 workflow_dispatch;未讀 secret / token / `.env` / raw sessions / SQLite / auth;未執行 runtime apply。
|
||||
|
||||
## 2026-07-01 — 14:35 AI Loop LOG 來源貼標與分群 contract
|
||||
|
||||
**照主線修正的問題**:
|
||||
|
||||
Reference in New Issue
Block a user