fix(web): surface knowledge taxonomy readback
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m3s
CD Pipeline / build-and-deploy (push) Successful in 6m43s
CD Pipeline / post-deploy-checks (push) Successful in 1m53s
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m3s
CD Pipeline / build-and-deploy (push) Successful in 6m43s
CD Pipeline / post-deploy-checks (push) Successful in 1m53s
This commit is contained in:
@@ -2168,13 +2168,37 @@
|
||||
},
|
||||
"assetLens": {
|
||||
"title": "資產維度",
|
||||
"scope": "目前列表",
|
||||
"scope": "自動更新",
|
||||
"matrixTitle": "全域分類 / 資產矩陣",
|
||||
"matrixSubtitle": "用目前 KM readback 與 RAG stats,把專案、產品、網站、服務、套件、工具、Log、Alert、PlayBook、RAG、MCP 與排程分群顯示。",
|
||||
"autoUpdated": "API 驅動",
|
||||
"global": "全域",
|
||||
"project": "專案",
|
||||
"product": "產品",
|
||||
"website": "網站",
|
||||
"service": "服務",
|
||||
"package": "套件",
|
||||
"tool": "工具"
|
||||
"tool": "工具",
|
||||
"log": "Log / 事件",
|
||||
"alert": "Alert / 告警",
|
||||
"playbook": "PlayBook",
|
||||
"rag": "RAG / 向量",
|
||||
"mcp": "MCP / Connector",
|
||||
"schedule": "排程 / Worker",
|
||||
"detail": {
|
||||
"project": "repo、branch、workflow 與 source-control 來源。",
|
||||
"product": "AWOOOI、IwoooS、AwoooP 與跨產品名稱命中。",
|
||||
"website": "domain、route、frontend、SSL 與外部站點訊號。",
|
||||
"service": "API、worker、runtime、container、pod 與基礎服務。",
|
||||
"package": "npm、pnpm、Python、dependency 與 library 訊號。",
|
||||
"tool": "Ansible、Wazuh、Kali、Sentry、SigNoz、runner 等工具。",
|
||||
"log": "LOG、event、timeline、trace、audit、callback 與 telemetry。",
|
||||
"alert": "Alertmanager、Telegram、Sentry、warning 與 critical 訊號。",
|
||||
"playbook": "已關聯 PlayBook / runbook 或 SOP 的 KM。",
|
||||
"rag": "RAG stats 全域 chunks;不是只看目前 50 筆列表。",
|
||||
"mcp": "MCP gateway、connector 與工具整合訊號。",
|
||||
"schedule": "cron、job、worker、recurrence 與 patrol 排程訊號。"
|
||||
}
|
||||
},
|
||||
"dataChain": {
|
||||
"errorTitle": "知識條目資料鏈路異常",
|
||||
@@ -2214,7 +2238,9 @@
|
||||
"scopeFiltered": "目前篩選",
|
||||
"scopeCurrent": "已載入",
|
||||
"categoryDistribution": "分類分佈",
|
||||
"categoryOther": "其他分類"
|
||||
"categoryOther": "其他分類",
|
||||
"loading": "正在讀取 KM 分類...",
|
||||
"noCategories": "目前沒有可顯示分類。"
|
||||
},
|
||||
"assetLedger": {
|
||||
"title": "自動化資產沉澱總帳",
|
||||
|
||||
@@ -2168,13 +2168,37 @@
|
||||
},
|
||||
"assetLens": {
|
||||
"title": "資產維度",
|
||||
"scope": "目前列表",
|
||||
"scope": "自動更新",
|
||||
"matrixTitle": "全域分類 / 資產矩陣",
|
||||
"matrixSubtitle": "用目前 KM readback 與 RAG stats,把專案、產品、網站、服務、套件、工具、Log、Alert、PlayBook、RAG、MCP 與排程分群顯示。",
|
||||
"autoUpdated": "API 驅動",
|
||||
"global": "全域",
|
||||
"project": "專案",
|
||||
"product": "產品",
|
||||
"website": "網站",
|
||||
"service": "服務",
|
||||
"package": "套件",
|
||||
"tool": "工具"
|
||||
"tool": "工具",
|
||||
"log": "Log / 事件",
|
||||
"alert": "Alert / 告警",
|
||||
"playbook": "PlayBook",
|
||||
"rag": "RAG / 向量",
|
||||
"mcp": "MCP / Connector",
|
||||
"schedule": "排程 / Worker",
|
||||
"detail": {
|
||||
"project": "repo、branch、workflow 與 source-control 來源。",
|
||||
"product": "AWOOOI、IwoooS、AwoooP 與跨產品名稱命中。",
|
||||
"website": "domain、route、frontend、SSL 與外部站點訊號。",
|
||||
"service": "API、worker、runtime、container、pod 與基礎服務。",
|
||||
"package": "npm、pnpm、Python、dependency 與 library 訊號。",
|
||||
"tool": "Ansible、Wazuh、Kali、Sentry、SigNoz、runner 等工具。",
|
||||
"log": "LOG、event、timeline、trace、audit、callback 與 telemetry。",
|
||||
"alert": "Alertmanager、Telegram、Sentry、warning 與 critical 訊號。",
|
||||
"playbook": "已關聯 PlayBook / runbook 或 SOP 的 KM。",
|
||||
"rag": "RAG stats 全域 chunks;不是只看目前 50 筆列表。",
|
||||
"mcp": "MCP gateway、connector 與工具整合訊號。",
|
||||
"schedule": "cron、job、worker、recurrence 與 patrol 排程訊號。"
|
||||
}
|
||||
},
|
||||
"dataChain": {
|
||||
"errorTitle": "知識條目資料鏈路異常",
|
||||
@@ -2214,7 +2238,9 @@
|
||||
"scopeFiltered": "目前篩選",
|
||||
"scopeCurrent": "已載入",
|
||||
"categoryDistribution": "分類分佈",
|
||||
"categoryOther": "其他分類"
|
||||
"categoryOther": "其他分類",
|
||||
"loading": "正在讀取 KM 分類...",
|
||||
"noCategories": "目前沒有可顯示分類。"
|
||||
},
|
||||
"assetLedger": {
|
||||
"title": "自動化資產沉澱總帳",
|
||||
|
||||
@@ -132,7 +132,19 @@ interface KnowledgeGovernanceTelemetry {
|
||||
|
||||
type AutomationAssetKey = 'km' | 'playbook' | 'script' | 'monitoring' | 'verifier' | 'rag'
|
||||
type WorkspaceViewKey = 'records' | 'automation' | 'queue'
|
||||
type AssetLensKey = 'project' | 'product' | 'website' | 'service' | 'package' | 'tool'
|
||||
type AssetLensKey =
|
||||
| 'project'
|
||||
| 'product'
|
||||
| 'website'
|
||||
| 'service'
|
||||
| 'package'
|
||||
| 'tool'
|
||||
| 'log'
|
||||
| 'alert'
|
||||
| 'playbook'
|
||||
| 'rag'
|
||||
| 'mcp'
|
||||
| 'schedule'
|
||||
|
||||
// =============================================================================
|
||||
// Category Config
|
||||
@@ -460,7 +472,7 @@ export default function KnowledgeBasePage({
|
||||
fetch(`${apiBase}/api/v1/ai/governance/km-stale-owner-review-completion-queue?project_id=awoooi&status_bucket=all&limit=5`)
|
||||
.then(res => res.ok ? res.json() : null)
|
||||
.catch(() => null),
|
||||
fetch(`${apiBase}/api/v1/knowledge/rag/stats`)
|
||||
fetch(`${apiBase}/api/v1/knowledge/rag/stats?project_id=${encodeURIComponent(KNOWLEDGE_PROJECT_ID)}`)
|
||||
.then(res => res.ok ? res.json() : null)
|
||||
.catch(() => null),
|
||||
])
|
||||
@@ -561,6 +573,7 @@ export default function KnowledgeBasePage({
|
||||
const displayedEntries = semanticMode ? semanticResults : entries
|
||||
const totalCount = categories.reduce((sum, c) => sum + c.count, 0)
|
||||
const isKnowledgeListUnavailable = Boolean(entryFetchError)
|
||||
const knowledgeReadbackReady = !loading && !isKnowledgeListUnavailable
|
||||
const localeCode = params.locale === 'en' ? 'en-US' : 'zh-TW'
|
||||
const formatCount = useCallback(
|
||||
(value: number) => value.toLocaleString(localeCode),
|
||||
@@ -598,9 +611,9 @@ export default function KnowledgeBasePage({
|
||||
}, [displayedEntries])
|
||||
|
||||
const categoryRows = useMemo(() => {
|
||||
const sourceRows = categories.length > 0
|
||||
? mergeCategoryCounts(categories).sort((a, b) => b.count - a.count)
|
||||
: DEFAULT_CATEGORY_ORDER.map(category => ({ category, count: 0 }))
|
||||
if (categories.length === 0) return []
|
||||
|
||||
const sourceRows = mergeCategoryCounts(categories).sort((a, b) => b.count - a.count)
|
||||
|
||||
const visibleRows = sourceRows.slice(0, CATEGORY_OVERVIEW_LIMIT).map(row => {
|
||||
const pct = totalCount > 0 ? Math.round((row.count / totalCount) * 100) : 0
|
||||
@@ -623,9 +636,9 @@ 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 }))
|
||||
if (categories.length === 0) return []
|
||||
|
||||
const sourceRows = mergeCategoryCounts(categories).filter(row => row.count > 0)
|
||||
|
||||
return [...sourceRows].sort((a, b) => {
|
||||
const aRank = CATEGORY_ORDER_RANK.get(a.category) ?? Number.MAX_SAFE_INTEGER
|
||||
@@ -636,26 +649,30 @@ export default function KnowledgeBasePage({
|
||||
}, [categories])
|
||||
|
||||
const assetLensRows = useMemo(() => {
|
||||
const ragChunks = governanceTelemetry.ragStats?.total_chunks ?? 0
|
||||
const countWhere = (candidates: string[]) =>
|
||||
displayedEntries.filter(entry => entryMatchesAny(entry, candidates)).length
|
||||
const rows: Array<{
|
||||
key: AssetLensKey
|
||||
icon: LucideIcon
|
||||
count: number
|
||||
tone: string
|
||||
global?: boolean
|
||||
}> = [
|
||||
{
|
||||
key: 'project',
|
||||
icon: GitBranch,
|
||||
count: displayedEntries.filter(entry => entryMatchesAny(entry, [
|
||||
count: countWhere([
|
||||
'project', 'repo', 'repository', 'gitea', 'branch', 'workflow', 'source_control',
|
||||
])).length,
|
||||
]),
|
||||
tone: 'text-claw-blue',
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
icon: Package,
|
||||
count: displayedEntries.filter(entry => entryMatchesAny(entry, [
|
||||
count: countWhere([
|
||||
'product', 'awoooi', 'awooop', 'iwooos', 'vibework', 'stockplatform', 'momo', 'awooogo', 'agent-bounty', 'tsenyang',
|
||||
])).length,
|
||||
]),
|
||||
tone: 'text-purple-600',
|
||||
},
|
||||
{
|
||||
@@ -679,9 +696,9 @@ export default function KnowledgeBasePage({
|
||||
{
|
||||
key: 'package',
|
||||
icon: Package,
|
||||
count: displayedEntries.filter(entry => entryMatchesAny(entry, [
|
||||
count: countWhere([
|
||||
'package', 'dependency', 'npm', 'pnpm', 'node', 'python', 'pip', 'prisma', 'next', 'library',
|
||||
])).length,
|
||||
]),
|
||||
tone: 'text-status-warning',
|
||||
},
|
||||
{
|
||||
@@ -693,9 +710,51 @@ export default function KnowledgeBasePage({
|
||||
).length,
|
||||
tone: 'text-status-critical',
|
||||
},
|
||||
{
|
||||
key: 'log',
|
||||
icon: FileText,
|
||||
count: countWhere(['log', 'logs', 'event', 'timeline', 'trace', 'audit', 'callback', 'conversation_event', 'telemetry']),
|
||||
tone: 'text-primary',
|
||||
},
|
||||
{
|
||||
key: 'alert',
|
||||
icon: TriangleAlert,
|
||||
count: displayedEntries.filter(entry =>
|
||||
entry.category === 'alert_handling'
|
||||
|| entryMatchesAny(entry, ['alert', 'telegram', 'sentry', 'signoz', 'notification', 'warning', 'critical']),
|
||||
).length,
|
||||
tone: 'text-status-warning',
|
||||
},
|
||||
{
|
||||
key: 'playbook',
|
||||
icon: ClipboardList,
|
||||
count: displayedEntries.filter(entry =>
|
||||
Boolean(entry.related_playbook_id) || entryMatchesAny(entry, ['playbook', 'runbook', 'sop']),
|
||||
).length,
|
||||
tone: 'text-claw-blue',
|
||||
},
|
||||
{
|
||||
key: 'rag',
|
||||
icon: FileSearch,
|
||||
count: ragChunks || countWhere(['rag', 'vector', 'embedding', 'semantic', 'retrieval']),
|
||||
tone: ragChunks > 0 ? 'text-status-healthy' : 'text-status-critical',
|
||||
global: ragChunks > 0,
|
||||
},
|
||||
{
|
||||
key: 'mcp',
|
||||
icon: Wrench,
|
||||
count: countWhere(['mcp', 'connector', 'gateway', 'tool integration', 'tool-integration']),
|
||||
tone: 'text-purple-600',
|
||||
},
|
||||
{
|
||||
key: 'schedule',
|
||||
icon: Clock3,
|
||||
count: countWhere(['schedule', 'cron', 'job', 'worker', 'patrol', 'recurrence', 'cadence']),
|
||||
tone: 'text-status-healthy',
|
||||
},
|
||||
]
|
||||
return rows
|
||||
}, [displayedEntries])
|
||||
}, [displayedEntries, governanceTelemetry.ragStats])
|
||||
|
||||
const qualityRows = useMemo(() => {
|
||||
const loaded = displayedEntries.length
|
||||
@@ -1072,17 +1131,19 @@ export default function KnowledgeBasePage({
|
||||
>
|
||||
<BookOpen className="w-4 h-4" />
|
||||
<span className="flex-1 text-left">{t('allCategories')}</span>
|
||||
<span className="text-xs text-muted">{totalCount}</span>
|
||||
<span className="text-xs text-muted">{knowledgeReadbackReady ? formatCount(total) : '--'}</span>
|
||||
</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>
|
||||
<span className="text-[10px] font-body tabular-nums text-muted">
|
||||
{knowledgeReadbackReady ? categoryNavigationRows.length : '--'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Category items */}
|
||||
<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">
|
||||
{categoryNavigationRows.map(row => {
|
||||
{categoryNavigationRows.length > 0 ? categoryNavigationRows.map(row => {
|
||||
const cat = row.category
|
||||
const icon = CATEGORY_ICONS[cat] ?? <BookOpen className="w-4 h-4" />
|
||||
return (
|
||||
@@ -1101,7 +1162,11 @@ export default function KnowledgeBasePage({
|
||||
<span className="text-xs text-muted">{formatCount(row.count)}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
}) : (
|
||||
<p className="col-span-2 px-2 py-2 text-xs font-body text-muted lg:col-span-1">
|
||||
{loading ? t('overview.loading') : t('overview.noCategories')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 rounded-md border border-nothing-gray-200 bg-white/75 p-2">
|
||||
@@ -1119,7 +1184,7 @@ export default function KnowledgeBasePage({
|
||||
<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)}
|
||||
{(row.key === 'rag' ? governanceLoading : !knowledgeReadbackReady) ? '--' : formatCount(row.count)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
@@ -1128,8 +1193,8 @@ export default function KnowledgeBasePage({
|
||||
</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.total'), value: knowledgeReadbackReady ? formatCount(total) : '--', icon: BookOpen },
|
||||
{ label: t('rail.aiExtracted'), value: knowledgeReadbackReady ? formatCount(visibleSummary.aiExtracted) : '--', icon: Bot },
|
||||
{
|
||||
label: t('rail.controlledQueue'),
|
||||
value: governanceLoading ? '--' : formatCount(governanceSummary.ownerPending),
|
||||
@@ -1209,7 +1274,7 @@ export default function KnowledgeBasePage({
|
||||
|
||||
{/* Count */}
|
||||
<span className="text-xs text-muted font-body">
|
||||
{isKnowledgeListUnavailable ? '--' : total} {t('entries')}
|
||||
{knowledgeReadbackReady ? formatCount(total) : '--'} {t('entries')}
|
||||
</span>
|
||||
|
||||
{/* Create button */}
|
||||
@@ -1324,15 +1389,15 @@ export default function KnowledgeBasePage({
|
||||
<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">
|
||||
{[
|
||||
{ label: t('overview.metricTotal'), value: formatCount(total), sub: t('overview.scopeFiltered'), tone: 'text-claw-blue' },
|
||||
{ label: t('overview.metricLoaded'), value: formatCount(visibleSummary.loaded), sub: t('overview.scopeCurrent'), tone: 'text-primary' },
|
||||
{ label: t('overview.metricAiExtracted'), value: formatCount(visibleSummary.aiExtracted), sub: t('overview.scopeCurrent'), tone: 'text-purple-600' },
|
||||
{ label: t('overview.metricApproved'), value: `${formatCount(visibleSummary.approved)} / ${visibleSummary.approvedRate}%`, sub: t('overview.scopeCurrent'), tone: 'text-status-healthy' },
|
||||
{ label: t('overview.metricTotal'), value: knowledgeReadbackReady ? formatCount(total) : '--', sub: t('overview.scopeFiltered'), tone: 'text-claw-blue' },
|
||||
{ label: t('overview.metricLoaded'), value: knowledgeReadbackReady ? formatCount(visibleSummary.loaded) : '--', sub: t('overview.scopeCurrent'), tone: 'text-primary' },
|
||||
{ label: t('overview.metricAiExtracted'), value: knowledgeReadbackReady ? formatCount(visibleSummary.aiExtracted) : '--', sub: t('overview.scopeCurrent'), tone: 'text-purple-600' },
|
||||
{ label: t('overview.metricApproved'), value: knowledgeReadbackReady ? `${formatCount(visibleSummary.approved)} / ${visibleSummary.approvedRate}%` : '--', sub: t('overview.scopeCurrent'), tone: 'text-status-healthy' },
|
||||
].map(metric => (
|
||||
<div key={metric.label} className="rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
|
||||
<p className="text-[10px] font-label uppercase tracking-wider text-muted">{metric.label}</p>
|
||||
<p className={cn('mt-1 text-xl font-heading font-semibold tabular-nums', metric.tone)}>
|
||||
{isKnowledgeListUnavailable ? '--' : metric.value}
|
||||
{metric.value}
|
||||
</p>
|
||||
<p className="mt-0.5 text-[10px] font-body text-muted">{metric.sub}</p>
|
||||
</div>
|
||||
@@ -1342,8 +1407,11 @@ export default function KnowledgeBasePage({
|
||||
<div className="rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<p className="text-[10px] font-label uppercase tracking-wider text-muted">{t('overview.categoryDistribution')}</p>
|
||||
<span className="text-[10px] font-body text-muted">{formatCount(totalCount)}</span>
|
||||
<span className="text-[10px] font-body text-muted">
|
||||
{knowledgeReadbackReady ? formatCount(totalCount) : '--'}
|
||||
</span>
|
||||
</div>
|
||||
{categoryRows.length > 0 ? (
|
||||
<div className="space-y-1.5">
|
||||
{categoryRows.map(row => (
|
||||
<div key={row.category} className="grid grid-cols-[96px_1fr_42px] items-center gap-2">
|
||||
@@ -1360,6 +1428,57 @@ export default function KnowledgeBasePage({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs font-body text-muted">
|
||||
{loading ? t('overview.loading') : t('overview.noCategories')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{workspaceView === 'records' && (
|
||||
<div
|
||||
className="mt-3 rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2"
|
||||
data-testid="knowledge-base-taxonomy-matrix"
|
||||
>
|
||||
<div className="mb-3 flex flex-col gap-1 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="min-w-0">
|
||||
<p className="text-[10px] font-label uppercase tracking-wider text-muted">{t('assetLens.matrixTitle')}</p>
|
||||
<p className="mt-0.5 text-[10px] font-body text-muted">{t('assetLens.matrixSubtitle')}</p>
|
||||
</div>
|
||||
<span className="shrink-0 rounded-full border border-claw-blue/20 bg-claw-blue/8 px-2 py-0.5 text-[10px] font-label text-claw-blue">
|
||||
{t('assetLens.autoUpdated')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 md:grid-cols-3 xl:grid-cols-6">
|
||||
{assetLensRows.map(row => {
|
||||
const Icon = row.icon
|
||||
const value = (row.key === 'rag' ? governanceLoading : !knowledgeReadbackReady)
|
||||
? '--'
|
||||
: formatCount(row.count)
|
||||
return (
|
||||
<div key={row.key} className="rounded-md border border-nothing-gray-200 bg-white/70 p-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<Icon className={cn('h-4 w-4 shrink-0', row.tone)} aria-hidden="true" />
|
||||
{row.global && (
|
||||
<span className="rounded-full border border-status-healthy/20 bg-status-healthy/10 px-1.5 py-0.5 text-[9px] font-label text-status-healthy">
|
||||
{t('assetLens.global')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-2 truncate text-[10px] font-label uppercase tracking-wider text-muted">
|
||||
{t(`assetLens.${row.key}` as never)}
|
||||
</p>
|
||||
<p className={cn('mt-1 text-xl font-heading font-semibold tabular-nums', row.tone)}>
|
||||
{value}
|
||||
</p>
|
||||
<p className="mt-0.5 line-clamp-2 text-[10px] font-body leading-4 text-muted">
|
||||
{t(`assetLens.detail.${row.key}` as never)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
## 2026-07-03 — 02:40 Knowledge Base 分類矩陣與 RAG readback 修正
|
||||
|
||||
**完成內容**:
|
||||
- `/zh-TW/knowledge-base` 的載入與資料鏈路失敗狀態不再把主 KM、動態分類、總覽 metrics 與資產 lens 渲染成 misleading `0`;主 KM readback 未 ready 時改顯示 `--` / 載入狀態,避免誤判成知識庫歸零。
|
||||
- RAG stats 前端請求補上 `project_id=awoooi`,對齊 tenant context;正式 API readback 帶 project 後為 `total_chunks=5814`、`sources=6`,不再顯示成 `RAG CHUNKS 0`。
|
||||
- `資產維度` 與主畫面新增 `全域分類 / 資產矩陣`,從原本 `專案 / 產品 / 網站 / 服務 / 套件 / 工具` 擴為 `專案 / 產品 / 網站 / 服務 / 套件 / 工具 / Log / Alert / PlayBook / RAG / MCP / 排程`,讓 KM 與 AI automation 所需的 Log、Alert、PlayBook、RAG、MCP、worker 類別直接可見。
|
||||
- 分類列改為只顯示 API readback 回來的實際分類;讀取前不再用 `DEFAULT_CATEGORY_ORDER` 產生一排 `0` 分類。
|
||||
|
||||
**驗證**:
|
||||
- Production API readback:`/api/v1/knowledge?project_id=awoooi&limit=1` 回 `200`、總數約 `724/725`;`/api/v1/knowledge/categories?project_id=awoooi` 回 `200`、動態分類 `12`;`/api/v1/knowledge/rag/stats?project_id=awoooi` 回 `200`、`total_chunks=5814`、`sources=6`。
|
||||
- `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`:通過。
|
||||
- `NEXT_PUBLIC_API_URL=https://awoooi.wooo.work pnpm --dir apps/web build`:通過。
|
||||
- `pnpm --dir apps/web audit:ui-density`:通過;`/knowledge-base` fixed taxonomy signal 維持 `0`。
|
||||
- `python3.11 ops/runner/guard-gitea-runner-pressure.py --root .`、`git diff --check`:通過。
|
||||
- Local production server + fulfilled production API smoke:desktop / mobile HTTP `200`、`全域分類 / 資產矩陣`、`Log / 事件`、`Alert / 告警`、`PlayBook`、`RAG / 向量`、`MCP / Connector`、`排程 / Worker` 可見;`全部 724`、`動態分類 12`、`RAG 5,814`、`受控隊列 10` 可見;`知識條目資料鏈路異常=false`、`尚未建立任何知識條目=false`、`全部 0=false`、desktop/mobile `horizontalOverflow=false`、console error `0`。
|
||||
- 截圖證據:`/tmp/awoooi-kb-taxonomy-local-desktop-20260703.png`、`/tmp/awoooi-kb-taxonomy-local-mobile-20260703.png`。
|
||||
|
||||
**仍維持**:
|
||||
- 這是 UI/API client readback 修正與分類矩陣擴充;未寫 KM 資料、未改 production DB schema、未提高 PlayBook trust、未觸發 runtime apply。
|
||||
- 未讀 secret / token / `.env` / raw sessions / SQLite / auth;未使用 GitHub / gh;未重啟主機 / VM / Docker / K3s / DB / firewall;未 DROP / TRUNCATE / restore / prune / force push。
|
||||
|
||||
## 2026-07-03 — 01:58 P0-006 host-probe TCP timeout 收斂
|
||||
|
||||
**完成內容**:
|
||||
|
||||
Reference in New Issue
Block a user