fix(web): surface knowledge RAG asset gap
All checks were successful
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Successful in 1m36s
CD Pipeline / build-and-deploy (push) Successful in 5m17s
CD Pipeline / post-deploy-checks (push) Successful in 1m30s

This commit is contained in:
Your Name
2026-06-26 21:01:58 +08:00
parent cabf3c0735
commit 4309c02eb0
3 changed files with 55 additions and 7 deletions

View File

@@ -1940,7 +1940,9 @@
"ownerQueueSub": "ready {ready} / blocked {blocked}",
"writeback": "已寫回",
"writebackSafe": "只讀回讀;讀取不寫入",
"writebackUnsafe": "偵測到讀取寫入風險"
"writebackUnsafe": "偵測到讀取寫入風險",
"ragIndex": "RAG 索引",
"ragIndexSub": "sources {sources}0 代表語意檢索尚未沉澱"
}
},
"overview": {
@@ -1978,6 +1980,10 @@
"verifier": {
"title": "Verifier 回寫",
"detail": "已批准或完成驗證 {ready} 筆;待 負責人審查 {pending} 筆。"
},
"rag": {
"title": "RAG / 向量索引",
"detail": "索引 chunks {ready};來源 {pending}。0 代表 KM 尚未進入語意檢索資產。"
}
}
},

View File

@@ -1940,7 +1940,9 @@
"ownerQueueSub": "ready {ready} / blocked {blocked}",
"writeback": "已寫回",
"writebackSafe": "只讀回讀;讀取不寫入",
"writebackUnsafe": "偵測到讀取寫入風險"
"writebackUnsafe": "偵測到讀取寫入風險",
"ragIndex": "RAG 索引",
"ragIndexSub": "sources {sources}0 代表語意檢索尚未沉澱"
}
},
"overview": {
@@ -1978,6 +1980,10 @@
"verifier": {
"title": "Verifier 回寫",
"detail": "已批准或完成驗證 {ready} 筆;待 負責人審查 {pending} 筆。"
},
"rag": {
"title": "RAG / 向量索引",
"detail": "索引 chunks {ready};來源 {pending}。0 代表 KM 尚未進入語意檢索資產。"
}
}
},

View File

@@ -116,14 +116,20 @@ interface KnowledgeStaleOwnerReviewCompletionQueueResponse {
batch_writes_allowed: boolean
}
interface KnowledgeRagStatsResponse {
total_chunks?: number
sources?: number
}
interface KnowledgeGovernanceTelemetry {
staleCandidates: KnowledgeStaleCandidatesResponse | null
ownerReviews: KnowledgeStaleOwnerReviewInboxResponse | null
burnDown: KnowledgeStaleOwnerReviewBurnDownResponse | null
completionQueue: KnowledgeStaleOwnerReviewCompletionQueueResponse | null
ragStats: KnowledgeRagStatsResponse | null
}
type AutomationAssetKey = 'km' | 'playbook' | 'script' | 'monitoring' | 'verifier'
type AutomationAssetKey = 'km' | 'playbook' | 'script' | 'monitoring' | 'verifier' | 'rag'
// =============================================================================
// Category Config
@@ -301,6 +307,7 @@ export default function KnowledgeBasePage({
ownerReviews: null,
burnDown: null,
completionQueue: null,
ragStats: null,
})
const [governanceLoading, setGovernanceLoading] = useState(true)
@@ -360,7 +367,7 @@ export default function KnowledgeBasePage({
setGovernanceLoading(true)
try {
const apiBase = process.env.NEXT_PUBLIC_API_URL ?? ''
const [staleCandidates, ownerReviews, burnDown, completionQueue] = await Promise.all([
const [staleCandidates, ownerReviews, burnDown, completionQueue, ragStats] = await Promise.all([
fetch(`${apiBase}/api/v1/ai/governance/km-stale-candidates?project_id=awoooi&limit=5`)
.then(res => res.ok ? res.json() : null)
.catch(() => null),
@@ -373,6 +380,9 @@ 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`)
.then(res => res.ok ? res.json() : null)
.catch(() => null),
])
setGovernanceTelemetry({
@@ -380,6 +390,7 @@ export default function KnowledgeBasePage({
ownerReviews,
burnDown,
completionQueue,
ragStats,
})
} catch (err) {
console.error('Failed to fetch knowledge governance telemetry', err)
@@ -615,6 +626,8 @@ export default function KnowledgeBasePage({
entry.status === 'approved'
|| hasTag(entry, ['human_approved', 'postmortem']),
)
const ragChunks = governanceTelemetry.ragStats?.total_chunks ?? 0
const ragSources = governanceTelemetry.ragStats?.sources ?? 0
const ownerPending =
governanceTelemetry.ownerReviews?.total
?? governanceTelemetry.completionQueue?.pending_count
@@ -666,6 +679,16 @@ export default function KnowledgeBasePage({
pct: pct(verifierReady),
tone: 'border-status-healthy/20 bg-status-healthy/10 text-status-healthy',
},
{
key: 'rag' as AutomationAssetKey,
icon: FileSearch,
ready: ragChunks,
pending: ragSources,
pct: ragChunks > 0 ? 100 : 0,
tone: ragChunks > 0
? 'border-status-healthy/20 bg-status-healthy/10 text-status-healthy'
: 'border-status-critical/25 bg-status-critical/10 text-status-critical',
},
] as const
}, [displayedEntries, governanceTelemetry])
@@ -686,6 +709,8 @@ export default function KnowledgeBasePage({
return t('assetLedger.asset.monitoring.detail', values)
case 'verifier':
return t('assetLedger.asset.verifier.detail', values)
case 'rag':
return t('assetLedger.asset.rag.detail', values)
}
},
[formatCount, t],
@@ -809,6 +834,8 @@ export default function KnowledgeBasePage({
const thresholdText = governanceSummary.threshold === null ? '--' : `${governanceSummary.threshold}%`
const staleTotalValue = governanceSummary.staleTotal === null ? '--' : formatCount(governanceSummary.staleTotal)
const entriesToThreshold = governanceSummary.entriesToThreshold === null ? '--' : formatCount(governanceSummary.entriesToThreshold)
const ragChunks = governanceTelemetry.ragStats?.total_chunks ?? 0
const ragSources = governanceTelemetry.ragStats?.sources ?? 0
return [
{
@@ -848,8 +875,17 @@ export default function KnowledgeBasePage({
? 'border-status-critical/25 bg-status-critical/10 text-status-critical'
: 'border-status-healthy/25 bg-status-healthy/10 text-status-healthy',
},
{
key: 'ragIndex',
icon: FileSearch,
value: formatCount(ragChunks),
sub: t('decision.card.ragIndexSub', { sources: formatCount(ragSources) }),
tone: ragChunks > 0
? 'border-status-healthy/25 bg-status-healthy/10 text-status-healthy'
: 'border-status-critical/25 bg-status-critical/10 text-status-critical',
},
] as const
}, [formatCount, governanceSummary, t])
}, [formatCount, governanceSummary, governanceTelemetry.ragStats, t])
const content = (
<div className="flex min-h-[calc(100vh-64px)] flex-col lg:h-[calc(100vh-64px)] lg:flex-row">
@@ -1020,7 +1056,7 @@ export default function KnowledgeBasePage({
<ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
</Link>
</div>
<div className="mt-3 grid grid-cols-2 gap-2 xl:grid-cols-4">
<div className="mt-3 grid grid-cols-2 gap-2 xl:grid-cols-5">
{governanceDecisionCards.map(card => {
const Icon = card.icon
return (
@@ -1095,7 +1131,7 @@ export default function KnowledgeBasePage({
{t('assetLedger.readOnly')}
</span>
</div>
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-5">
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-6">
{automationAssetRows.map(row => {
const Icon = row.icon
return (