From dc6039c6eac7efcd9fe6cad3d63e44de45e5d14a Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 3 Jun 2026 09:20:01 +0800 Subject: [PATCH] fix(web): show knowledge governance flow --- apps/web/messages/en.json | 27 ++++ apps/web/messages/zh-TW.json | 27 ++++ .../src/app/[locale]/knowledge-base/page.tsx | 119 ++++++++++++++++++ 3 files changed, 173 insertions(+) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 2a3de4c1..6713fc1e 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -1482,6 +1482,33 @@ "owner": "Owner:在 AwoooP Work Items 預覽後確認寫回。", "noWritesOnRead": "讀取不寫入", "unexpectedWrites": "偵測到 read endpoint 宣告會寫入", + "flow": { + "title": "治理流程圖", + "scope": "從偵測到寫回與比例回測的目前位置", + "node": { + "detected": "偵測", + "ownerReview": "Owner Review", + "dryRun": "乾跑預覽", + "ownerConfirm": "Owner 確認", + "writeback": "寫回 KM", + "recheck": "比例回測" + }, + "state": { + "warning": "需處理", + "ready": "可操作", + "waiting": "等待", + "done": "已有證據", + "blocked": "卡住" + }, + "detail": { + "detected": "目前 {ratio};門檻 {threshold}", + "ownerReview": "{count} 筆等待 owner 審核", + "dryRun": "{ready} 筆可乾跑;{blocked} 筆卡住", + "ownerConfirm": "確認後才允許寫回,避免 AI 固化錯誤知識", + "writeback": "{count} 筆已有 completion audit", + "recheck": "{count} 筆已回測;距離門檻仍差 {remaining} 筆" + } + }, "metric": { "staleRatio": "陳舊比例", "staleTotal": "陳舊 KM", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 2a3de4c1..6713fc1e 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -1482,6 +1482,33 @@ "owner": "Owner:在 AwoooP Work Items 預覽後確認寫回。", "noWritesOnRead": "讀取不寫入", "unexpectedWrites": "偵測到 read endpoint 宣告會寫入", + "flow": { + "title": "治理流程圖", + "scope": "從偵測到寫回與比例回測的目前位置", + "node": { + "detected": "偵測", + "ownerReview": "Owner Review", + "dryRun": "乾跑預覽", + "ownerConfirm": "Owner 確認", + "writeback": "寫回 KM", + "recheck": "比例回測" + }, + "state": { + "warning": "需處理", + "ready": "可操作", + "waiting": "等待", + "done": "已有證據", + "blocked": "卡住" + }, + "detail": { + "detected": "目前 {ratio};門檻 {threshold}", + "ownerReview": "{count} 筆等待 owner 審核", + "dryRun": "{ready} 筆可乾跑;{blocked} 筆卡住", + "ownerConfirm": "確認後才允許寫回,避免 AI 固化錯誤知識", + "writeback": "{count} 筆已有 completion audit", + "recheck": "{count} 筆已回測;距離門檻仍差 {remaining} 筆" + } + }, "metric": { "staleRatio": "陳舊比例", "staleTotal": "陳舊 KM", diff --git a/apps/web/src/app/[locale]/knowledge-base/page.tsx b/apps/web/src/app/[locale]/knowledge-base/page.tsx index d0c69e1e..c5359d3f 100644 --- a/apps/web/src/app/[locale]/knowledge-base/page.tsx +++ b/apps/web/src/app/[locale]/knowledge-base/page.tsx @@ -20,6 +20,7 @@ import { Search, BookOpen, FileText, Shield, Cpu, Server, Eye, Bot, ChevronRight, Plus, Sparkles, ClipboardList, Tag, TriangleAlert, GitBranch, CheckCircle2, Clock3, Link2, FileSearch, ListChecks, ArrowRight, Users, + PlayCircle, Database, RotateCw, } from 'lucide-react' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' @@ -92,6 +93,8 @@ interface KnowledgeStaleOwnerReviewBurnDownResponse { entries_to_threshold: number pending_owner_reviews: number completed_owner_reviews: number + completion_audit_total?: number + stale_ratio_recheck_total?: number current_snapshot?: { stale_count: number total_count: number @@ -594,6 +597,81 @@ export default function KnowledgeBasePage({ } }, [governanceTelemetry]) + const governanceFlowNodes = useMemo(() => { + const staleRatioText = governanceSummary.ratio === null ? '--' : `${governanceSummary.ratio}%` + const thresholdText = governanceSummary.threshold === null ? '--' : `${governanceSummary.threshold}%` + const recheckTotal = governanceTelemetry.burnDown?.stale_ratio_recheck_total ?? 0 + const auditTotal = governanceTelemetry.burnDown?.completion_audit_total ?? governanceSummary.completed + const nodeTone = (state: 'warning' | 'ready' | 'waiting' | 'done' | 'blocked') => { + switch (state) { + case 'warning': + return 'border-status-warning/25 bg-status-warning/10 text-status-warning' + case 'ready': + return 'border-claw-blue/25 bg-claw-blue/8 text-claw-blue' + case 'done': + return 'border-status-healthy/25 bg-status-healthy/10 text-status-healthy' + case 'blocked': + return 'border-status-critical/25 bg-status-critical/10 text-status-critical' + default: + return 'border-nothing-gray-200 bg-white text-secondary' + } + } + + return [ + { + key: 'detected', + icon: TriangleAlert, + value: governanceSummary.staleTotal === null ? '--' : formatCount(governanceSummary.staleTotal), + state: governanceSummary.burnDownStatus === 'above_threshold' ? 'warning' : 'done', + detail: t('workItems.flow.detail.detected', { ratio: staleRatioText, threshold: thresholdText }), + }, + { + key: 'ownerReview', + icon: Users, + value: formatCount(governanceSummary.ownerPending), + state: governanceSummary.ownerPending > 0 ? 'warning' : 'done', + detail: t('workItems.flow.detail.ownerReview', { count: formatCount(governanceSummary.ownerPending) }), + }, + { + key: 'dryRun', + icon: PlayCircle, + value: formatCount(governanceSummary.ownerReady), + state: governanceSummary.ownerReady > 0 ? 'ready' : 'waiting', + detail: t('workItems.flow.detail.dryRun', { + ready: formatCount(governanceSummary.ownerReady), + blocked: formatCount(governanceSummary.ownerBlocked), + }), + }, + { + key: 'ownerConfirm', + icon: Shield, + value: formatCount(governanceSummary.ownerReady), + state: governanceSummary.ownerBlocked > 0 ? 'blocked' : governanceSummary.ownerReady > 0 ? 'ready' : 'waiting', + detail: t('workItems.flow.detail.ownerConfirm'), + }, + { + key: 'writeback', + icon: Database, + value: formatCount(auditTotal), + state: auditTotal > 0 ? 'done' : 'waiting', + detail: t('workItems.flow.detail.writeback', { count: formatCount(auditTotal) }), + }, + { + key: 'recheck', + icon: RotateCw, + value: formatCount(recheckTotal), + state: recheckTotal > 0 ? 'done' : 'waiting', + detail: t('workItems.flow.detail.recheck', { + count: formatCount(recheckTotal), + remaining: governanceSummary.entriesToThreshold === null ? '--' : formatCount(governanceSummary.entriesToThreshold), + }), + }, + ].map(node => ({ + ...node, + tone: nodeTone(node.state as 'warning' | 'ready' | 'waiting' | 'done' | 'blocked'), + })) + }, [formatCount, governanceSummary, governanceTelemetry.burnDown, t]) + const content = (
@@ -898,6 +976,47 @@ export default function KnowledgeBasePage({ ))}
+
+
+
+

{t('workItems.flow.title')}

+

{t('workItems.flow.scope')}

+
+ + {governanceSummary.writesOnRead ? t('workItems.unexpectedWrites') : t('workItems.noWritesOnRead')} + +
+
+ {governanceFlowNodes.map((node, index) => { + const Icon = node.icon + return ( +
+ {index > 0 && ( +
+ ) + })} +
+
+