import { useState } from 'react'; import { theme } from 'antd'; import { Brain, ChevronDown, ChevronRight, AlertCircle, Database } from 'lucide-react'; import type { NodeComponentProps } from 'markstream-react'; import { useTranslation } from 'react-i18next'; import type { MemorySourceResult, MemoryRetrievedItem } from '@/lib/memoryUtils'; type MemoryRetrievalNodeData = { type: 'memory-retrieval'; content?: string; attrs?: Record | [string, string][]; loading?: boolean; }; function getAttrValue( attrs: MemoryRetrievalNodeData['attrs'], key: string, ): string | undefined { if (!attrs) return undefined; if (Array.isArray(attrs)) { const entry = attrs.find(([name]) => name === key); return entry?.[1]; } return attrs[key]; } function truncateContent(text: string, maxLen = 120): string { if (text.length <= maxLen) return text; return text.slice(0, maxLen) + '…'; } export function MemoryRetrievalNode(props: NodeComponentProps) { const { node } = props; const { token } = theme.useToken(); const { t } = useTranslation(); const [expanded, setExpanded] = useState(false); // Guard against undefined node (e.g. from malformed HTML comment prefixes in old data) if (!node) return null; const status = getAttrValue(node.attrs, 'status') ?? (node.loading ? 'searching' : 'done'); let sources: MemorySourceResult[] = []; if (node.content) { try { const parsed = JSON.parse(node.content); if (Array.isArray(parsed)) sources = parsed; } catch { // invalid JSON } } const totalItems = sources.reduce((sum, s) => sum + s.items.length, 0); // Searching state if (status === 'searching') { return (
{t('chat.memoryRetrieval.searching')}
); } // Error state if (status === 'error') { return (
{node.content || t('chat.memoryRetrieval.error')}
); } if (status === 'empty') { const reason = getAttrValue(node.attrs, 'reason'); const messageKey = reason === 'threshold_filtered' ? 'chat.memoryRetrieval.emptyThreshold' : 'chat.memoryRetrieval.empty'; const defaultValue = reason === 'threshold_filtered' ? '未找到符合检索阈值的记忆内容,可适当降低检索阈值。' : '未找到相关记忆内容。'; return (
{t(messageKey, { defaultValue })}
); } // Done state — no results if (totalItems === 0) return null; return (
{/* Header */}
setExpanded(!expanded)} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', cursor: 'pointer', backgroundColor: token.colorFillQuaternary, userSelect: 'none', }} > {t('chat.memoryRetrieval.resultsCount', { count: totalItems, defaultValue: '检索到 {{count}} 条记忆', })} {expanded ? : }
{/* Per-item overview */}
{sources.flatMap((src, si) => src.items.map((item, ii) => ( {item.id ? ( #{item.id.slice(0, 6)} ) : ( {item.document_id?.slice(0, 8) || '—'} )} {(1 / (1 + item.score)).toFixed(3)} )), )}
{/* Expanded details */} {expanded && (
{sources.map((src, si) => src.items.map((item: MemoryRetrievedItem, ii: number) => (
{t('chat.memoryRetrieval.label')} {item.id && ( #{item.id.slice(0, 8)} )} {(1 / (1 + item.score)).toFixed(4)}

{truncateContent(item.content, 200)}

)), )}
)}
); }