From d581f455f76ef4a1fe03d1d015f4d32fb73e47c6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 18 Jun 2026 17:27:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E5=89=8D=E7=A7=BB=20Knowledge=20B?= =?UTF-8?q?ase=20=E8=87=AA=E5=8B=95=E5=8C=96=E6=8E=8C=E6=8E=A7=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/messages/en.json | 18 ++++ apps/web/messages/zh-TW.json | 18 ++++ .../src/app/[locale]/knowledge-base/page.tsx | 101 ++++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index b904b188..c3a8b6cd 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -1810,6 +1810,24 @@ "errorDescription": "主知識條目 API 未成功回應:{reason}。下方治理軌道仍會顯示 Hermes owner-review 與陳舊 KM 狀態,避免誤判成知識庫真的歸零。", "retry": "重新讀取" }, + "decision": { + "badge": "KM 治理警戒", + "readback": "Hermes / AwoooP 只讀回讀", + "title": "KM 自動化掌控台", + "subtitle": "先看 stale ratio、owner review、ready / blocked 與寫回數,再進入條目列表;這裡把 KM、PlayBook、腳本、排程與 Verifier 的沉澱狀態前移到首屏。", + "openWorkItems": "處理 Owner Review", + "card": { + "staleRatio": "Stale Ratio", + "staleRatioSub": "門檻 {threshold}", + "staleTotal": "Stale KM", + "staleTotalSub": "距離門檻需處理 {count}", + "ownerQueue": "Owner Review", + "ownerQueueSub": "ready {ready} / blocked {blocked}", + "writeback": "已寫回", + "writebackSafe": "只讀回讀;讀取不寫入", + "writebackUnsafe": "偵測到讀取寫入風險" + } + }, "overview": { "metricTotal": "總條目", "metricLoaded": "目前列表", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index b904b188..c3a8b6cd 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -1810,6 +1810,24 @@ "errorDescription": "主知識條目 API 未成功回應:{reason}。下方治理軌道仍會顯示 Hermes owner-review 與陳舊 KM 狀態,避免誤判成知識庫真的歸零。", "retry": "重新讀取" }, + "decision": { + "badge": "KM 治理警戒", + "readback": "Hermes / AwoooP 只讀回讀", + "title": "KM 自動化掌控台", + "subtitle": "先看 stale ratio、owner review、ready / blocked 與寫回數,再進入條目列表;這裡把 KM、PlayBook、腳本、排程與 Verifier 的沉澱狀態前移到首屏。", + "openWorkItems": "處理 Owner Review", + "card": { + "staleRatio": "Stale Ratio", + "staleRatioSub": "門檻 {threshold}", + "staleTotal": "Stale KM", + "staleTotalSub": "距離門檻需處理 {count}", + "ownerQueue": "Owner Review", + "ownerQueueSub": "ready {ready} / blocked {blocked}", + "writeback": "已寫回", + "writebackSafe": "只讀回讀;讀取不寫入", + "writebackUnsafe": "偵測到讀取寫入風險" + } + }, "overview": { "metricTotal": "總條目", "metricLoaded": "目前列表", diff --git a/apps/web/src/app/[locale]/knowledge-base/page.tsx b/apps/web/src/app/[locale]/knowledge-base/page.tsx index 1af4320d..c6e34b45 100644 --- a/apps/web/src/app/[locale]/knowledge-base/page.tsx +++ b/apps/web/src/app/[locale]/knowledge-base/page.tsx @@ -800,6 +800,57 @@ export default function KnowledgeBasePage({ })) }, [formatCount, governanceSummary, governanceTelemetry.burnDown, t]) + const governanceDecisionCards = useMemo(() => { + const ratioAboveThreshold = + governanceSummary.ratio !== null + && governanceSummary.threshold !== null + && governanceSummary.ratio > governanceSummary.threshold + const staleRatioValue = governanceSummary.ratio === null ? '--' : `${governanceSummary.ratio}%` + const thresholdText = governanceSummary.threshold === null ? '--' : `${governanceSummary.threshold}%` + const staleTotalValue = governanceSummary.staleTotal === null ? '--' : formatCount(governanceSummary.staleTotal) + const entriesToThreshold = governanceSummary.entriesToThreshold === null ? '--' : formatCount(governanceSummary.entriesToThreshold) + + return [ + { + key: 'staleRatio', + icon: Clock3, + value: staleRatioValue, + sub: t('decision.card.staleRatioSub', { threshold: thresholdText }), + tone: ratioAboveThreshold + ? 'border-status-critical/25 bg-status-critical/10 text-status-critical' + : 'border-status-healthy/25 bg-status-healthy/10 text-status-healthy', + }, + { + key: 'staleTotal', + icon: BookOpen, + value: staleTotalValue, + sub: t('decision.card.staleTotalSub', { count: entriesToThreshold }), + tone: 'border-status-warning/25 bg-status-warning/10 text-status-warning', + }, + { + key: 'ownerQueue', + icon: Users, + value: formatCount(governanceSummary.ownerPending), + sub: t('decision.card.ownerQueueSub', { + ready: formatCount(governanceSummary.ownerReady), + blocked: formatCount(governanceSummary.ownerBlocked), + }), + tone: 'border-claw-blue/25 bg-claw-blue/8 text-claw-blue', + }, + { + key: 'writeback', + icon: Database, + value: formatCount(governanceSummary.completed), + sub: governanceSummary.writesOnRead + ? t('decision.card.writebackUnsafe') + : t('decision.card.writebackSafe'), + tone: governanceSummary.writesOnRead + ? 'border-status-critical/25 bg-status-critical/10 text-status-critical' + : 'border-status-healthy/25 bg-status-healthy/10 text-status-healthy', + }, + ] as const + }, [formatCount, governanceSummary, t]) + const content = (
@@ -942,6 +993,56 @@ export default function KnowledgeBasePage({ )}
+
+
+
+
+ + + + {governanceLoading ? t('workItems.loading') : t('decision.readback')} + +
+

+ {t('decision.title')} +

+

+ {t('decision.subtitle')} +

+
+ + {t('decision.openWorkItems')} +
+
+ {governanceDecisionCards.map(card => { + const Icon = card.icon + return ( +
+
+
+
+ + {t(`decision.card.${card.key}` as never)} + +
+

+ {governanceLoading ? '--' : card.value} +

+

{card.sub}

+
+ ) + })} +
+
+
{[