From b97bc6f35e9953b12432bba86b9c351fe607c43f Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 3 Jul 2026 00:35:57 +0800 Subject: [PATCH] feat(km): surface dynamic knowledge categories --- apps/web/messages/en.json | 76 +++-- apps/web/messages/zh-TW.json | 76 +++-- apps/web/package.json | 3 +- apps/web/scripts/audit-ui-density.mjs | 130 +++++++++ .../app/[locale]/awooop/work-items/page.tsx | 1 - .../src/app/[locale]/knowledge-base/page.tsx | 275 +++++++++++++++++- docs/LOGBOOK.md | 18 ++ 7 files changed, 519 insertions(+), 60 deletions(-) create mode 100644 apps/web/scripts/audit-ui-density.mjs diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 69f8e17a1..877b1e9a3 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -2159,23 +2159,40 @@ "title": "知識殿堂", "searchPlaceholder": "搜尋知識條目...", "allCategories": "全部", + "rail": { + "categoryTitle": "動態分類", + "total": "總 KM", + "aiExtracted": "AI 萃取", + "controlledQueue": "受控隊列", + "ragChunks": "RAG chunks" + }, + "assetLens": { + "title": "資產維度", + "scope": "目前列表", + "project": "專案", + "product": "產品", + "website": "網站", + "service": "服務", + "package": "套件", + "tool": "工具" + }, "dataChain": { "errorTitle": "知識條目資料鏈路異常", - "errorDescription": "主知識條目 API 未成功回應:{reason}。下方治理軌道仍會顯示 Hermes owner-review 與陳舊 KM 狀態,避免誤判成知識庫真的歸零。", + "errorDescription": "主知識條目 API 未成功回應:{reason}。下方治理軌道仍會顯示 Hermes 受控覆核與陳舊 KM 狀態,避免誤判成知識庫真的歸零。", "retry": "重新讀取" }, "decision": { "badge": "KM 治理警戒", - "readback": "Hermes / AwoooP 只讀回讀", + "readback": "Hermes / AwoooP 受控回讀", "title": "KM 自動化掌控台", - "subtitle": "先看 stale ratio、負責人審查、ready / blocked 與寫回數,再進入條目列表;這裡把 KM、PlayBook、腳本、排程與 Verifier 的沉澱狀態前移到首屏。", - "openWorkItems": "處理 Owner Review", + "subtitle": "先看 stale ratio、受控覆核、ready / blocked 與寫回數,再進入條目列表;這裡把 KM、PlayBook、腳本、排程與 Verifier 的沉澱狀態前移到首屏。", + "openWorkItems": "處理受控隊列", "card": { "staleRatio": "Stale Ratio", "staleRatioSub": "門檻 {threshold}", "staleTotal": "Stale KM", "staleTotalSub": "距離門檻需處理 {count}", - "ownerQueue": "Owner Review", + "ownerQueue": "受控覆核", "ownerQueueSub": "ready {ready} / blocked {blocked}", "writeback": "已寫回", "writebackSafe": "只讀回讀;讀取不寫入", @@ -2184,6 +2201,11 @@ "ragIndexSub": "sources {sources};0 代表語意檢索尚未沉澱" } }, + "workspace": { + "records": "Records", + "automation": "Automation", + "queue": "Queue" + }, "overview": { "metricTotal": "總條目", "metricLoaded": "目前列表", @@ -2196,7 +2218,7 @@ }, "assetLedger": { "title": "自動化資產沉澱總帳", - "subtitle": "把目前 KM 清單與 Hermes owner-review 讀模型,對齊到 PlayBook、腳本、排程、Verifier 五類資產。", + "subtitle": "把目前 KM 清單與 Hermes 受控覆核讀模型,對齊到 PlayBook、腳本、排程、Verifier 五類資產。", "readOnly": "只讀總覽", "boundary": "此區塊只顯示可追蹤沉澱狀態,不代表已授權修復、已寫回 KM 或已提高 PlayBook trust。", "asset": { @@ -2214,11 +2236,11 @@ }, "monitoring": { "title": "排程 / 監控規則", - "detail": "含告警 / 監控訊號 {ready} 筆;負責人審查 待處理 {pending} 筆。" + "detail": "含告警 / 監控訊號 {ready} 筆;受控覆核待處理 {pending} 筆。" }, "verifier": { "title": "Verifier 回寫", - "detail": "已批准或完成驗證 {ready} 筆;待 負責人審查 {pending} 筆。" + "detail": "已批准或完成驗證 {ready} 筆;待受控覆核 {pending} 筆。" }, "rag": { "title": "RAG / 向量索引", @@ -2244,7 +2266,7 @@ "incident": "Incident", "playbook": "Playbook", "review": "審核", - "sourceDetail": "AI {ai} / 人工 {human}", + "sourceDetail": "AI {ai} / 受控建立 {human}", "knowledgeDetail": "已載入 {count}", "countDetail": "{count} / {total}" }, @@ -2257,8 +2279,8 @@ "missingIncidentAction": "補齊來源事故或標記為通用知識", "missingPlaybook": "缺 Playbook", "missingPlaybookAction": "匹配既有 Playbook或建立草稿", - "reviewBacklog": "待 Owner 審核", - "reviewBacklogAction": "高影響條目先人工覆核再批准" + "reviewBacklog": "待受控覆核", + "reviewBacklogAction": "高影響條目先進 controlled review,再由 AI 補齊 verifier 與寫回證據" }, "entryLineage": { "title": "單筆證據鏈", @@ -2270,12 +2292,12 @@ }, "workItems": { "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", "loading": "讀取中", "threshold": "門檻 {value}", "entriesToThreshold": "需處理 {count} 筆才回到門檻", - "ownerPendingSub": "待 Owner review", + "ownerPendingSub": "待受控覆核", "readyBlockedSub": "ready / blocked", "burndownStatus": "狀態:{status}", "topCandidate": "最高優先處理項", @@ -2285,9 +2307,9 @@ "topCandidateRefs": "Incident {incident} / Playbook {playbook}", "noCandidate": "目前沒有可顯示的 stale KM candidate。", "ownerSplit": "主責分工", - "hermes": "Hermes:建立 KM 更新草稿與owner-review queue。", + "hermes": "Hermes:建立 KM 更新草稿與 controlled review queue。", "openclaw": "OpenClaw:補告警分類、Playbook與上下文摘要。", - "owner": "Owner:在 AwoooP Work Items 預覽後確認寫回。", + "owner": "Controlled lane:在 AwoooP Work Items 預覽後確認寫回邊界。", "noWritesOnRead": "讀取不寫入", "unexpectedWrites": "偵測到 read endpoint 宣告會寫入", "flow": { @@ -2295,9 +2317,9 @@ "scope": "從偵測到寫回與比例回測的目前位置", "node": { "detected": "偵測", - "ownerReview": "Owner Review", + "ownerReview": "Controlled Review", "dryRun": "乾跑預覽", - "ownerConfirm": "Owner 確認", + "ownerConfirm": "邊界確認", "writeback": "寫回 KM", "recheck": "比例回測" }, @@ -2310,7 +2332,7 @@ }, "detail": { "detected": "目前 {ratio};門檻 {threshold}", - "ownerReview": "{count} 筆等待 owner 審核", + "ownerReview": "{count} 筆等待受控覆核", "dryRun": "{ready} 筆可乾跑;{blocked} 筆卡住", "ownerConfirm": "確認後才允許寫回,避免 AI 固化錯誤知識", "writeback": "{count} 筆已有 completion audit", @@ -2356,7 +2378,7 @@ }, "source": { "ai_extracted": "AI 萃取", - "human": "人工建立" + "human": "受控建立" }, "signals": { "label": "訊號", @@ -2371,14 +2393,15 @@ "execution": "執行紀錄", "execution_failed": "執行失敗", "failure": "失敗", - "human_approved": "人工批准", - "human_intervention": "人工介入", + "human_approved": "受控批准", + "human_intervention": "受控介入", "incident": "事故", "postmortem": "事後檢討", "telegram": "Telegram", "warning": "警告" }, "category": { + "AI自動化/Ansible受控修復": "AI 自動化 / Ansible 受控修復", "AI治理": "AI 治理", "alert_handling": "告警處理", "infrastructure": "基礎設施", @@ -2396,13 +2419,20 @@ "flywheel_health": "飛輪健康", "ssl_cert": "SSL 憑證", "devops_tool": "DevOps 工具", - "incident_postmortem": "事故檢討" + "incident_postmortem": "事故檢討", + "project": "專案", + "product": "產品", + "website": "網站", + "service": "服務", + "package": "套件", + "tool": "工具", + "source_control": "Source Control" }, "filterByType": "篩選類型", "filterByStatus": "篩選狀態", "entries": "筆", "empty": "尚未建立任何知識條目", - "emptyDescription": "知識庫將自動從 Incident 中萃取案例,你也可以手動新增", + "emptyDescription": "知識庫將自動從 Incident、LOG、PlayBook 與 verifier 中萃取案例", "semanticSearchPlaceholder": "輸入語意搜尋查詢...", "semanticOn": "語意", "semanticOff": "語意", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 3052ffaee..c99903c22 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -2159,23 +2159,40 @@ "title": "知識殿堂", "searchPlaceholder": "搜尋知識條目...", "allCategories": "全部", + "rail": { + "categoryTitle": "動態分類", + "total": "總 KM", + "aiExtracted": "AI 萃取", + "controlledQueue": "受控隊列", + "ragChunks": "RAG chunks" + }, + "assetLens": { + "title": "資產維度", + "scope": "目前列表", + "project": "專案", + "product": "產品", + "website": "網站", + "service": "服務", + "package": "套件", + "tool": "工具" + }, "dataChain": { "errorTitle": "知識條目資料鏈路異常", - "errorDescription": "主知識條目 API 未成功回應:{reason}。下方治理軌道仍會顯示 Hermes owner-review 與陳舊 KM 狀態,避免誤判成知識庫真的歸零。", + "errorDescription": "主知識條目 API 未成功回應:{reason}。下方治理軌道仍會顯示 Hermes 受控覆核與陳舊 KM 狀態,避免誤判成知識庫真的歸零。", "retry": "重新讀取" }, "decision": { "badge": "KM 治理警戒", - "readback": "Hermes / AwoooP 只讀回讀", + "readback": "Hermes / AwoooP 受控回讀", "title": "KM 自動化掌控台", - "subtitle": "先看 stale ratio、負責人審查、ready / blocked 與寫回數,再進入條目列表;這裡把 KM、PlayBook、腳本、排程與 Verifier 的沉澱狀態前移到首屏。", - "openWorkItems": "處理 Owner Review", + "subtitle": "先看 stale ratio、受控覆核、ready / blocked 與寫回數,再進入條目列表;這裡把 KM、PlayBook、腳本、排程與 Verifier 的沉澱狀態前移到首屏。", + "openWorkItems": "處理受控隊列", "card": { "staleRatio": "Stale Ratio", "staleRatioSub": "門檻 {threshold}", "staleTotal": "Stale KM", "staleTotalSub": "距離門檻需處理 {count}", - "ownerQueue": "Owner Review", + "ownerQueue": "受控覆核", "ownerQueueSub": "ready {ready} / blocked {blocked}", "writeback": "已寫回", "writebackSafe": "只讀回讀;讀取不寫入", @@ -2184,6 +2201,11 @@ "ragIndexSub": "sources {sources};0 代表語意檢索尚未沉澱" } }, + "workspace": { + "records": "Records", + "automation": "Automation", + "queue": "Queue" + }, "overview": { "metricTotal": "總條目", "metricLoaded": "目前列表", @@ -2196,7 +2218,7 @@ }, "assetLedger": { "title": "自動化資產沉澱總帳", - "subtitle": "把目前 KM 清單與 Hermes owner-review 讀模型,對齊到 PlayBook、腳本、排程、Verifier 五類資產。", + "subtitle": "把目前 KM 清單與 Hermes 受控覆核讀模型,對齊到 PlayBook、腳本、排程、Verifier 五類資產。", "readOnly": "只讀總覽", "boundary": "此區塊只顯示可追蹤沉澱狀態,不代表已授權修復、已寫回 KM 或已提高 PlayBook trust。", "asset": { @@ -2214,11 +2236,11 @@ }, "monitoring": { "title": "排程 / 監控規則", - "detail": "含告警 / 監控訊號 {ready} 筆;負責人審查 待處理 {pending} 筆。" + "detail": "含告警 / 監控訊號 {ready} 筆;受控覆核待處理 {pending} 筆。" }, "verifier": { "title": "Verifier 回寫", - "detail": "已批准或完成驗證 {ready} 筆;待 負責人審查 {pending} 筆。" + "detail": "已批准或完成驗證 {ready} 筆;待受控覆核 {pending} 筆。" }, "rag": { "title": "RAG / 向量索引", @@ -2244,7 +2266,7 @@ "incident": "Incident", "playbook": "Playbook", "review": "審核", - "sourceDetail": "AI {ai} / 人工 {human}", + "sourceDetail": "AI {ai} / 受控建立 {human}", "knowledgeDetail": "已載入 {count}", "countDetail": "{count} / {total}" }, @@ -2257,8 +2279,8 @@ "missingIncidentAction": "補齊來源事故或標記為通用知識", "missingPlaybook": "缺 Playbook", "missingPlaybookAction": "匹配既有 Playbook或建立草稿", - "reviewBacklog": "待 Owner 審核", - "reviewBacklogAction": "高影響條目先人工覆核再批准" + "reviewBacklog": "待受控覆核", + "reviewBacklogAction": "高影響條目先進 controlled review,再由 AI 補齊 verifier 與寫回證據" }, "entryLineage": { "title": "單筆證據鏈", @@ -2270,12 +2292,12 @@ }, "workItems": { "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", "loading": "讀取中", "threshold": "門檻 {value}", "entriesToThreshold": "需處理 {count} 筆才回到門檻", - "ownerPendingSub": "待 Owner review", + "ownerPendingSub": "待受控覆核", "readyBlockedSub": "ready / blocked", "burndownStatus": "狀態:{status}", "topCandidate": "最高優先處理項", @@ -2285,9 +2307,9 @@ "topCandidateRefs": "Incident {incident} / Playbook {playbook}", "noCandidate": "目前沒有可顯示的 stale KM candidate。", "ownerSplit": "主責分工", - "hermes": "Hermes:建立 KM 更新草稿與owner-review queue。", + "hermes": "Hermes:建立 KM 更新草稿與 controlled review queue。", "openclaw": "OpenClaw:補告警分類、Playbook與上下文摘要。", - "owner": "Owner:在 AwoooP Work Items 預覽後確認寫回。", + "owner": "Controlled lane:在 AwoooP Work Items 預覽後確認寫回邊界。", "noWritesOnRead": "讀取不寫入", "unexpectedWrites": "偵測到 read endpoint 宣告會寫入", "flow": { @@ -2295,9 +2317,9 @@ "scope": "從偵測到寫回與比例回測的目前位置", "node": { "detected": "偵測", - "ownerReview": "Owner Review", + "ownerReview": "Controlled Review", "dryRun": "乾跑預覽", - "ownerConfirm": "Owner 確認", + "ownerConfirm": "邊界確認", "writeback": "寫回 KM", "recheck": "比例回測" }, @@ -2310,7 +2332,7 @@ }, "detail": { "detected": "目前 {ratio};門檻 {threshold}", - "ownerReview": "{count} 筆等待 owner 審核", + "ownerReview": "{count} 筆等待受控覆核", "dryRun": "{ready} 筆可乾跑;{blocked} 筆卡住", "ownerConfirm": "確認後才允許寫回,避免 AI 固化錯誤知識", "writeback": "{count} 筆已有 completion audit", @@ -2356,7 +2378,7 @@ }, "source": { "ai_extracted": "AI 萃取", - "human": "人工建立" + "human": "受控建立" }, "signals": { "label": "訊號", @@ -2371,14 +2393,15 @@ "execution": "執行紀錄", "execution_failed": "執行失敗", "failure": "失敗", - "human_approved": "人工批准", - "human_intervention": "人工介入", + "human_approved": "受控批准", + "human_intervention": "受控介入", "incident": "事故", "postmortem": "事後檢討", "telegram": "Telegram", "warning": "警告" }, "category": { + "AI自動化/Ansible受控修復": "AI 自動化 / Ansible 受控修復", "AI治理": "AI 治理", "alert_handling": "告警處理", "infrastructure": "基礎設施", @@ -2396,13 +2419,20 @@ "flywheel_health": "飛輪健康", "ssl_cert": "SSL 憑證", "devops_tool": "DevOps 工具", - "incident_postmortem": "事故檢討" + "incident_postmortem": "事故檢討", + "project": "專案", + "product": "產品", + "website": "網站", + "service": "服務", + "package": "套件", + "tool": "工具", + "source_control": "Source Control" }, "filterByType": "篩選類型", "filterByStatus": "篩選狀態", "entries": "筆", "empty": "尚未建立任何知識條目", - "emptyDescription": "知識庫將自動從 Incident 中萃取案例,你也可以手動新增", + "emptyDescription": "知識庫將自動從 Incident、LOG、PlayBook 與 verifier 中萃取案例", "semanticSearchPlaceholder": "輸入語意搜尋查詢...", "semanticOn": "語意", "semanticOff": "語意", diff --git a/apps/web/package.json b/apps/web/package.json index 87219531e..fce40b97e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -7,7 +7,8 @@ "build": "next build", "start": "next start", "lint": "next lint", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "audit:ui-density": "node scripts/audit-ui-density.mjs" }, "dependencies": { "@awoooi/lewooogo-core": "workspace:*", diff --git a/apps/web/scripts/audit-ui-density.mjs b/apps/web/scripts/audit-ui-density.mjs new file mode 100644 index 000000000..cffc8987a --- /dev/null +++ b/apps/web/scripts/audit-ui-density.mjs @@ -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 = [ + / 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)) diff --git a/apps/web/src/app/[locale]/awooop/work-items/page.tsx b/apps/web/src/app/[locale]/awooop/work-items/page.tsx index c54d3bc8c..12587ca04 100644 --- a/apps/web/src/app/[locale]/awooop/work-items/page.tsx +++ b/apps/web/src/app/[locale]/awooop/work-items/page.tsx @@ -1288,7 +1288,6 @@ const COMMANDER_INSERTED_REQUIREMENT_FALLBACK: PriorityWorkOrderResponse = { "windows99_uptime_unknown", ], stockplatform_public_api_runtime_status: "stockplatform_public_api_runtime_ready", - stockplatform_public_api_runtime_ready: true, stockplatform_public_api_controlled_recovery_preflight_status: "not_required_stockplatform_runtime_ready", stockplatform_public_api_controlled_recovery_data_dependency: "none", diff --git a/apps/web/src/app/[locale]/knowledge-base/page.tsx b/apps/web/src/app/[locale]/knowledge-base/page.tsx index 8c67606be..446bd18e8 100644 --- a/apps/web/src/app/[locale]/knowledge-base/page.tsx +++ b/apps/web/src/app/[locale]/knowledge-base/page.tsx @@ -17,10 +17,11 @@ import { useTranslations } from 'next-intl' import Link from 'next/link' import { cn } from '@/lib/utils' import { - Search, BookOpen, FileText, Shield, Cpu, + type LucideIcon, + Search, BookOpen, FileText, Shield, Cpu, Globe2, Server, Eye, Bot, ChevronRight, Plus, Sparkles, ClipboardList, Tag, TriangleAlert, GitBranch, CheckCircle2, Clock3, Link2, FileSearch, ListChecks, ArrowRight, Users, - PlayCircle, Database, RotateCw, + PlayCircle, Database, RotateCw, Package, Wrench, } from 'lucide-react' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' @@ -130,20 +131,58 @@ interface KnowledgeGovernanceTelemetry { } type AutomationAssetKey = 'km' | 'playbook' | 'script' | 'monitoring' | 'verifier' | 'rag' +type WorkspaceViewKey = 'records' | 'automation' | 'queue' +type AssetLensKey = 'project' | 'product' | 'website' | 'service' | 'package' | 'tool' // ============================================================================= // Category Config // ============================================================================= const CATEGORY_ICONS: Record = { + 'AI自動化/Ansible受控修復': , + AI治理: , + alert_handling: , infrastructure: , application: , ai_system: , security: , + host_resource: , + database: , + auto_repair: , + postmortem: , + incident_postmortem: , + external_site: , + backup_failure: , + kubernetes: , + flywheel_health: , + ssl_cert: , + devops_tool: , + general: , } -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([ + 'AI自動化/Ansible受控修復', 'AI治理', 'alert_handling', 'application', @@ -160,8 +199,14 @@ const KNOWN_CATEGORY_KEYS = new Set([ 'infrastructure', 'kubernetes', 'postmortem', + 'project', + 'product', + 'service', 'security', + 'source_control', 'ssl_cert', + 'tool', + 'website', ]) const CATEGORY_OVERVIEW_LIMIT = 8 @@ -222,17 +267,29 @@ const STATUS_COLORS: Record = { } const CATEGORY_BAR_COLORS: Record = { + 'AI自動化/Ansible受控修復': 'bg-purple-500', AI治理: 'bg-claw-blue', alert_handling: 'bg-status-warning', infrastructure: 'bg-claw-blue', + host_resource: 'bg-status-warning', application: 'bg-status-healthy', ai_system: 'bg-purple-500', 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', postmortem: 'bg-status-critical', __other__: 'bg-nothing-gray-300', } +const CATEGORY_ORDER_RANK = new Map(DEFAULT_CATEGORY_ORDER.map((category, index) => [category, index])) + const TAG_TONES: Record = { 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', @@ -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() + 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 // ============================================================================= @@ -319,6 +398,7 @@ export default function KnowledgeBasePage({ const [semanticResults, setSemanticResults] = useState<(KnowledgeEntry & { score: number })[]>([]) const [semanticLoading, setSemanticLoading] = useState(false) const [selectedEntry, setSelectedEntry] = useState(null) + const [workspaceView, setWorkspaceView] = useState('records') // KB-D: approve/archive state (2026-04-03 ogt) const [actionLoading, setActionLoading] = useState(false) @@ -519,8 +599,8 @@ export default function KnowledgeBasePage({ const categoryRows = useMemo(() => { const sourceRows = categories.length > 0 - ? [...categories].sort((a, b) => b.count - a.count) - : CATEGORIES.map(category => ({ category, count: 0 })) + ? mergeCategoryCounts(categories).sort((a, b) => b.count - a.count) + : DEFAULT_CATEGORY_ORDER.map(category => ({ category, count: 0 })) const visibleRows = sourceRows.slice(0, CATEGORY_OVERVIEW_LIMIT).map(row => { const pct = totalCount > 0 ? Math.round((row.count / totalCount) * 100) : 0 @@ -542,6 +622,81 @@ export default function KnowledgeBasePage({ ] }, [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 loaded = displayedEntries.length const now = Date.now() @@ -887,11 +1042,20 @@ export default function KnowledgeBasePage({ ] as const }, [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 = (
{/* 左側分類導航 */} -
+
+ {workspaceViews.map(view => { + const Icon = view.icon + return ( + + ) + })} +
-
+ {workspaceView === 'records' && ( +
{[ { label: t('overview.metricTotal'), value: formatCount(total), sub: t('overview.scopeFiltered'), tone: 'text-claw-blue' }, @@ -1120,7 +1362,9 @@ export default function KnowledgeBasePage({
+ )} + {workspaceView === 'automation' && (
@@ -1169,7 +1413,9 @@ export default function KnowledgeBasePage({ {t('assetLedger.boundary')}

+ )} + {workspaceView === 'records' && (

{t('quality.title')}

@@ -1194,7 +1440,9 @@ export default function KnowledgeBasePage({ ))}
+ )} + {(workspaceView === 'records' || workspaceView === 'automation') && (
@@ -1259,7 +1507,9 @@ export default function KnowledgeBasePage({
+ )} + {workspaceView === 'queue' && (
@@ -1413,6 +1663,7 @@ export default function KnowledgeBasePage({
+ )} {/* 結果列表 */} diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 5dc376e10..8eb369cb3 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -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 已完成。 - 未讀 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 **完成內容**: