fix(km): compact knowledge readback cockpit UI
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
This commit is contained in:
@@ -2220,9 +2220,22 @@
|
||||
"errorTitle": "知識條目入口需重讀",
|
||||
"errorDescription": "知識條目入口回應:{reason}。頁面會保留 AI source-backed KM、Hermes 受控覆核與陳舊 KM 狀態,避免誤判成知識庫歸零。",
|
||||
"sourceBackedTitle": "AI source-backed KM 已接管",
|
||||
"sourceBackedSummary": "資料未歸零",
|
||||
"sourceBackedDescription": "Primary KM DB readback 正在受控修復;目前顯示 source-backed 條目、分類與資產 taxonomy,不是知識庫歸零。原因:{reason};階段:{stage};下一步:{next}。",
|
||||
"degradedTitle": "知識條目讀取降級",
|
||||
"degradedSummary": "Primary readback 降級;列表保留可用資料。",
|
||||
"degradedDescription": "主知識條目 API 已回 degraded:{stage};下一步:{next}。目前列表不視為真實歸零,AI 受控修復隊列與 RAG / PlayBook readback 仍會保留。",
|
||||
"mode": {
|
||||
"sourceBacked": "接管"
|
||||
},
|
||||
"chip": {
|
||||
"mode": "模式",
|
||||
"reason": "原因",
|
||||
"stage": "階段",
|
||||
"next": "下一步",
|
||||
"entries": "條目",
|
||||
"taxonomy": "分類 / 資產"
|
||||
},
|
||||
"retry": "重新讀取"
|
||||
},
|
||||
"decision": {
|
||||
|
||||
@@ -2220,9 +2220,22 @@
|
||||
"errorTitle": "知識條目入口需重讀",
|
||||
"errorDescription": "知識條目入口回應:{reason}。頁面會保留 AI source-backed KM、Hermes 受控覆核與陳舊 KM 狀態,避免誤判成知識庫歸零。",
|
||||
"sourceBackedTitle": "AI source-backed KM 已接管",
|
||||
"sourceBackedSummary": "資料未歸零",
|
||||
"sourceBackedDescription": "Primary KM DB readback 正在受控修復;目前顯示 source-backed 條目、分類與資產 taxonomy,不是知識庫歸零。原因:{reason};階段:{stage};下一步:{next}。",
|
||||
"degradedTitle": "知識條目讀取降級",
|
||||
"degradedSummary": "Primary readback 降級;列表保留可用資料。",
|
||||
"degradedDescription": "主知識條目 API 已回 degraded:{stage};下一步:{next}。目前列表不視為真實歸零,AI 受控修復隊列與 RAG / PlayBook readback 仍會保留。",
|
||||
"mode": {
|
||||
"sourceBacked": "接管"
|
||||
},
|
||||
"chip": {
|
||||
"mode": "模式",
|
||||
"reason": "原因",
|
||||
"stage": "階段",
|
||||
"next": "下一步",
|
||||
"entries": "條目",
|
||||
"taxonomy": "分類 / 資產"
|
||||
},
|
||||
"retry": "重新讀取"
|
||||
},
|
||||
"decision": {
|
||||
|
||||
@@ -438,6 +438,18 @@ const humanizeSignalTag = (tag: string) => {
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
const compactReadbackToken = (value: string | null | undefined) => {
|
||||
if (!value) return '--'
|
||||
if (value === 'primary_km_db_timeout_or_pool_exhausted') return 'DB pool'
|
||||
if (value === 'knowledge_readback_source_backed_ai_controlled_repair') return '接管'
|
||||
if (value === 'repair_primary_km_db_readback_then_promote_source_backed_receipts_to_persistent_km') return '修復 KM'
|
||||
if (value === 'primary_km_taxonomy_readback_degraded') return 'Taxonomy 降級'
|
||||
if (value === 'knowledge_primary_entries_ready_taxonomy_degraded') return 'Primary 可讀'
|
||||
if (value === 'repair_primary_km_taxonomy_readback_without_hiding_entries') return '修復 taxonomy'
|
||||
if (value.startsWith('source_backed')) return '接管'
|
||||
return humanizeSignalTag(value)
|
||||
}
|
||||
|
||||
const getSignalTags = (entry: KnowledgeEntry) => {
|
||||
const seen = new Set<string>()
|
||||
return entry.tags
|
||||
@@ -698,6 +710,58 @@ export default function KnowledgeBasePage({
|
||||
(value: number) => value.toLocaleString(localeCode),
|
||||
[localeCode],
|
||||
)
|
||||
const readbackNoticeChips = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: t('dataChain.chip.mode'),
|
||||
value: isKnowledgeSourceBacked ? t('dataChain.mode.sourceBacked') : compactReadbackToken(readbackStatus),
|
||||
icon: CheckCircle2,
|
||||
tone: 'border-status-healthy/20 bg-status-healthy/10 text-status-healthy',
|
||||
},
|
||||
{
|
||||
label: t('dataChain.chip.reason'),
|
||||
value: compactReadbackToken(entryReadback?.degraded_reason_code),
|
||||
icon: Database,
|
||||
tone: 'border-status-warning/20 bg-status-warning/10 text-status-warning',
|
||||
},
|
||||
{
|
||||
label: t('dataChain.chip.stage'),
|
||||
value: compactReadbackToken(entryReadback?.operator_stage),
|
||||
icon: PlayCircle,
|
||||
tone: 'border-claw-blue/20 bg-claw-blue/8 text-claw-blue',
|
||||
},
|
||||
{
|
||||
label: t('dataChain.chip.next'),
|
||||
value: compactReadbackToken(entryReadback?.next_step),
|
||||
icon: ArrowRight,
|
||||
tone: 'border-nothing-gray-200 bg-white text-secondary',
|
||||
},
|
||||
{
|
||||
label: t('dataChain.chip.entries'),
|
||||
value: formatCount(total),
|
||||
icon: BookOpen,
|
||||
tone: 'border-claw-blue/20 bg-white text-claw-blue',
|
||||
},
|
||||
{
|
||||
label: t('dataChain.chip.taxonomy'),
|
||||
value: `${formatCount(categories.length)} / ${formatCount(assetTaxonomy.length)}`,
|
||||
icon: Tag,
|
||||
tone: 'border-purple-200 bg-purple-50 text-purple-600',
|
||||
},
|
||||
],
|
||||
[
|
||||
assetTaxonomy.length,
|
||||
categories.length,
|
||||
entryReadback?.degraded_reason_code,
|
||||
entryReadback?.next_step,
|
||||
entryReadback?.operator_stage,
|
||||
formatCount,
|
||||
isKnowledgeSourceBacked,
|
||||
readbackStatus,
|
||||
t,
|
||||
total,
|
||||
],
|
||||
)
|
||||
const formatCategoryLabel = useCallback(
|
||||
(category: string) => {
|
||||
if (KNOWN_CATEGORY_KEYS.has(category)) {
|
||||
@@ -1422,33 +1486,44 @@ export default function KnowledgeBasePage({
|
||||
</div>
|
||||
|
||||
{(entryFetchError || isKnowledgeListDegraded) && (
|
||||
<div className="border-b border-status-warning/20 bg-status-warning/10 px-4 py-3">
|
||||
<div className="flex flex-col gap-2 rounded-md border border-status-warning/25 bg-white/75 px-3 py-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 text-xs font-label text-status-warning">
|
||||
<TriangleAlert className="h-4 w-4 shrink-0" aria-hidden="true" />
|
||||
<span>
|
||||
<div className="border-b border-status-warning/20 bg-white px-4 py-2">
|
||||
<div className="flex flex-col gap-2 rounded-md border border-status-warning/20 bg-status-warning/5 px-3 py-2 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div className="grid min-w-0 flex-1 gap-2 xl:grid-cols-[minmax(220px,0.8fr)_minmax(0,2fr)] xl:items-center">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 text-xs font-label text-status-warning">
|
||||
<TriangleAlert className="h-4 w-4 shrink-0" aria-hidden="true" />
|
||||
<span className="truncate">
|
||||
{entryFetchError
|
||||
? t('dataChain.errorTitle')
|
||||
: isKnowledgeSourceBacked
|
||||
? t('dataChain.sourceBackedTitle')
|
||||
: t('dataChain.degradedTitle')}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 truncate text-[11px] font-body text-muted">
|
||||
{entryFetchError
|
||||
? t('dataChain.errorTitle')
|
||||
? t('dataChain.errorDescription', { reason: entryFetchError })
|
||||
: isKnowledgeSourceBacked
|
||||
? t('dataChain.sourceBackedTitle')
|
||||
: t('dataChain.degradedTitle')}
|
||||
</span>
|
||||
? t('dataChain.sourceBackedSummary')
|
||||
: t('dataChain.degradedSummary')}
|
||||
</p>
|
||||
</div>
|
||||
<p className="mt-1 text-xs font-body leading-5 text-secondary">
|
||||
{entryFetchError
|
||||
? t('dataChain.errorDescription', { reason: entryFetchError })
|
||||
: isKnowledgeSourceBacked
|
||||
? t('dataChain.sourceBackedDescription', {
|
||||
reason: entryReadback?.degraded_reason_code ?? '--',
|
||||
stage: entryReadback?.operator_stage ?? '--',
|
||||
next: entryReadback?.next_step ?? '--',
|
||||
})
|
||||
: t('dataChain.degradedDescription', {
|
||||
stage: entryReadback?.operator_stage ?? '--',
|
||||
next: entryReadback?.next_step ?? '--',
|
||||
})}
|
||||
</p>
|
||||
{!entryFetchError && (
|
||||
<div className="grid grid-cols-2 gap-1.5 sm:grid-cols-3 xl:grid-cols-6">
|
||||
{readbackNoticeChips.map(chip => {
|
||||
const Icon = chip.icon
|
||||
return (
|
||||
<div key={chip.label} className={cn('min-w-0 rounded-md border px-2 py-1.5', chip.tone)}>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Icon className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
|
||||
<span className="truncate text-[9px] font-label uppercase tracking-wider opacity-75">{chip.label}</span>
|
||||
</div>
|
||||
<div className="mt-1 truncate text-xs font-heading font-semibold tabular-nums">{chip.value}</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
|
||||
Reference in New Issue
Block a user