Compare commits
1 Commits
codex/host
...
codex/know
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5dc0a2ef53 |
@@ -2159,23 +2159,40 @@
|
|||||||
"title": "知識殿堂",
|
"title": "知識殿堂",
|
||||||
"searchPlaceholder": "搜尋知識條目...",
|
"searchPlaceholder": "搜尋知識條目...",
|
||||||
"allCategories": "全部",
|
"allCategories": "全部",
|
||||||
|
"rail": {
|
||||||
|
"categoryTitle": "動態分類",
|
||||||
|
"total": "總 KM",
|
||||||
|
"aiExtracted": "AI 萃取",
|
||||||
|
"controlledQueue": "受控隊列",
|
||||||
|
"ragChunks": "RAG chunks"
|
||||||
|
},
|
||||||
|
"assetLens": {
|
||||||
|
"title": "資產維度",
|
||||||
|
"scope": "目前列表",
|
||||||
|
"project": "專案",
|
||||||
|
"product": "產品",
|
||||||
|
"website": "網站",
|
||||||
|
"service": "服務",
|
||||||
|
"package": "套件",
|
||||||
|
"tool": "工具"
|
||||||
|
},
|
||||||
"dataChain": {
|
"dataChain": {
|
||||||
"errorTitle": "知識條目資料鏈路異常",
|
"errorTitle": "知識條目資料鏈路異常",
|
||||||
"errorDescription": "主知識條目 API 未成功回應:{reason}。下方治理軌道仍會顯示 Hermes owner-review 與陳舊 KM 狀態,避免誤判成知識庫真的歸零。",
|
"errorDescription": "主知識條目 API 未成功回應:{reason}。下方治理軌道仍會顯示 Hermes 受控覆核與陳舊 KM 狀態,避免誤判成知識庫真的歸零。",
|
||||||
"retry": "重新讀取"
|
"retry": "重新讀取"
|
||||||
},
|
},
|
||||||
"decision": {
|
"decision": {
|
||||||
"badge": "KM 治理警戒",
|
"badge": "KM 治理警戒",
|
||||||
"readback": "Hermes / AwoooP 只讀回讀",
|
"readback": "Hermes / AwoooP 受控回讀",
|
||||||
"title": "KM 自動化掌控台",
|
"title": "KM 自動化掌控台",
|
||||||
"subtitle": "先看 stale ratio、負責人審查、ready / blocked 與寫回數,再進入條目列表;這裡把 KM、PlayBook、腳本、排程與 Verifier 的沉澱狀態前移到首屏。",
|
"subtitle": "先看 stale ratio、受控覆核、ready / blocked 與寫回數,再進入條目列表;這裡把 KM、PlayBook、腳本、排程與 Verifier 的沉澱狀態前移到首屏。",
|
||||||
"openWorkItems": "處理 Owner Review",
|
"openWorkItems": "處理受控隊列",
|
||||||
"card": {
|
"card": {
|
||||||
"staleRatio": "Stale Ratio",
|
"staleRatio": "Stale Ratio",
|
||||||
"staleRatioSub": "門檻 {threshold}",
|
"staleRatioSub": "門檻 {threshold}",
|
||||||
"staleTotal": "Stale KM",
|
"staleTotal": "Stale KM",
|
||||||
"staleTotalSub": "距離門檻需處理 {count}",
|
"staleTotalSub": "距離門檻需處理 {count}",
|
||||||
"ownerQueue": "Owner Review",
|
"ownerQueue": "受控覆核",
|
||||||
"ownerQueueSub": "ready {ready} / blocked {blocked}",
|
"ownerQueueSub": "ready {ready} / blocked {blocked}",
|
||||||
"writeback": "已寫回",
|
"writeback": "已寫回",
|
||||||
"writebackSafe": "只讀回讀;讀取不寫入",
|
"writebackSafe": "只讀回讀;讀取不寫入",
|
||||||
@@ -2184,6 +2201,11 @@
|
|||||||
"ragIndexSub": "sources {sources};0 代表語意檢索尚未沉澱"
|
"ragIndexSub": "sources {sources};0 代表語意檢索尚未沉澱"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"workspace": {
|
||||||
|
"records": "Records",
|
||||||
|
"automation": "Automation",
|
||||||
|
"queue": "Queue"
|
||||||
|
},
|
||||||
"overview": {
|
"overview": {
|
||||||
"metricTotal": "總條目",
|
"metricTotal": "總條目",
|
||||||
"metricLoaded": "目前列表",
|
"metricLoaded": "目前列表",
|
||||||
@@ -2196,7 +2218,7 @@
|
|||||||
},
|
},
|
||||||
"assetLedger": {
|
"assetLedger": {
|
||||||
"title": "自動化資產沉澱總帳",
|
"title": "自動化資產沉澱總帳",
|
||||||
"subtitle": "把目前 KM 清單與 Hermes owner-review 讀模型,對齊到 PlayBook、腳本、排程、Verifier 五類資產。",
|
"subtitle": "把目前 KM 清單與 Hermes 受控覆核讀模型,對齊到 PlayBook、腳本、排程、Verifier 五類資產。",
|
||||||
"readOnly": "只讀總覽",
|
"readOnly": "只讀總覽",
|
||||||
"boundary": "此區塊只顯示可追蹤沉澱狀態,不代表已授權修復、已寫回 KM 或已提高 PlayBook trust。",
|
"boundary": "此區塊只顯示可追蹤沉澱狀態,不代表已授權修復、已寫回 KM 或已提高 PlayBook trust。",
|
||||||
"asset": {
|
"asset": {
|
||||||
@@ -2214,11 +2236,11 @@
|
|||||||
},
|
},
|
||||||
"monitoring": {
|
"monitoring": {
|
||||||
"title": "排程 / 監控規則",
|
"title": "排程 / 監控規則",
|
||||||
"detail": "含告警 / 監控訊號 {ready} 筆;負責人審查 待處理 {pending} 筆。"
|
"detail": "含告警 / 監控訊號 {ready} 筆;受控覆核待處理 {pending} 筆。"
|
||||||
},
|
},
|
||||||
"verifier": {
|
"verifier": {
|
||||||
"title": "Verifier 回寫",
|
"title": "Verifier 回寫",
|
||||||
"detail": "已批准或完成驗證 {ready} 筆;待 負責人審查 {pending} 筆。"
|
"detail": "已批准或完成驗證 {ready} 筆;待受控覆核 {pending} 筆。"
|
||||||
},
|
},
|
||||||
"rag": {
|
"rag": {
|
||||||
"title": "RAG / 向量索引",
|
"title": "RAG / 向量索引",
|
||||||
@@ -2244,7 +2266,7 @@
|
|||||||
"incident": "Incident",
|
"incident": "Incident",
|
||||||
"playbook": "Playbook",
|
"playbook": "Playbook",
|
||||||
"review": "審核",
|
"review": "審核",
|
||||||
"sourceDetail": "AI {ai} / 人工 {human}",
|
"sourceDetail": "AI {ai} / 受控建立 {human}",
|
||||||
"knowledgeDetail": "已載入 {count}",
|
"knowledgeDetail": "已載入 {count}",
|
||||||
"countDetail": "{count} / {total}"
|
"countDetail": "{count} / {total}"
|
||||||
},
|
},
|
||||||
@@ -2257,8 +2279,8 @@
|
|||||||
"missingIncidentAction": "補齊來源事故或標記為通用知識",
|
"missingIncidentAction": "補齊來源事故或標記為通用知識",
|
||||||
"missingPlaybook": "缺 Playbook",
|
"missingPlaybook": "缺 Playbook",
|
||||||
"missingPlaybookAction": "匹配既有 Playbook或建立草稿",
|
"missingPlaybookAction": "匹配既有 Playbook或建立草稿",
|
||||||
"reviewBacklog": "待 Owner 審核",
|
"reviewBacklog": "待受控覆核",
|
||||||
"reviewBacklogAction": "高影響條目先人工覆核再批准"
|
"reviewBacklogAction": "高影響條目先進 controlled review,再由 AI 補齊 verifier 與寫回證據"
|
||||||
},
|
},
|
||||||
"entryLineage": {
|
"entryLineage": {
|
||||||
"title": "單筆證據鏈",
|
"title": "單筆證據鏈",
|
||||||
@@ -2270,12 +2292,12 @@
|
|||||||
},
|
},
|
||||||
"workItems": {
|
"workItems": {
|
||||||
"title": "Work Items 接續狀態",
|
"title": "Work Items 接續狀態",
|
||||||
"subtitle": "這裡只讀取 Hermes KB owner-review queue,讓你知道陳舊 KM 是否已進入可追蹤工作項;實際預覽、確認與寫回仍在 AwoooP Work Items 執行。",
|
"subtitle": "這裡只讀取 Hermes KB controlled review queue,讓你知道陳舊 KM 是否已進入可追蹤工作項;實際預覽、確認與寫回仍在 AwoooP Work Items 執行。",
|
||||||
"open": "打開 Work Items",
|
"open": "打開 Work Items",
|
||||||
"loading": "讀取中",
|
"loading": "讀取中",
|
||||||
"threshold": "門檻 {value}",
|
"threshold": "門檻 {value}",
|
||||||
"entriesToThreshold": "需處理 {count} 筆才回到門檻",
|
"entriesToThreshold": "需處理 {count} 筆才回到門檻",
|
||||||
"ownerPendingSub": "待 Owner review",
|
"ownerPendingSub": "待受控覆核",
|
||||||
"readyBlockedSub": "ready / blocked",
|
"readyBlockedSub": "ready / blocked",
|
||||||
"burndownStatus": "狀態:{status}",
|
"burndownStatus": "狀態:{status}",
|
||||||
"topCandidate": "最高優先處理項",
|
"topCandidate": "最高優先處理項",
|
||||||
@@ -2285,9 +2307,9 @@
|
|||||||
"topCandidateRefs": "Incident {incident} / Playbook {playbook}",
|
"topCandidateRefs": "Incident {incident} / Playbook {playbook}",
|
||||||
"noCandidate": "目前沒有可顯示的 stale KM candidate。",
|
"noCandidate": "目前沒有可顯示的 stale KM candidate。",
|
||||||
"ownerSplit": "主責分工",
|
"ownerSplit": "主責分工",
|
||||||
"hermes": "Hermes:建立 KM 更新草稿與owner-review queue。",
|
"hermes": "Hermes:建立 KM 更新草稿與 controlled review queue。",
|
||||||
"openclaw": "OpenClaw:補告警分類、Playbook與上下文摘要。",
|
"openclaw": "OpenClaw:補告警分類、Playbook與上下文摘要。",
|
||||||
"owner": "Owner:在 AwoooP Work Items 預覽後確認寫回。",
|
"owner": "Controlled lane:在 AwoooP Work Items 預覽後確認寫回邊界。",
|
||||||
"noWritesOnRead": "讀取不寫入",
|
"noWritesOnRead": "讀取不寫入",
|
||||||
"unexpectedWrites": "偵測到 read endpoint 宣告會寫入",
|
"unexpectedWrites": "偵測到 read endpoint 宣告會寫入",
|
||||||
"flow": {
|
"flow": {
|
||||||
@@ -2295,9 +2317,9 @@
|
|||||||
"scope": "從偵測到寫回與比例回測的目前位置",
|
"scope": "從偵測到寫回與比例回測的目前位置",
|
||||||
"node": {
|
"node": {
|
||||||
"detected": "偵測",
|
"detected": "偵測",
|
||||||
"ownerReview": "Owner Review",
|
"ownerReview": "Controlled Review",
|
||||||
"dryRun": "乾跑預覽",
|
"dryRun": "乾跑預覽",
|
||||||
"ownerConfirm": "Owner 確認",
|
"ownerConfirm": "邊界確認",
|
||||||
"writeback": "寫回 KM",
|
"writeback": "寫回 KM",
|
||||||
"recheck": "比例回測"
|
"recheck": "比例回測"
|
||||||
},
|
},
|
||||||
@@ -2310,7 +2332,7 @@
|
|||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
"detected": "目前 {ratio};門檻 {threshold}",
|
"detected": "目前 {ratio};門檻 {threshold}",
|
||||||
"ownerReview": "{count} 筆等待 owner 審核",
|
"ownerReview": "{count} 筆等待受控覆核",
|
||||||
"dryRun": "{ready} 筆可乾跑;{blocked} 筆卡住",
|
"dryRun": "{ready} 筆可乾跑;{blocked} 筆卡住",
|
||||||
"ownerConfirm": "確認後才允許寫回,避免 AI 固化錯誤知識",
|
"ownerConfirm": "確認後才允許寫回,避免 AI 固化錯誤知識",
|
||||||
"writeback": "{count} 筆已有 completion audit",
|
"writeback": "{count} 筆已有 completion audit",
|
||||||
@@ -2356,7 +2378,7 @@
|
|||||||
},
|
},
|
||||||
"source": {
|
"source": {
|
||||||
"ai_extracted": "AI 萃取",
|
"ai_extracted": "AI 萃取",
|
||||||
"human": "人工建立"
|
"human": "受控建立"
|
||||||
},
|
},
|
||||||
"signals": {
|
"signals": {
|
||||||
"label": "訊號",
|
"label": "訊號",
|
||||||
@@ -2371,14 +2393,15 @@
|
|||||||
"execution": "執行紀錄",
|
"execution": "執行紀錄",
|
||||||
"execution_failed": "執行失敗",
|
"execution_failed": "執行失敗",
|
||||||
"failure": "失敗",
|
"failure": "失敗",
|
||||||
"human_approved": "人工批准",
|
"human_approved": "受控批准",
|
||||||
"human_intervention": "人工介入",
|
"human_intervention": "受控介入",
|
||||||
"incident": "事故",
|
"incident": "事故",
|
||||||
"postmortem": "事後檢討",
|
"postmortem": "事後檢討",
|
||||||
"telegram": "Telegram",
|
"telegram": "Telegram",
|
||||||
"warning": "警告"
|
"warning": "警告"
|
||||||
},
|
},
|
||||||
"category": {
|
"category": {
|
||||||
|
"AI自動化/Ansible受控修復": "AI 自動化 / Ansible 受控修復",
|
||||||
"AI治理": "AI 治理",
|
"AI治理": "AI 治理",
|
||||||
"alert_handling": "告警處理",
|
"alert_handling": "告警處理",
|
||||||
"infrastructure": "基礎設施",
|
"infrastructure": "基礎設施",
|
||||||
@@ -2396,13 +2419,20 @@
|
|||||||
"flywheel_health": "飛輪健康",
|
"flywheel_health": "飛輪健康",
|
||||||
"ssl_cert": "SSL 憑證",
|
"ssl_cert": "SSL 憑證",
|
||||||
"devops_tool": "DevOps 工具",
|
"devops_tool": "DevOps 工具",
|
||||||
"incident_postmortem": "事故檢討"
|
"incident_postmortem": "事故檢討",
|
||||||
|
"project": "專案",
|
||||||
|
"product": "產品",
|
||||||
|
"website": "網站",
|
||||||
|
"service": "服務",
|
||||||
|
"package": "套件",
|
||||||
|
"tool": "工具",
|
||||||
|
"source_control": "Source Control"
|
||||||
},
|
},
|
||||||
"filterByType": "篩選類型",
|
"filterByType": "篩選類型",
|
||||||
"filterByStatus": "篩選狀態",
|
"filterByStatus": "篩選狀態",
|
||||||
"entries": "筆",
|
"entries": "筆",
|
||||||
"empty": "尚未建立任何知識條目",
|
"empty": "尚未建立任何知識條目",
|
||||||
"emptyDescription": "知識庫將自動從 Incident 中萃取案例,你也可以手動新增",
|
"emptyDescription": "知識庫將自動從 Incident、LOG、PlayBook 與 verifier 中萃取案例",
|
||||||
"semanticSearchPlaceholder": "輸入語意搜尋查詢...",
|
"semanticSearchPlaceholder": "輸入語意搜尋查詢...",
|
||||||
"semanticOn": "語意",
|
"semanticOn": "語意",
|
||||||
"semanticOff": "語意",
|
"semanticOff": "語意",
|
||||||
|
|||||||
@@ -2159,23 +2159,40 @@
|
|||||||
"title": "知識殿堂",
|
"title": "知識殿堂",
|
||||||
"searchPlaceholder": "搜尋知識條目...",
|
"searchPlaceholder": "搜尋知識條目...",
|
||||||
"allCategories": "全部",
|
"allCategories": "全部",
|
||||||
|
"rail": {
|
||||||
|
"categoryTitle": "動態分類",
|
||||||
|
"total": "總 KM",
|
||||||
|
"aiExtracted": "AI 萃取",
|
||||||
|
"controlledQueue": "受控隊列",
|
||||||
|
"ragChunks": "RAG chunks"
|
||||||
|
},
|
||||||
|
"assetLens": {
|
||||||
|
"title": "資產維度",
|
||||||
|
"scope": "目前列表",
|
||||||
|
"project": "專案",
|
||||||
|
"product": "產品",
|
||||||
|
"website": "網站",
|
||||||
|
"service": "服務",
|
||||||
|
"package": "套件",
|
||||||
|
"tool": "工具"
|
||||||
|
},
|
||||||
"dataChain": {
|
"dataChain": {
|
||||||
"errorTitle": "知識條目資料鏈路異常",
|
"errorTitle": "知識條目資料鏈路異常",
|
||||||
"errorDescription": "主知識條目 API 未成功回應:{reason}。下方治理軌道仍會顯示 Hermes owner-review 與陳舊 KM 狀態,避免誤判成知識庫真的歸零。",
|
"errorDescription": "主知識條目 API 未成功回應:{reason}。下方治理軌道仍會顯示 Hermes 受控覆核與陳舊 KM 狀態,避免誤判成知識庫真的歸零。",
|
||||||
"retry": "重新讀取"
|
"retry": "重新讀取"
|
||||||
},
|
},
|
||||||
"decision": {
|
"decision": {
|
||||||
"badge": "KM 治理警戒",
|
"badge": "KM 治理警戒",
|
||||||
"readback": "Hermes / AwoooP 只讀回讀",
|
"readback": "Hermes / AwoooP 受控回讀",
|
||||||
"title": "KM 自動化掌控台",
|
"title": "KM 自動化掌控台",
|
||||||
"subtitle": "先看 stale ratio、負責人審查、ready / blocked 與寫回數,再進入條目列表;這裡把 KM、PlayBook、腳本、排程與 Verifier 的沉澱狀態前移到首屏。",
|
"subtitle": "先看 stale ratio、受控覆核、ready / blocked 與寫回數,再進入條目列表;這裡把 KM、PlayBook、腳本、排程與 Verifier 的沉澱狀態前移到首屏。",
|
||||||
"openWorkItems": "處理 Owner Review",
|
"openWorkItems": "處理受控隊列",
|
||||||
"card": {
|
"card": {
|
||||||
"staleRatio": "Stale Ratio",
|
"staleRatio": "Stale Ratio",
|
||||||
"staleRatioSub": "門檻 {threshold}",
|
"staleRatioSub": "門檻 {threshold}",
|
||||||
"staleTotal": "Stale KM",
|
"staleTotal": "Stale KM",
|
||||||
"staleTotalSub": "距離門檻需處理 {count}",
|
"staleTotalSub": "距離門檻需處理 {count}",
|
||||||
"ownerQueue": "Owner Review",
|
"ownerQueue": "受控覆核",
|
||||||
"ownerQueueSub": "ready {ready} / blocked {blocked}",
|
"ownerQueueSub": "ready {ready} / blocked {blocked}",
|
||||||
"writeback": "已寫回",
|
"writeback": "已寫回",
|
||||||
"writebackSafe": "只讀回讀;讀取不寫入",
|
"writebackSafe": "只讀回讀;讀取不寫入",
|
||||||
@@ -2184,6 +2201,11 @@
|
|||||||
"ragIndexSub": "sources {sources};0 代表語意檢索尚未沉澱"
|
"ragIndexSub": "sources {sources};0 代表語意檢索尚未沉澱"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"workspace": {
|
||||||
|
"records": "Records",
|
||||||
|
"automation": "Automation",
|
||||||
|
"queue": "Queue"
|
||||||
|
},
|
||||||
"overview": {
|
"overview": {
|
||||||
"metricTotal": "總條目",
|
"metricTotal": "總條目",
|
||||||
"metricLoaded": "目前列表",
|
"metricLoaded": "目前列表",
|
||||||
@@ -2196,7 +2218,7 @@
|
|||||||
},
|
},
|
||||||
"assetLedger": {
|
"assetLedger": {
|
||||||
"title": "自動化資產沉澱總帳",
|
"title": "自動化資產沉澱總帳",
|
||||||
"subtitle": "把目前 KM 清單與 Hermes owner-review 讀模型,對齊到 PlayBook、腳本、排程、Verifier 五類資產。",
|
"subtitle": "把目前 KM 清單與 Hermes 受控覆核讀模型,對齊到 PlayBook、腳本、排程、Verifier 五類資產。",
|
||||||
"readOnly": "只讀總覽",
|
"readOnly": "只讀總覽",
|
||||||
"boundary": "此區塊只顯示可追蹤沉澱狀態,不代表已授權修復、已寫回 KM 或已提高 PlayBook trust。",
|
"boundary": "此區塊只顯示可追蹤沉澱狀態,不代表已授權修復、已寫回 KM 或已提高 PlayBook trust。",
|
||||||
"asset": {
|
"asset": {
|
||||||
@@ -2214,11 +2236,11 @@
|
|||||||
},
|
},
|
||||||
"monitoring": {
|
"monitoring": {
|
||||||
"title": "排程 / 監控規則",
|
"title": "排程 / 監控規則",
|
||||||
"detail": "含告警 / 監控訊號 {ready} 筆;負責人審查 待處理 {pending} 筆。"
|
"detail": "含告警 / 監控訊號 {ready} 筆;受控覆核待處理 {pending} 筆。"
|
||||||
},
|
},
|
||||||
"verifier": {
|
"verifier": {
|
||||||
"title": "Verifier 回寫",
|
"title": "Verifier 回寫",
|
||||||
"detail": "已批准或完成驗證 {ready} 筆;待 負責人審查 {pending} 筆。"
|
"detail": "已批准或完成驗證 {ready} 筆;待受控覆核 {pending} 筆。"
|
||||||
},
|
},
|
||||||
"rag": {
|
"rag": {
|
||||||
"title": "RAG / 向量索引",
|
"title": "RAG / 向量索引",
|
||||||
@@ -2244,7 +2266,7 @@
|
|||||||
"incident": "Incident",
|
"incident": "Incident",
|
||||||
"playbook": "Playbook",
|
"playbook": "Playbook",
|
||||||
"review": "審核",
|
"review": "審核",
|
||||||
"sourceDetail": "AI {ai} / 人工 {human}",
|
"sourceDetail": "AI {ai} / 受控建立 {human}",
|
||||||
"knowledgeDetail": "已載入 {count}",
|
"knowledgeDetail": "已載入 {count}",
|
||||||
"countDetail": "{count} / {total}"
|
"countDetail": "{count} / {total}"
|
||||||
},
|
},
|
||||||
@@ -2257,8 +2279,8 @@
|
|||||||
"missingIncidentAction": "補齊來源事故或標記為通用知識",
|
"missingIncidentAction": "補齊來源事故或標記為通用知識",
|
||||||
"missingPlaybook": "缺 Playbook",
|
"missingPlaybook": "缺 Playbook",
|
||||||
"missingPlaybookAction": "匹配既有 Playbook或建立草稿",
|
"missingPlaybookAction": "匹配既有 Playbook或建立草稿",
|
||||||
"reviewBacklog": "待 Owner 審核",
|
"reviewBacklog": "待受控覆核",
|
||||||
"reviewBacklogAction": "高影響條目先人工覆核再批准"
|
"reviewBacklogAction": "高影響條目先進 controlled review,再由 AI 補齊 verifier 與寫回證據"
|
||||||
},
|
},
|
||||||
"entryLineage": {
|
"entryLineage": {
|
||||||
"title": "單筆證據鏈",
|
"title": "單筆證據鏈",
|
||||||
@@ -2270,12 +2292,12 @@
|
|||||||
},
|
},
|
||||||
"workItems": {
|
"workItems": {
|
||||||
"title": "Work Items 接續狀態",
|
"title": "Work Items 接續狀態",
|
||||||
"subtitle": "這裡只讀取 Hermes KB owner-review queue,讓你知道陳舊 KM 是否已進入可追蹤工作項;實際預覽、確認與寫回仍在 AwoooP Work Items 執行。",
|
"subtitle": "這裡只讀取 Hermes KB controlled review queue,讓你知道陳舊 KM 是否已進入可追蹤工作項;實際預覽、確認與寫回仍在 AwoooP Work Items 執行。",
|
||||||
"open": "打開 Work Items",
|
"open": "打開 Work Items",
|
||||||
"loading": "讀取中",
|
"loading": "讀取中",
|
||||||
"threshold": "門檻 {value}",
|
"threshold": "門檻 {value}",
|
||||||
"entriesToThreshold": "需處理 {count} 筆才回到門檻",
|
"entriesToThreshold": "需處理 {count} 筆才回到門檻",
|
||||||
"ownerPendingSub": "待 Owner review",
|
"ownerPendingSub": "待受控覆核",
|
||||||
"readyBlockedSub": "ready / blocked",
|
"readyBlockedSub": "ready / blocked",
|
||||||
"burndownStatus": "狀態:{status}",
|
"burndownStatus": "狀態:{status}",
|
||||||
"topCandidate": "最高優先處理項",
|
"topCandidate": "最高優先處理項",
|
||||||
@@ -2285,9 +2307,9 @@
|
|||||||
"topCandidateRefs": "Incident {incident} / Playbook {playbook}",
|
"topCandidateRefs": "Incident {incident} / Playbook {playbook}",
|
||||||
"noCandidate": "目前沒有可顯示的 stale KM candidate。",
|
"noCandidate": "目前沒有可顯示的 stale KM candidate。",
|
||||||
"ownerSplit": "主責分工",
|
"ownerSplit": "主責分工",
|
||||||
"hermes": "Hermes:建立 KM 更新草稿與owner-review queue。",
|
"hermes": "Hermes:建立 KM 更新草稿與 controlled review queue。",
|
||||||
"openclaw": "OpenClaw:補告警分類、Playbook與上下文摘要。",
|
"openclaw": "OpenClaw:補告警分類、Playbook與上下文摘要。",
|
||||||
"owner": "Owner:在 AwoooP Work Items 預覽後確認寫回。",
|
"owner": "Controlled lane:在 AwoooP Work Items 預覽後確認寫回邊界。",
|
||||||
"noWritesOnRead": "讀取不寫入",
|
"noWritesOnRead": "讀取不寫入",
|
||||||
"unexpectedWrites": "偵測到 read endpoint 宣告會寫入",
|
"unexpectedWrites": "偵測到 read endpoint 宣告會寫入",
|
||||||
"flow": {
|
"flow": {
|
||||||
@@ -2295,9 +2317,9 @@
|
|||||||
"scope": "從偵測到寫回與比例回測的目前位置",
|
"scope": "從偵測到寫回與比例回測的目前位置",
|
||||||
"node": {
|
"node": {
|
||||||
"detected": "偵測",
|
"detected": "偵測",
|
||||||
"ownerReview": "Owner Review",
|
"ownerReview": "Controlled Review",
|
||||||
"dryRun": "乾跑預覽",
|
"dryRun": "乾跑預覽",
|
||||||
"ownerConfirm": "Owner 確認",
|
"ownerConfirm": "邊界確認",
|
||||||
"writeback": "寫回 KM",
|
"writeback": "寫回 KM",
|
||||||
"recheck": "比例回測"
|
"recheck": "比例回測"
|
||||||
},
|
},
|
||||||
@@ -2310,7 +2332,7 @@
|
|||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
"detected": "目前 {ratio};門檻 {threshold}",
|
"detected": "目前 {ratio};門檻 {threshold}",
|
||||||
"ownerReview": "{count} 筆等待 owner 審核",
|
"ownerReview": "{count} 筆等待受控覆核",
|
||||||
"dryRun": "{ready} 筆可乾跑;{blocked} 筆卡住",
|
"dryRun": "{ready} 筆可乾跑;{blocked} 筆卡住",
|
||||||
"ownerConfirm": "確認後才允許寫回,避免 AI 固化錯誤知識",
|
"ownerConfirm": "確認後才允許寫回,避免 AI 固化錯誤知識",
|
||||||
"writeback": "{count} 筆已有 completion audit",
|
"writeback": "{count} 筆已有 completion audit",
|
||||||
@@ -2356,7 +2378,7 @@
|
|||||||
},
|
},
|
||||||
"source": {
|
"source": {
|
||||||
"ai_extracted": "AI 萃取",
|
"ai_extracted": "AI 萃取",
|
||||||
"human": "人工建立"
|
"human": "受控建立"
|
||||||
},
|
},
|
||||||
"signals": {
|
"signals": {
|
||||||
"label": "訊號",
|
"label": "訊號",
|
||||||
@@ -2371,14 +2393,15 @@
|
|||||||
"execution": "執行紀錄",
|
"execution": "執行紀錄",
|
||||||
"execution_failed": "執行失敗",
|
"execution_failed": "執行失敗",
|
||||||
"failure": "失敗",
|
"failure": "失敗",
|
||||||
"human_approved": "人工批准",
|
"human_approved": "受控批准",
|
||||||
"human_intervention": "人工介入",
|
"human_intervention": "受控介入",
|
||||||
"incident": "事故",
|
"incident": "事故",
|
||||||
"postmortem": "事後檢討",
|
"postmortem": "事後檢討",
|
||||||
"telegram": "Telegram",
|
"telegram": "Telegram",
|
||||||
"warning": "警告"
|
"warning": "警告"
|
||||||
},
|
},
|
||||||
"category": {
|
"category": {
|
||||||
|
"AI自動化/Ansible受控修復": "AI 自動化 / Ansible 受控修復",
|
||||||
"AI治理": "AI 治理",
|
"AI治理": "AI 治理",
|
||||||
"alert_handling": "告警處理",
|
"alert_handling": "告警處理",
|
||||||
"infrastructure": "基礎設施",
|
"infrastructure": "基礎設施",
|
||||||
@@ -2396,13 +2419,20 @@
|
|||||||
"flywheel_health": "飛輪健康",
|
"flywheel_health": "飛輪健康",
|
||||||
"ssl_cert": "SSL 憑證",
|
"ssl_cert": "SSL 憑證",
|
||||||
"devops_tool": "DevOps 工具",
|
"devops_tool": "DevOps 工具",
|
||||||
"incident_postmortem": "事故檢討"
|
"incident_postmortem": "事故檢討",
|
||||||
|
"project": "專案",
|
||||||
|
"product": "產品",
|
||||||
|
"website": "網站",
|
||||||
|
"service": "服務",
|
||||||
|
"package": "套件",
|
||||||
|
"tool": "工具",
|
||||||
|
"source_control": "Source Control"
|
||||||
},
|
},
|
||||||
"filterByType": "篩選類型",
|
"filterByType": "篩選類型",
|
||||||
"filterByStatus": "篩選狀態",
|
"filterByStatus": "篩選狀態",
|
||||||
"entries": "筆",
|
"entries": "筆",
|
||||||
"empty": "尚未建立任何知識條目",
|
"empty": "尚未建立任何知識條目",
|
||||||
"emptyDescription": "知識庫將自動從 Incident 中萃取案例,你也可以手動新增",
|
"emptyDescription": "知識庫將自動從 Incident、LOG、PlayBook 與 verifier 中萃取案例",
|
||||||
"semanticSearchPlaceholder": "輸入語意搜尋查詢...",
|
"semanticSearchPlaceholder": "輸入語意搜尋查詢...",
|
||||||
"semanticOn": "語意",
|
"semanticOn": "語意",
|
||||||
"semanticOff": "語意",
|
"semanticOff": "語意",
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit",
|
||||||
|
"audit:ui-density": "node scripts/audit-ui-density.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@awoooi/lewooogo-core": "workspace:*",
|
"@awoooi/lewooogo-core": "workspace:*",
|
||||||
|
|||||||
130
apps/web/scripts/audit-ui-density.mjs
Normal file
130
apps/web/scripts/audit-ui-density.mjs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
||||||
|
import { join, relative } from 'node:path'
|
||||||
|
|
||||||
|
const appRoot = join(process.cwd(), 'src', 'app')
|
||||||
|
|
||||||
|
const textLikePatterns = [
|
||||||
|
/<p\b/g,
|
||||||
|
/<span\b/g,
|
||||||
|
/<li\b/g,
|
||||||
|
/<h[1-6]\b/g,
|
||||||
|
/<ReactMarkdown\b/g,
|
||||||
|
/prose\b/g,
|
||||||
|
/leading-\d/g,
|
||||||
|
]
|
||||||
|
|
||||||
|
const controlPatterns = [
|
||||||
|
/<button\b/g,
|
||||||
|
/<select\b/g,
|
||||||
|
/<input\b/g,
|
||||||
|
/<Link\b/g,
|
||||||
|
/role=["']tab/g,
|
||||||
|
/type=["']checkbox/g,
|
||||||
|
]
|
||||||
|
|
||||||
|
const layoutPatterns = [
|
||||||
|
/grid-cols-/g,
|
||||||
|
/flex\b/g,
|
||||||
|
/overflow-y-auto/g,
|
||||||
|
/sticky\b/g,
|
||||||
|
]
|
||||||
|
|
||||||
|
const textWallPatterns = [
|
||||||
|
/max-w-(?:2xl|3xl|4xl|5xl|6xl|7xl)/g,
|
||||||
|
/line-clamp-[4-9]/g,
|
||||||
|
/prose\b/g,
|
||||||
|
/<ReactMarkdown\b/g,
|
||||||
|
]
|
||||||
|
|
||||||
|
const manualTerms = [
|
||||||
|
/\u4eba\u5de5/g,
|
||||||
|
/\u624b\u52d5/g,
|
||||||
|
/manual/gi,
|
||||||
|
/needs_human/gi,
|
||||||
|
/human_/gi,
|
||||||
|
]
|
||||||
|
|
||||||
|
const fixedTaxonomyPatterns = [
|
||||||
|
/const\s+CATEGORIES\s*=/g,
|
||||||
|
]
|
||||||
|
|
||||||
|
function collectPages(dir, acc = []) {
|
||||||
|
for (const name of readdirSync(dir)) {
|
||||||
|
const fullPath = join(dir, name)
|
||||||
|
const stats = statSync(fullPath)
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
collectPages(fullPath, acc)
|
||||||
|
} else if (name === 'page.tsx') {
|
||||||
|
acc.push(fullPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
function countMatches(source, patterns) {
|
||||||
|
return patterns.reduce((sum, pattern) => sum + (source.match(pattern) ?? []).length, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function routeFromPage(filePath) {
|
||||||
|
return relative(appRoot, filePath)
|
||||||
|
.replace(/\/page\.tsx$/, '')
|
||||||
|
.replace(/\[locale\]/, ':locale')
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = collectPages(appRoot)
|
||||||
|
.map(filePath => {
|
||||||
|
const source = readFileSync(filePath, 'utf8')
|
||||||
|
const lineCount = source.split('\n').length
|
||||||
|
const textSignals = countMatches(source, textLikePatterns)
|
||||||
|
const controls = countMatches(source, controlPatterns)
|
||||||
|
const layoutSignals = countMatches(source, layoutPatterns)
|
||||||
|
const textWallSignals = countMatches(source, textWallPatterns)
|
||||||
|
const manualSignals = countMatches(source, manualTerms)
|
||||||
|
const fixedTaxonomySignals = countMatches(source, fixedTaxonomyPatterns)
|
||||||
|
const nestedCardSignals = (source.match(/rounded-(?:md|lg|xl)[^`'"]*border/g) ?? []).length
|
||||||
|
const score =
|
||||||
|
Math.round(
|
||||||
|
(textSignals * 1.2)
|
||||||
|
+ (textWallSignals * 8)
|
||||||
|
+ (manualSignals * 4)
|
||||||
|
+ (fixedTaxonomySignals * 10)
|
||||||
|
+ (nestedCardSignals * 0.7)
|
||||||
|
- (controls * 1.5)
|
||||||
|
- (layoutSignals * 0.4),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
route: routeFromPage(filePath),
|
||||||
|
file: relative(process.cwd(), filePath),
|
||||||
|
lines: lineCount,
|
||||||
|
score,
|
||||||
|
textSignals,
|
||||||
|
controls,
|
||||||
|
layoutSignals,
|
||||||
|
textWallSignals,
|
||||||
|
manualSignals,
|
||||||
|
fixedTaxonomySignals,
|
||||||
|
nestedCardSignals,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sort((a, b) => b.score - a.score)
|
||||||
|
|
||||||
|
console.log('AWOOOI_UI_DENSITY_AUDIT')
|
||||||
|
console.table(
|
||||||
|
pages.slice(0, 20).map(page => ({
|
||||||
|
route: page.route,
|
||||||
|
score: page.score,
|
||||||
|
lines: page.lines,
|
||||||
|
text: page.textSignals,
|
||||||
|
controls: page.controls,
|
||||||
|
manual: page.manualSignals,
|
||||||
|
fixedTaxonomy: page.fixedTaxonomySignals,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
page_count: pages.length,
|
||||||
|
top_routes: pages.slice(0, 20),
|
||||||
|
}, null, 2))
|
||||||
@@ -1288,7 +1288,6 @@ const COMMANDER_INSERTED_REQUIREMENT_FALLBACK: PriorityWorkOrderResponse = {
|
|||||||
"windows99_uptime_unknown",
|
"windows99_uptime_unknown",
|
||||||
],
|
],
|
||||||
stockplatform_public_api_runtime_status: "stockplatform_public_api_runtime_ready",
|
stockplatform_public_api_runtime_status: "stockplatform_public_api_runtime_ready",
|
||||||
stockplatform_public_api_runtime_ready: true,
|
|
||||||
stockplatform_public_api_controlled_recovery_preflight_status:
|
stockplatform_public_api_controlled_recovery_preflight_status:
|
||||||
"not_required_stockplatform_runtime_ready",
|
"not_required_stockplatform_runtime_ready",
|
||||||
stockplatform_public_api_controlled_recovery_data_dependency: "none",
|
stockplatform_public_api_controlled_recovery_data_dependency: "none",
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ import { useTranslations } from 'next-intl'
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import {
|
import {
|
||||||
Search, BookOpen, FileText, Shield, Cpu,
|
type LucideIcon,
|
||||||
|
Search, BookOpen, FileText, Shield, Cpu, Globe2,
|
||||||
Server, Eye, Bot, ChevronRight, Plus, Sparkles, ClipboardList, Tag, TriangleAlert,
|
Server, Eye, Bot, ChevronRight, Plus, Sparkles, ClipboardList, Tag, TriangleAlert,
|
||||||
GitBranch, CheckCircle2, Clock3, Link2, FileSearch, ListChecks, ArrowRight, Users,
|
GitBranch, CheckCircle2, Clock3, Link2, FileSearch, ListChecks, ArrowRight, Users,
|
||||||
PlayCircle, Database, RotateCw,
|
PlayCircle, Database, RotateCw, Package, Wrench,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from 'react-markdown'
|
||||||
import remarkGfm from 'remark-gfm'
|
import remarkGfm from 'remark-gfm'
|
||||||
@@ -130,20 +131,58 @@ interface KnowledgeGovernanceTelemetry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AutomationAssetKey = 'km' | 'playbook' | 'script' | 'monitoring' | 'verifier' | 'rag'
|
type AutomationAssetKey = 'km' | 'playbook' | 'script' | 'monitoring' | 'verifier' | 'rag'
|
||||||
|
type WorkspaceViewKey = 'records' | 'automation' | 'queue'
|
||||||
|
type AssetLensKey = 'project' | 'product' | 'website' | 'service' | 'package' | 'tool'
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Category Config
|
// Category Config
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
const CATEGORY_ICONS: Record<string, React.ReactNode> = {
|
const CATEGORY_ICONS: Record<string, React.ReactNode> = {
|
||||||
|
'AI自動化/Ansible受控修復': <Bot className="w-4 h-4" />,
|
||||||
|
AI治理: <Bot className="w-4 h-4" />,
|
||||||
|
alert_handling: <TriangleAlert className="w-4 h-4" />,
|
||||||
infrastructure: <Server className="w-4 h-4" />,
|
infrastructure: <Server className="w-4 h-4" />,
|
||||||
application: <FileText className="w-4 h-4" />,
|
application: <FileText className="w-4 h-4" />,
|
||||||
ai_system: <Bot className="w-4 h-4" />,
|
ai_system: <Bot className="w-4 h-4" />,
|
||||||
security: <Shield className="w-4 h-4" />,
|
security: <Shield className="w-4 h-4" />,
|
||||||
|
host_resource: <Cpu className="w-4 h-4" />,
|
||||||
|
database: <Database className="w-4 h-4" />,
|
||||||
|
auto_repair: <Wrench className="w-4 h-4" />,
|
||||||
|
postmortem: <FileSearch className="w-4 h-4" />,
|
||||||
|
incident_postmortem: <FileSearch className="w-4 h-4" />,
|
||||||
|
external_site: <Globe2 className="w-4 h-4" />,
|
||||||
|
backup_failure: <Database className="w-4 h-4" />,
|
||||||
|
kubernetes: <Package className="w-4 h-4" />,
|
||||||
|
flywheel_health: <RotateCw className="w-4 h-4" />,
|
||||||
|
ssl_cert: <Shield className="w-4 h-4" />,
|
||||||
|
devops_tool: <Wrench className="w-4 h-4" />,
|
||||||
|
general: <BookOpen className="w-4 h-4" />,
|
||||||
}
|
}
|
||||||
|
|
||||||
const CATEGORIES = ['infrastructure', 'application', 'ai_system', 'security'] as const
|
const DEFAULT_CATEGORY_ORDER = [
|
||||||
|
'alert_handling',
|
||||||
|
'AI自動化/Ansible受控修復',
|
||||||
|
'infrastructure',
|
||||||
|
'host_resource',
|
||||||
|
'postmortem',
|
||||||
|
'AI治理',
|
||||||
|
'application',
|
||||||
|
'ai_system',
|
||||||
|
'security',
|
||||||
|
'database',
|
||||||
|
'auto_repair',
|
||||||
|
'kubernetes',
|
||||||
|
'external_site',
|
||||||
|
'devops_tool',
|
||||||
|
'ssl_cert',
|
||||||
|
'backup_failure',
|
||||||
|
'flywheel_health',
|
||||||
|
'general',
|
||||||
|
]
|
||||||
|
|
||||||
const KNOWN_CATEGORY_KEYS = new Set([
|
const KNOWN_CATEGORY_KEYS = new Set([
|
||||||
|
'AI自動化/Ansible受控修復',
|
||||||
'AI治理',
|
'AI治理',
|
||||||
'alert_handling',
|
'alert_handling',
|
||||||
'application',
|
'application',
|
||||||
@@ -160,8 +199,14 @@ const KNOWN_CATEGORY_KEYS = new Set([
|
|||||||
'infrastructure',
|
'infrastructure',
|
||||||
'kubernetes',
|
'kubernetes',
|
||||||
'postmortem',
|
'postmortem',
|
||||||
|
'project',
|
||||||
|
'product',
|
||||||
|
'service',
|
||||||
'security',
|
'security',
|
||||||
|
'source_control',
|
||||||
'ssl_cert',
|
'ssl_cert',
|
||||||
|
'tool',
|
||||||
|
'website',
|
||||||
])
|
])
|
||||||
|
|
||||||
const CATEGORY_OVERVIEW_LIMIT = 8
|
const CATEGORY_OVERVIEW_LIMIT = 8
|
||||||
@@ -222,17 +267,29 @@ const STATUS_COLORS: Record<string, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CATEGORY_BAR_COLORS: Record<string, string> = {
|
const CATEGORY_BAR_COLORS: Record<string, string> = {
|
||||||
|
'AI自動化/Ansible受控修復': 'bg-purple-500',
|
||||||
AI治理: 'bg-claw-blue',
|
AI治理: 'bg-claw-blue',
|
||||||
alert_handling: 'bg-status-warning',
|
alert_handling: 'bg-status-warning',
|
||||||
infrastructure: 'bg-claw-blue',
|
infrastructure: 'bg-claw-blue',
|
||||||
|
host_resource: 'bg-status-warning',
|
||||||
application: 'bg-status-healthy',
|
application: 'bg-status-healthy',
|
||||||
ai_system: 'bg-purple-500',
|
ai_system: 'bg-purple-500',
|
||||||
security: 'bg-status-warning',
|
security: 'bg-status-warning',
|
||||||
|
database: 'bg-status-critical',
|
||||||
|
auto_repair: 'bg-purple-500',
|
||||||
|
external_site: 'bg-status-healthy',
|
||||||
|
devops_tool: 'bg-claw-blue',
|
||||||
|
kubernetes: 'bg-claw-blue',
|
||||||
|
ssl_cert: 'bg-status-warning',
|
||||||
|
backup_failure: 'bg-status-critical',
|
||||||
|
flywheel_health: 'bg-status-healthy',
|
||||||
general: 'bg-nothing-gray-400',
|
general: 'bg-nothing-gray-400',
|
||||||
postmortem: 'bg-status-critical',
|
postmortem: 'bg-status-critical',
|
||||||
__other__: 'bg-nothing-gray-300',
|
__other__: 'bg-nothing-gray-300',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CATEGORY_ORDER_RANK = new Map(DEFAULT_CATEGORY_ORDER.map((category, index) => [category, index]))
|
||||||
|
|
||||||
const TAG_TONES: Record<string, string> = {
|
const TAG_TONES: Record<string, string> = {
|
||||||
critical: 'border-status-critical/20 bg-status-critical/10 text-status-critical',
|
critical: 'border-status-critical/20 bg-status-critical/10 text-status-critical',
|
||||||
execution_failed: 'border-status-critical/20 bg-status-critical/10 text-status-critical',
|
execution_failed: 'border-status-critical/20 bg-status-critical/10 text-status-critical',
|
||||||
@@ -285,6 +342,28 @@ const getSignalTags = (entry: KnowledgeEntry) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const entrySearchText = (entry: KnowledgeEntry) =>
|
||||||
|
[
|
||||||
|
entry.title,
|
||||||
|
entry.category,
|
||||||
|
entry.entry_type,
|
||||||
|
entry.source,
|
||||||
|
...entry.tags,
|
||||||
|
].join(' ').toLowerCase()
|
||||||
|
|
||||||
|
const entryMatchesAny = (entry: KnowledgeEntry, candidates: string[]) => {
|
||||||
|
const text = entrySearchText(entry)
|
||||||
|
return candidates.some(candidate => text.includes(candidate.toLowerCase()))
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergeCategoryCounts = (rows: CategoryCount[]) => {
|
||||||
|
const merged = new Map<string, number>()
|
||||||
|
rows.forEach(row => {
|
||||||
|
merged.set(row.category, (merged.get(row.category) ?? 0) + row.count)
|
||||||
|
})
|
||||||
|
return Array.from(merged.entries()).map(([category, count]) => ({ category, count }))
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Component
|
// Component
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -319,6 +398,7 @@ export default function KnowledgeBasePage({
|
|||||||
const [semanticResults, setSemanticResults] = useState<(KnowledgeEntry & { score: number })[]>([])
|
const [semanticResults, setSemanticResults] = useState<(KnowledgeEntry & { score: number })[]>([])
|
||||||
const [semanticLoading, setSemanticLoading] = useState(false)
|
const [semanticLoading, setSemanticLoading] = useState(false)
|
||||||
const [selectedEntry, setSelectedEntry] = useState<KnowledgeEntry | null>(null)
|
const [selectedEntry, setSelectedEntry] = useState<KnowledgeEntry | null>(null)
|
||||||
|
const [workspaceView, setWorkspaceView] = useState<WorkspaceViewKey>('records')
|
||||||
|
|
||||||
// KB-D: approve/archive state (2026-04-03 ogt)
|
// KB-D: approve/archive state (2026-04-03 ogt)
|
||||||
const [actionLoading, setActionLoading] = useState(false)
|
const [actionLoading, setActionLoading] = useState(false)
|
||||||
@@ -519,8 +599,8 @@ export default function KnowledgeBasePage({
|
|||||||
|
|
||||||
const categoryRows = useMemo(() => {
|
const categoryRows = useMemo(() => {
|
||||||
const sourceRows = categories.length > 0
|
const sourceRows = categories.length > 0
|
||||||
? [...categories].sort((a, b) => b.count - a.count)
|
? mergeCategoryCounts(categories).sort((a, b) => b.count - a.count)
|
||||||
: CATEGORIES.map(category => ({ category, count: 0 }))
|
: DEFAULT_CATEGORY_ORDER.map(category => ({ category, count: 0 }))
|
||||||
|
|
||||||
const visibleRows = sourceRows.slice(0, CATEGORY_OVERVIEW_LIMIT).map(row => {
|
const visibleRows = sourceRows.slice(0, CATEGORY_OVERVIEW_LIMIT).map(row => {
|
||||||
const pct = totalCount > 0 ? Math.round((row.count / totalCount) * 100) : 0
|
const pct = totalCount > 0 ? Math.round((row.count / totalCount) * 100) : 0
|
||||||
@@ -542,6 +622,81 @@ export default function KnowledgeBasePage({
|
|||||||
]
|
]
|
||||||
}, [categories, totalCount])
|
}, [categories, totalCount])
|
||||||
|
|
||||||
|
const categoryNavigationRows = useMemo(() => {
|
||||||
|
const sourceRows = categories.length > 0
|
||||||
|
? mergeCategoryCounts(categories).filter(row => row.count > 0)
|
||||||
|
: DEFAULT_CATEGORY_ORDER.map(category => ({ category, count: 0 }))
|
||||||
|
|
||||||
|
return [...sourceRows].sort((a, b) => {
|
||||||
|
const aRank = CATEGORY_ORDER_RANK.get(a.category) ?? Number.MAX_SAFE_INTEGER
|
||||||
|
const bRank = CATEGORY_ORDER_RANK.get(b.category) ?? Number.MAX_SAFE_INTEGER
|
||||||
|
if (aRank !== bRank) return aRank - bRank
|
||||||
|
return b.count - a.count
|
||||||
|
})
|
||||||
|
}, [categories])
|
||||||
|
|
||||||
|
const assetLensRows = useMemo(() => {
|
||||||
|
const rows: Array<{
|
||||||
|
key: AssetLensKey
|
||||||
|
icon: LucideIcon
|
||||||
|
count: number
|
||||||
|
tone: string
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
key: 'project',
|
||||||
|
icon: GitBranch,
|
||||||
|
count: displayedEntries.filter(entry => entryMatchesAny(entry, [
|
||||||
|
'project', 'repo', 'repository', 'gitea', 'branch', 'workflow', 'source_control',
|
||||||
|
])).length,
|
||||||
|
tone: 'text-claw-blue',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'product',
|
||||||
|
icon: Package,
|
||||||
|
count: displayedEntries.filter(entry => entryMatchesAny(entry, [
|
||||||
|
'product', 'awoooi', 'awooop', 'iwooos', 'vibework', 'stockplatform', 'momo', 'awooogo', 'agent-bounty', 'tsenyang',
|
||||||
|
])).length,
|
||||||
|
tone: 'text-purple-600',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'website',
|
||||||
|
icon: Globe2,
|
||||||
|
count: displayedEntries.filter(entry =>
|
||||||
|
entry.category === 'external_site'
|
||||||
|
|| entryMatchesAny(entry, ['website', 'site', 'nginx', 'ssl', 'domain', 'route', 'frontend']),
|
||||||
|
).length,
|
||||||
|
tone: 'text-status-healthy',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'service',
|
||||||
|
icon: Server,
|
||||||
|
count: displayedEntries.filter(entry =>
|
||||||
|
['infrastructure', 'application', 'ai_system', 'database', 'host_resource', 'kubernetes', 'alert_handling'].includes(entry.category)
|
||||||
|
|| entryMatchesAny(entry, ['service', 'daemon', 'api', 'worker', 'runtime', 'container', 'pod']),
|
||||||
|
).length,
|
||||||
|
tone: 'text-primary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'package',
|
||||||
|
icon: Package,
|
||||||
|
count: displayedEntries.filter(entry => entryMatchesAny(entry, [
|
||||||
|
'package', 'dependency', 'npm', 'pnpm', 'node', 'python', 'pip', 'prisma', 'next', 'library',
|
||||||
|
])).length,
|
||||||
|
tone: 'text-status-warning',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tool',
|
||||||
|
icon: Wrench,
|
||||||
|
count: displayedEntries.filter(entry =>
|
||||||
|
entry.category === 'devops_tool'
|
||||||
|
|| entryMatchesAny(entry, ['tool', 'ansible', 'mcp', 'playbook', 'telegram', 'wazuh', 'kali', 'sentry', 'signoz', 'runner']),
|
||||||
|
).length,
|
||||||
|
tone: 'text-status-critical',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
return rows
|
||||||
|
}, [displayedEntries])
|
||||||
|
|
||||||
const qualityRows = useMemo(() => {
|
const qualityRows = useMemo(() => {
|
||||||
const loaded = displayedEntries.length
|
const loaded = displayedEntries.length
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
@@ -887,11 +1042,20 @@ export default function KnowledgeBasePage({
|
|||||||
] as const
|
] as const
|
||||||
}, [formatCount, governanceSummary, governanceTelemetry.ragStats, t])
|
}, [formatCount, governanceSummary, governanceTelemetry.ragStats, t])
|
||||||
|
|
||||||
|
const workspaceViews = useMemo(
|
||||||
|
() => [
|
||||||
|
{ key: 'records' as WorkspaceViewKey, icon: BookOpen },
|
||||||
|
{ key: 'automation' as WorkspaceViewKey, icon: Bot },
|
||||||
|
{ key: 'queue' as WorkspaceViewKey, icon: ListChecks },
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<div className="flex min-h-[calc(100vh-64px)] flex-col lg:h-[calc(100vh-64px)] lg:flex-row">
|
<div className="flex min-h-[calc(100vh-64px)] flex-col lg:h-[calc(100vh-64px)] lg:flex-row">
|
||||||
|
|
||||||
{/* 左側分類導航 */}
|
{/* 左側分類導航 */}
|
||||||
<aside className="w-full flex-shrink-0 border-b border-nothing-gray-200/50 bg-white/50 p-3 lg:w-52 lg:border-b-0 lg:border-r">
|
<aside className="w-full flex-shrink-0 border-b border-nothing-gray-200/50 bg-white/60 p-3 lg:w-60 lg:border-b-0 lg:border-r">
|
||||||
<h2 className="font-heading text-xs font-semibold text-tertiary uppercase tracking-widest mb-3 px-2">
|
<h2 className="font-heading text-xs font-semibold text-tertiary uppercase tracking-widest mb-3 px-2">
|
||||||
{t('title')}
|
{t('title')}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -911,10 +1075,16 @@ export default function KnowledgeBasePage({
|
|||||||
<span className="text-xs text-muted">{totalCount}</span>
|
<span className="text-xs text-muted">{totalCount}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div className="mt-3 flex items-center justify-between px-2">
|
||||||
|
<span className="text-[10px] font-label uppercase tracking-wider text-muted">{t('rail.categoryTitle')}</span>
|
||||||
|
<span className="text-[10px] font-body tabular-nums text-muted">{categoryNavigationRows.length}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Category items */}
|
{/* Category items */}
|
||||||
<div className="mt-1 grid grid-cols-2 gap-0.5 lg:block lg:space-y-0.5">
|
<div className="mt-1 grid max-h-60 grid-cols-2 gap-0.5 overflow-y-auto pr-1 lg:block lg:space-y-0.5">
|
||||||
{CATEGORIES.map(cat => {
|
{categoryNavigationRows.map(row => {
|
||||||
const count = categories.find(c => c.category === cat)?.count ?? 0
|
const cat = row.category
|
||||||
|
const icon = CATEGORY_ICONS[cat] ?? <BookOpen className="w-4 h-4" />
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={cat}
|
key={cat}
|
||||||
@@ -926,13 +1096,63 @@ export default function KnowledgeBasePage({
|
|||||||
: 'text-secondary hover:bg-nothing-gray-100'
|
: 'text-secondary hover:bg-nothing-gray-100'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{CATEGORY_ICONS[cat]}
|
{icon}
|
||||||
<span className="flex-1 text-left">{formatCategoryLabel(cat)}</span>
|
<span className="flex-1 text-left">{formatCategoryLabel(cat)}</span>
|
||||||
<span className="text-xs text-muted">{count}</span>
|
<span className="text-xs text-muted">{formatCount(row.count)}</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 rounded-md border border-nothing-gray-200 bg-white/75 p-2">
|
||||||
|
<div className="mb-2 flex items-center justify-between gap-2">
|
||||||
|
<span className="truncate text-[10px] font-label uppercase tracking-wider text-muted">{t('assetLens.title')}</span>
|
||||||
|
<span className="shrink-0 text-[10px] font-body text-muted">{t('assetLens.scope')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-1">
|
||||||
|
{assetLensRows.map(row => {
|
||||||
|
const Icon = row.icon
|
||||||
|
return (
|
||||||
|
<div key={row.key} className="rounded border border-nothing-gray-100 bg-nothing-gray-50 px-2 py-1.5">
|
||||||
|
<div className="flex items-center justify-between gap-1">
|
||||||
|
<span className="truncate text-[10px] font-body text-secondary">{t(`assetLens.${row.key}` as never)}</span>
|
||||||
|
<Icon className={cn('h-3 w-3 shrink-0', row.tone)} aria-hidden={true} />
|
||||||
|
</div>
|
||||||
|
<p className={cn('mt-1 text-sm font-heading font-semibold tabular-nums', row.tone)}>
|
||||||
|
{formatCount(row.count)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 grid grid-cols-2 gap-2 lg:grid-cols-1">
|
||||||
|
{[
|
||||||
|
{ label: t('rail.total'), value: isKnowledgeListUnavailable ? '--' : formatCount(total), icon: BookOpen },
|
||||||
|
{ label: t('rail.aiExtracted'), value: formatCount(visibleSummary.aiExtracted), icon: Bot },
|
||||||
|
{
|
||||||
|
label: t('rail.controlledQueue'),
|
||||||
|
value: governanceLoading ? '--' : formatCount(governanceSummary.ownerPending),
|
||||||
|
icon: ListChecks,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('rail.ragChunks'),
|
||||||
|
value: governanceLoading ? '--' : formatCount(governanceTelemetry.ragStats?.total_chunks ?? 0),
|
||||||
|
icon: FileSearch,
|
||||||
|
},
|
||||||
|
].map(item => {
|
||||||
|
const Icon = item.icon
|
||||||
|
return (
|
||||||
|
<div key={item.label} className="rounded-md border border-nothing-gray-200 bg-white/75 px-2.5 py-2">
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<span className="truncate text-[10px] font-label uppercase tracking-wider text-muted">{item.label}</span>
|
||||||
|
<Icon className="h-3.5 w-3.5 shrink-0 text-claw-blue" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 text-lg font-heading font-semibold tabular-nums text-primary">{item.value}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
{/* 主區域 */}
|
{/* 主區域 */}
|
||||||
@@ -1077,9 +1297,31 @@ export default function KnowledgeBasePage({
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mt-3 flex flex-wrap gap-1 rounded-md border border-nothing-gray-200 bg-nothing-gray-50 p-1">
|
||||||
|
{workspaceViews.map(view => {
|
||||||
|
const Icon = view.icon
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={view.key}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setWorkspaceView(view.key)}
|
||||||
|
className={cn(
|
||||||
|
'inline-flex flex-1 items-center justify-center gap-1.5 rounded px-3 py-1.5 text-xs font-label transition-colors sm:flex-none',
|
||||||
|
workspaceView === view.key
|
||||||
|
? 'bg-white text-claw-blue shadow-sm'
|
||||||
|
: 'text-muted hover:bg-white/70 hover:text-secondary',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon className="h-3.5 w-3.5" aria-hidden="true" />
|
||||||
|
{t(`workspace.${view.key}` as never)}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-4 xl:grid-cols-[minmax(0,1fr)_280px]">
|
{workspaceView === 'records' && (
|
||||||
|
<div className="grid grid-cols-1 items-start gap-4 xl:grid-cols-[minmax(0,1fr)_280px]">
|
||||||
<div className="grid grid-cols-2 gap-2 lg:grid-cols-4">
|
<div className="grid grid-cols-2 gap-2 lg:grid-cols-4">
|
||||||
{[
|
{[
|
||||||
{ label: t('overview.metricTotal'), value: formatCount(total), sub: t('overview.scopeFiltered'), tone: 'text-claw-blue' },
|
{ label: t('overview.metricTotal'), value: formatCount(total), sub: t('overview.scopeFiltered'), tone: 'text-claw-blue' },
|
||||||
@@ -1120,7 +1362,9 @@ export default function KnowledgeBasePage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{workspaceView === 'automation' && (
|
||||||
<div className="mt-3 rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
|
<div className="mt-3 rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
|
||||||
<div className="mb-3 flex flex-col gap-1 sm:flex-row sm:items-start sm:justify-between">
|
<div className="mb-3 flex flex-col gap-1 sm:flex-row sm:items-start sm:justify-between">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
@@ -1169,7 +1413,9 @@ export default function KnowledgeBasePage({
|
|||||||
{t('assetLedger.boundary')}
|
{t('assetLedger.boundary')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{workspaceView === 'records' && (
|
||||||
<div className="mt-3 rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
|
<div className="mt-3 rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
|
||||||
<div className="mb-2 flex items-center justify-between gap-2">
|
<div className="mb-2 flex items-center justify-between gap-2">
|
||||||
<p className="text-[10px] font-label uppercase tracking-wider text-muted">{t('quality.title')}</p>
|
<p className="text-[10px] font-label uppercase tracking-wider text-muted">{t('quality.title')}</p>
|
||||||
@@ -1194,7 +1440,9 @@ export default function KnowledgeBasePage({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(workspaceView === 'records' || workspaceView === 'automation') && (
|
||||||
<div className="mt-3 grid grid-cols-1 gap-3 xl:grid-cols-[minmax(0,1.25fr)_minmax(260px,0.75fr)]">
|
<div className="mt-3 grid grid-cols-1 gap-3 xl:grid-cols-[minmax(0,1.25fr)_minmax(260px,0.75fr)]">
|
||||||
<div className="rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
|
<div className="rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
|
||||||
<div className="mb-3 flex items-center justify-between gap-2">
|
<div className="mb-3 flex items-center justify-between gap-2">
|
||||||
@@ -1259,7 +1507,9 @@ export default function KnowledgeBasePage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{workspaceView === 'queue' && (
|
||||||
<div className="mt-3 rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-3">
|
<div className="mt-3 rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-3">
|
||||||
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
@@ -1413,6 +1663,7 @@ export default function KnowledgeBasePage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* 結果列表 */}
|
{/* 結果列表 */}
|
||||||
|
|||||||
@@ -20,6 +20,24 @@
|
|||||||
- P0-006 仍 fail-closed:最新 production reboot SLO scorecard 仍有 `active_blocker_count=11`,包含 99 remote execution channel、VMware autostart readback、host boot detection、service/backup/Wazuh 類 blocker;不得宣稱 10 分鐘全主機自動恢復 SLO 已完成。
|
- P0-006 仍 fail-closed:最新 production reboot SLO scorecard 仍有 `active_blocker_count=11`,包含 99 remote execution channel、VMware autostart readback、host boot detection、service/backup/Wazuh 類 blocker;不得宣稱 10 分鐘全主機自動恢復 SLO 已完成。
|
||||||
- 未讀 secret / token / `.env` / raw sessions / SQLite / auth;未使用 GitHub / gh;未 workflow_dispatch;未重啟 host / VM / service;未 Docker / Nginx / K3s / DB / firewall restart;未 DROP / TRUNCATE / restore / prune / delete / force push。
|
- 未讀 secret / token / `.env` / raw sessions / SQLite / auth;未使用 GitHub / gh;未 workflow_dispatch;未重啟 host / VM / service;未 Docker / Nginx / K3s / DB / firewall restart;未 DROP / TRUNCATE / restore / prune / delete / force push。
|
||||||
|
|
||||||
|
## 2026-07-03 — 00:31 Knowledge Base 分類/資產鏡頭與 UI density audit
|
||||||
|
|
||||||
|
**完成內容**:
|
||||||
|
- `/zh-TW/knowledge-base` 左側分類由固定四類改為 production category readback 驅動,合併重複分類計數,移除 misleading 的 `應用層 0`、`安全 / 合規 0` 這類空殼分類。
|
||||||
|
- 新增資產鏡頭:專案、產品、網站、服務、套件、工具,先用目前列表的 title / category / type / tags 做可解釋分群,讓 KM 不再只以少數技術類別呈現。
|
||||||
|
- Knowledge Base 工作區改成 `記錄 / 自動化 / 隊列` 分段視圖,降低治理文字牆一次全部攤開的問題。
|
||||||
|
- 新增 `pnpm --dir apps/web audit:ui-density`,自動掃描所有 `src/app/**/page.tsx`,依文字密度、控制項不足、人工語意、固定分類等訊號排出 UI/UX 優先序;不用再靠逐頁人工回報。
|
||||||
|
- 修正 rebase 後 `awooop/work-items` fallback summary 多出的 `stockplatform_public_api_runtime_ready` 欄位,讓最新 main 的 web typecheck 恢復通過。
|
||||||
|
|
||||||
|
**驗證**:
|
||||||
|
- `python3.11 -m json.tool apps/web/messages/zh-TW.json`、`python3.11 -m json.tool apps/web/messages/en.json`:通過。
|
||||||
|
- `pnpm --dir apps/web typecheck`:通過。
|
||||||
|
- `pnpm --dir apps/web audit:ui-density`:通過;目前前五優先頁為 `:locale/iwooos`、`:locale/awooop/work-items`、`:locale/awooop/runs`、`:locale/awooop`、`:locale/awooop/approvals`。
|
||||||
|
- Local web + production API smoke:desktop / mobile HTTP 200,主 KM total `727` 可見,`告警處理 225`、`AI 自動化 / Ansible 受控修復 154`、資產鏡頭可見,API response 全部 200,console error `0`。
|
||||||
|
|
||||||
|
**仍維持**:
|
||||||
|
- 未讀 secret / token / `.env` / raw sessions / SQLite / auth;未使用 GitHub / gh;未 production DB schema / data destructive write;未重啟主機 / VM / Docker / K3s / DB / firewall。
|
||||||
|
|
||||||
## 2026-07-03 — 00:11 P0-006 latest CD profile / Windows99 live probe readback
|
## 2026-07-03 — 00:11 P0-006 latest CD profile / Windows99 live probe readback
|
||||||
|
|
||||||
**完成內容**:
|
**完成內容**:
|
||||||
|
|||||||
Reference in New Issue
Block a user