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

This commit is contained in:
ogt
2026-07-09 22:07:27 +08:00
parent a408972ee9
commit c4f98de3b2
3 changed files with 125 additions and 24 deletions

View File

@@ -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={() => {