fix(km): recover primary knowledge readback via direct fallback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 1m36s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 1m36s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
This commit is contained in:
@@ -64,6 +64,7 @@ interface ListResponse {
|
||||
categories: CategoryCount[]
|
||||
asset_taxonomy?: AssetTaxonomyCount[]
|
||||
readback_status?: 'ready' | 'degraded' | string
|
||||
primary_readback_ready?: boolean
|
||||
degraded_reason_code?: string | null
|
||||
operator_stage?: string | null
|
||||
next_step?: string | null
|
||||
@@ -441,8 +442,12 @@ const humanizeSignalTag = (tag: string) => {
|
||||
const compactReadbackToken = (value: string | null | undefined) => {
|
||||
if (!value) return '--'
|
||||
if (value === 'primary_km_db_timeout_or_pool_exhausted') return 'DB pool'
|
||||
if (value === 'ready_direct_connection_after_session_timeout') return 'Direct KM'
|
||||
if (value === 'knowledge_readback_direct_connection_recovered') return '已救回'
|
||||
if (value === 'repair_session_pool_readback_without_hiding_primary_km') return '修復 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 === 'source_backed_db_empty_direct_connection') return 'Direct 空值'
|
||||
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'
|
||||
@@ -509,7 +514,7 @@ export default function KnowledgeBasePage({
|
||||
const [assetTaxonomy, setAssetTaxonomy] = useState<AssetTaxonomyCount[]>(ASSET_TAXONOMY_FALLBACK_COUNTS)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [entryFetchError, setEntryFetchError] = useState<string | null>(null)
|
||||
const [entryReadback, setEntryReadback] = useState<Pick<ListResponse, 'readback_status' | 'degraded_reason_code' | 'operator_stage' | 'next_step'> | null>(null)
|
||||
const [entryReadback, setEntryReadback] = useState<Pick<ListResponse, 'readback_status' | 'primary_readback_ready' | 'degraded_reason_code' | 'operator_stage' | 'next_step'> | null>(null)
|
||||
const [governanceTelemetry, setGovernanceTelemetry] = useState<KnowledgeGovernanceTelemetry>({
|
||||
staleCandidates: null,
|
||||
ownerReviews: null,
|
||||
@@ -554,6 +559,7 @@ export default function KnowledgeBasePage({
|
||||
setAssetTaxonomy(data.asset_taxonomy?.length ? data.asset_taxonomy : ASSET_TAXONOMY_FALLBACK_COUNTS)
|
||||
setEntryReadback({
|
||||
readback_status: data.readback_status ?? 'ready',
|
||||
primary_readback_ready: data.primary_readback_ready ?? false,
|
||||
degraded_reason_code: data.degraded_reason_code ?? null,
|
||||
operator_stage: data.operator_stage ?? null,
|
||||
next_step: data.next_step ?? null,
|
||||
@@ -701,10 +707,16 @@ export default function KnowledgeBasePage({
|
||||
const totalCount = categories.reduce((sum, c) => sum + c.count, 0)
|
||||
const isKnowledgeListUnavailable = Boolean(entryFetchError)
|
||||
const readbackStatus = entryReadback?.readback_status ?? null
|
||||
const isKnowledgeDirectRecovered = readbackStatus === 'ready_direct_connection_after_session_timeout'
|
||||
const isKnowledgeSourceBacked = Boolean(readbackStatus?.startsWith('source_backed'))
|
||||
const isKnowledgeListDegraded = Boolean(readbackStatus && readbackStatus !== 'ready')
|
||||
const isKnowledgeListDegraded = Boolean(readbackStatus && readbackStatus !== 'ready' && !isKnowledgeDirectRecovered)
|
||||
const hasKnowledgePayload = entries.length > 0 || total > 0 || categories.some(category => category.count > 0)
|
||||
const knowledgeReadbackReady = !loading && !isKnowledgeListUnavailable && (!isKnowledgeListDegraded || hasKnowledgePayload)
|
||||
const showKnowledgeReadbackNotice = Boolean(entryFetchError || isKnowledgeListDegraded || isKnowledgeDirectRecovered)
|
||||
const readbackNoticeTone = isKnowledgeDirectRecovered
|
||||
? 'border-status-healthy/20 bg-status-healthy/5'
|
||||
: 'border-status-warning/20 bg-status-warning/5'
|
||||
const readbackNoticeTitleTone = isKnowledgeDirectRecovered ? 'text-status-healthy' : 'text-status-warning'
|
||||
const localeCode = params.locale === 'en' ? 'en-US' : 'zh-TW'
|
||||
const formatCount = useCallback(
|
||||
(value: number) => value.toLocaleString(localeCode),
|
||||
@@ -714,7 +726,11 @@ export default function KnowledgeBasePage({
|
||||
() => [
|
||||
{
|
||||
label: t('dataChain.chip.mode'),
|
||||
value: isKnowledgeSourceBacked ? t('dataChain.mode.sourceBacked') : compactReadbackToken(readbackStatus),
|
||||
value: isKnowledgeSourceBacked
|
||||
? t('dataChain.mode.sourceBacked')
|
||||
: isKnowledgeDirectRecovered
|
||||
? t('dataChain.mode.directRecovered')
|
||||
: compactReadbackToken(readbackStatus),
|
||||
icon: CheckCircle2,
|
||||
tone: 'border-status-healthy/20 bg-status-healthy/10 text-status-healthy',
|
||||
},
|
||||
@@ -757,6 +773,7 @@ export default function KnowledgeBasePage({
|
||||
entryReadback?.operator_stage,
|
||||
formatCount,
|
||||
isKnowledgeSourceBacked,
|
||||
isKnowledgeDirectRecovered,
|
||||
readbackStatus,
|
||||
t,
|
||||
total,
|
||||
@@ -1485,27 +1502,31 @@ export default function KnowledgeBasePage({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{(entryFetchError || isKnowledgeListDegraded) && (
|
||||
{showKnowledgeReadbackNotice && (
|
||||
<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={cn('flex flex-col gap-2 rounded-md border px-3 py-2 xl:flex-row xl:items-center xl:justify-between', readbackNoticeTone)}>
|
||||
<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">
|
||||
<div className={cn('flex items-center gap-2 text-xs font-label', readbackNoticeTitleTone)}>
|
||||
<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')}
|
||||
: isKnowledgeDirectRecovered
|
||||
? t('dataChain.directRecoveredTitle')
|
||||
: isKnowledgeSourceBacked
|
||||
? t('dataChain.sourceBackedTitle')
|
||||
: t('dataChain.degradedTitle')}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 truncate text-[11px] font-body text-muted">
|
||||
{entryFetchError
|
||||
? t('dataChain.errorDescription', { reason: entryFetchError })
|
||||
: isKnowledgeSourceBacked
|
||||
? t('dataChain.sourceBackedSummary')
|
||||
: t('dataChain.degradedSummary')}
|
||||
: isKnowledgeDirectRecovered
|
||||
? t('dataChain.directRecoveredSummary')
|
||||
: isKnowledgeSourceBacked
|
||||
? t('dataChain.sourceBackedSummary')
|
||||
: t('dataChain.degradedSummary')}
|
||||
</p>
|
||||
</div>
|
||||
{!entryFetchError && (
|
||||
|
||||
Reference in New Issue
Block a user