/* * @Author: ZhaoYing * @Date: 2026-02-03 17:30:11 * @Last Modified by: ZhaoYing * @Last Modified time: 2026-03-19 15:38:38 */ /** * Result Component * Displays real-time extraction results with progress tracking * Shows text preprocessing, knowledge extraction, node/edge creation, and deduplication */ import { type FC, useState } from 'react' import { useParams } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { Space, Button, Progress, Form, Input, Flex } from 'antd' import { ExclamationCircleFilled, LoadingOutlined } from '@ant-design/icons' import clsx from 'clsx' import type { AnyObject } from 'antd/es/_util/type'; import Card from './Card' import RbAlert from '@/components/RbAlert' import type { TestResult, OntologyCoverage } from '../types' import { pilotRunMemoryExtractionConfig } from '@/api/memory' import { type SSEMessage } from '@/utils/stream' import Tag, { type TagProps } from '@/components/Tag' import Markdown from '@/components/Markdown' import { groupDataByType } from '../constant' import Empty from '@/components/Empty' import NoDataIcon from '@/assets/images/empty/noData.png' import ResultCard from '@/components/RbCard/ResultCard' /** Result metric mapping */ const resultObj = { extractTheNumberOfEntities: 'entities.extracted_count', numberOfEntityDisambiguation: 'disambiguation.block_count', memoryFragments: 'memory.chunks', numberOfRelationalTriples: 'triplets.count' } /** * Component props */ interface ResultProps { loading: boolean; handleSave: () => void; } /** * Module processing item */ interface ModuleItem { status: 'pending' | 'processing' | 'completed' | 'failed'; data: any[], result: any, start_at?: number; end_at?: number; } /** Tag color mapping by status */ const tagColors: { [key: string]: TagProps['color'] } = { pending: 'warning', processing: 'processing', completed: 'success', failed: 'error' } /** Initial module state */ const initObj = { data: [], status: 'pending', result: null } const initialExpanded = { text_preprocessing: false, knowledge_extraction: false, creating_nodes_edges: false, deduplication: false, dataStatistics: false, entityDeduplicationImpact: false, disambiguation: false, coreEntities: false, triplet_samples: false, ontologyCoverage: false, } const Result: FC = ({ loading, handleSave }) => { const { t } = useTranslation(); const { id } = useParams() const [runLoading, setRunLoading] = useState(false) const [activeTab, setActiveTab] = useState('processData') const [testResult, setTestResult] = useState({} as TestResult) const [coreEntitiesTab, setCoreEntitiesTab] = useState(null) const [textPreprocessing, setTextPreprocessing] = useState(initObj as ModuleItem) const [textPreprocessingTab, setTextPreprocessingTab] = useState('chunking') const [knowledgeExtraction, setKnowledgeExtraction] = useState(initObj as ModuleItem) const [creatingNodesEdges, setCreatingNodesEdges] = useState(initObj as ModuleItem) const [deduplication, setDeduplication] = useState(initObj as ModuleItem) const [ontologyCoverage, setOntologyCoverage] = useState({} as OntologyCoverage) const [expandedCards, setExpandedCards] = useState>(initialExpanded) const toggleCard = (key: string) => { console.log('toggleCard', key) setExpandedCards(prev => ({ ...prev, [key]: !prev[key] })) } console.log('expandedCards', expandedCards) const [runForm] = Form.useForm() const customText = Form.useWatch(['custom_text'], runForm) /** Run pilot test */ const handleRun = () => { if(!id) return setActiveTab('processData') setCoreEntitiesTab(null) setTextPreprocessing({...initObj} as ModuleItem) setTextPreprocessingTab('chunking') setKnowledgeExtraction({...initObj} as ModuleItem) setCreatingNodesEdges({...initObj} as ModuleItem) setDeduplication({...initObj} as ModuleItem) setTestResult({} as TestResult) setExpandedCards(initialExpanded) const handleStreamMessage = (list: SSEMessage[]) => { list.forEach((data: AnyObject) => { switch(data.event) { case 'text_preprocessing': // Start text preprocessing setTextPreprocessing(prev => ({ ...prev, status: 'processing', start_at: data.data.time })) toggleCard('text_preprocessing') break case 'text_preprocessing_result': // Text preprocessing in progress setTextPreprocessing(prev => ({ ...prev, data: [...prev.data, data.data?.deleted_messages ? { deleted_messages: data.data?.deleted_messages } : data.data?.data], })) break case 'text_preprocessing_complete': // Text preprocessing complete setTextPreprocessing(prev => ({ ...prev, result: data.data?.data, status: 'completed', end_at: data.data.time })) break case 'knowledge_extraction': // Start knowledge extraction setKnowledgeExtraction(prev => ({ ...prev, status: 'processing', start_at: data.data.time })) toggleCard('knowledge_extraction') break case 'knowledge_extraction_result': // Knowledge extraction in progress setKnowledgeExtraction(prev => ({ ...prev, data: [...prev.data, data.data?.data] })) break case 'knowledge_extraction_complete': // Knowledge extraction complete setKnowledgeExtraction(prev => ({ ...prev, result: data.data?.data, status: 'completed', end_at: data.data.time })) break case 'creating_nodes_edges': // Start creating nodes and edges setCreatingNodesEdges(prev => ({ ...prev, status: 'processing', start_at: data.data.time })) toggleCard('creating_nodes_edges') break case 'creating_nodes_edges_result': // Creating nodes and edges in progress setCreatingNodesEdges(prev => ({ ...prev, data: [...prev.data, data.data?.data] })) break case 'creating_nodes_edges_complete': // Creating nodes and edges complete setCreatingNodesEdges(prev => ({ ...prev, result: data.data?.data, status: 'completed', end_at: data.data.time })) break case 'deduplication': // Start deduplication and disambiguation setDeduplication(prev => ({ ...prev, status: 'processing', start_at: data.data.time })) toggleCard('deduplication') break case 'dedup_disambiguation_result': // Deduplication and disambiguation in progress setDeduplication(prev => ({ ...prev, data: [...prev.data, data.data.data] })) break case 'dedup_disambiguation_complete': // Deduplication and disambiguation complete setDeduplication(prev => ({ ...prev, result: data.data?.data, status: 'completed', end_at: data.data.time })) break case 'generating_results': // Generating results break case 'result': // Result setTestResult(data.data?.extracted_result) setOntologyCoverage(data.data?.ontology_coverage) setExpandedCards(prev => ({ ...prev, dataStatistics: true, entityDeduplicationImpact: true, disambiguation: true, coreEntities: true, triplet_samples: true, ontologyCoverage: true, })) break } }) } setRunLoading(true) pilotRunMemoryExtractionConfig({ config_id: id, dialogue_text: t('memoryExtractionEngine.exampleText'), custom_text: runForm.getFieldValue('custom_text') }, handleStreamMessage) .finally(() => { setRunLoading(false) }) } const completedNum = [textPreprocessing, knowledgeExtraction, creatingNodesEdges, deduplication].filter(item => item.status === 'completed').length const deduplicationData = groupDataByType(deduplication.data, 'result_type') /** Format status tag */ const formatTag = (status: string) => { return ( {status === 'pending' &&
} {status === 'processing' && } {status === 'completed' &&
} {t(`memoryExtractionEngine.status.${status}`)}
) } /** Format processing time */ const formatTime = (data: ModuleItem, color?: string) => { if (typeof data.end_at === 'number' && typeof data.start_at === 'number') { return
{t('memoryExtractionEngine.time')}{data.end_at - data.start_at}ms
} return null } /** Convert first character to lowercase */ const lowercaseFirst = (str: string) => str.charAt(0).toLowerCase() + str.slice(1) return ( } > {/* } className="rb:mb-3!"> {t('memoryExtractionEngine.warning')} */}
{t('memoryExtractionEngine.custom_text')}
{customText?.length || 0}
{runLoading ? <>
{t('memoryExtractionEngine.processing')} {/* Overall Progress */}
{t('memoryExtractionEngine.overallProgress')}{`${(completedNum*100/4).toFixed(0)}%`}
: !testResult || Object.keys(testResult).length === 0 ? }> {t('memoryExtractionEngine.warning')} : }> {t('memoryExtractionEngine.success')} } {['processData', 'finalResult'].map(tab => (
setActiveTab(tab)} >{t(`memoryExtractionEngine.${tab}`)}
))}
{activeTab === 'processData' && {/* Text Preprocessing */} toggleCard('text_preprocessing')} > {expandedCards['text_preprocessing'] && textPreprocessing.data?.length > 0 && {(['chunking', ...(textPreprocessing.data.some(vo => vo.deleted_messages) ? ['pruning'] : [])] as string[]).map(type => (
setTextPreprocessingTab(type)} > {t(`memoryExtractionEngine.${type}`)}
))}
} {expandedCards['text_preprocessing'] && textPreprocessing.result &&
{formatTime(textPreprocessing)}
{t('memoryExtractionEngine.pruning_desc', { count: textPreprocessing.result.pruning.deleted_count || 0 })}, {t('memoryExtractionEngine.text_preprocessing_desc', { count: textPreprocessing.result.total_chunks })}, {t('memoryExtractionEngine.chunkerStrategy')}: {t(`memoryExtractionEngine.${lowercaseFirst(textPreprocessing.result.chunker_strategy)}`)}
} {expandedCards['text_preprocessing'] && textPreprocessing.data.map((vo, index) => { if (vo.deleted_messages && textPreprocessingTab === 'pruning') { return
{t('memoryExtractionEngine.Pruned')}
{vo.deleted_messages.map((msg: any, idx: number) => (
-{t('memoryExtractionEngine.pruning')}{idx}:
))}
} if (textPreprocessingTab === 'chunking') { return (
-{t('memoryExtractionEngine.fragment')}{vo.chunk_index}:
) } return null })}
{/* Knowledge Extraction */} toggleCard('knowledge_extraction')} > {knowledgeExtraction.result &&
{formatTime(knowledgeExtraction)}
{t('memoryExtractionEngine.knowledge_extraction_desc', { entities: knowledgeExtraction.result.entities_count, statements: knowledgeExtraction.result.statements_count, temporal_ranges_count: knowledgeExtraction.result.temporal_ranges_count, triplets: knowledgeExtraction.result.triplets_count })}
} {knowledgeExtraction.data?.length > 0 &&
    {knowledgeExtraction.data.map((vo, index) =>
  • {vo.statement}
  • )}
}
{/* Creating Entity Relationships */} toggleCard('creating_nodes_edges')} > {creatingNodesEdges.result &&
{formatTime(creatingNodesEdges)}
{t('memoryExtractionEngine.creating_nodes_edges_desc', { num: creatingNodesEdges.result.entity_entity_edges_count })}
} {creatingNodesEdges.data?.length > 0 &&
    {creatingNodesEdges.data.map((vo, index) =>
  • {vo?.result_type === 'entity_nodes_creation' ? <>{vo.type_display_name}: {vo.entity_names.join(', ')} : <>{vo?.relationship_text} }
  • )}
}
{/* Deduplication and Disambiguation */} toggleCard('deduplication')} > {deduplication.result &&
{formatTime(deduplication)}
{t('memoryExtractionEngine.deduplication_desc', { count: deduplication.result.summary.total_merges })}
} {Object.keys(deduplicationData).length > 0 &&
    {Object.keys(deduplicationData).map(key => { return deduplicationData[key].map((vo, index) => (
  • {vo.message}
  • )) })}
}
} {activeTab === 'finalResult' && {!testResult || Object.keys(testResult).length === 0 ? : null } {testResult && Object.keys(testResult).length > 0 && resultObj && Object.keys(resultObj).length > 0 && toggleCard('dataStatistics')} >
{Object.keys(resultObj).map((key, index) => { const keys = (resultObj as Record)[key].split('.') return (
{(testResult?.[keys[0] as keyof TestResult] as any)?.[keys[1]]}
{t(`memoryExtractionEngine.${key}`)}
{key === 'extractTheNumberOfEntities' && testResult.dedup ? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.dedup.total_merged_count, exact: testResult.dedup.breakdown.exact, fuzzy: testResult.dedup.breakdown.fuzzy, llm: testResult.dedup.breakdown.llm, }) : key === 'numberOfEntityDisambiguation' && testResult.disambiguation ? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.disambiguation.effects?.length, block_count: testResult.disambiguation.block_count }) : key === 'numberOfRelationalTriples' && testResult.triplets ? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.triplets.count }) : t(`memoryExtractionEngine.${key}Desc`) }
) })}
} {testResult?.dedup?.impact && testResult.dedup.impact?.length > 0 && toggleCard('entityDeduplicationImpact')} >
{t('memoryExtractionEngine.entityDeduplicationImpactDesc', { count: testResult.dedup.impact.length })}
{t('memoryExtractionEngine.identifyDuplicates')}:
    {testResult.dedup.impact.map((item, index) => (
  • {t('memoryExtractionEngine.identifyDuplicatesDesc', { ...item })}
  • ))}
} {testResult?.disambiguation && testResult.disambiguation?.effects?.length > 0 && toggleCard('disambiguation')} >
{t('memoryExtractionEngine.entityDeduplicationImpactDesc', { count: testResult.dedup.impact.length })} {testResult.disambiguation.effects.map((item, index) => (
0, })}>
{t('memoryExtractionEngine.disagreementCase')} {index + 1}:
  • {item.left.name}({item.left.type}) vs {item.right.name}({item.right.type}) → {item.result}
))}
} {testResult?.core_entities && testResult?.core_entities.length > 0 && toggleCard('coreEntities')} > {testResult.core_entities.map((item, index) => (
setCoreEntitiesTab(item.type)} > {item.type}({item.count})
))}
{testResult.core_entities.filter((item, index) => (coreEntitiesTab && item.type === coreEntitiesTab) || (!coreEntitiesTab && index === 0)).map((item, idx) => (
{item.type}({item.count})
    {item.entities.map((entity, index) => (
  • {entity}
  • ))}
))}
} {testResult?.triplet_samples && testResult?.triplet_samples.length > 0 && toggleCard('triplet_samples')} >
{t('memoryExtractionEngine.extractRelationalTriplesDesc', { count: testResult.triplet_samples.length })}
    {testResult.triplet_samples.map((item, index) => (
  • ({item.subject}, {item.predicate}, {item.object})
  • ))}
} {ontologyCoverage && Object.keys(ontologyCoverage).length > 0 && {t('memoryExtractionEngine.ontologyCoverage')}({ontologyCoverage.total_entities})} expanded={expandedCards['ontologyCoverage']} handleExpand={() => toggleCard('ontologyCoverage')} >
{(['scene_type_distribution', 'general_type_distribution', 'unmatched'] as const).map((key, idx) => { if (!ontologyCoverage[key]) return null return (
{t(`memoryExtractionEngine.${key}`)}({ontologyCoverage[key].type_count})
{t('memoryExtractionEngine.entity_total', { num: ontologyCoverage[key].entity_total })}
    {ontologyCoverage[key].types.map((type, index) => { if (!type.type || type.type === '') return null return (
  • {type.type}({type.count})
  • ) })}
) })}
}
}
) } export default Result