import React, { useEffect, useState, useCallback } from 'react'; import { GroundingMetadata } from '../types/ragGrounding'; import { markdownService } from '../services/markdownService'; import { MarkdownParseResult, MarkdownNode, MarkdownNodeType, ValidationResult } from '../types/markdown'; /** * 增强Markdown渲染器属性接口 */ interface EnhancedMarkdownRendererProps { /** 要渲染的文本内容 */ content: string; /** Grounding元数据 */ groundingMetadata?: GroundingMetadata; /** 是否启用角标引用 */ enableReferences?: boolean; /** 是否启用Markdown渲染 */ enableMarkdown?: boolean; /** 自定义样式类名 */ className?: string; /** 是否显示解析统计信息 */ showStatistics?: boolean; /** 是否启用实时解析 */ enableRealTimeParsing?: boolean; } /** * 增强Markdown渲染器组件 * 使用基于Tree-sitter的MarkdownService替代ReactMarkdown */ export const EnhancedMarkdownRenderer: React.FC = ({ content, groundingMetadata, enableReferences = true, enableMarkdown = true, className = '', showStatistics = false, enableRealTimeParsing = false }) => { const [parseResult, setParseResult] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [validation, setValidation] = useState(null); // 解析Markdown内容 const parseContent = useCallback(async () => { if (!content.trim() || !enableMarkdown) { setParseResult(null); setValidation(null); return; } setIsLoading(true); setError(null); try { // 解析Markdown const result = await markdownService.parseMarkdown(content); console.log({ content: content.length, result: result.source_text.length }) setParseResult(result); // 验证文档结构 const validationResult = await markdownService.validateMarkdown(content); setValidation(validationResult); } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; setError(errorMessage); console.error('📝 Markdown解析失败:', err); } finally { setIsLoading(false); } }, [content, enableMarkdown]); // 实时解析效果 useEffect(() => { if (enableRealTimeParsing) { const timeoutId = setTimeout(parseContent, 300); // 防抖 return () => clearTimeout(timeoutId); } }, [content, enableRealTimeParsing, parseContent]); // 初始解析 useEffect(() => { if (!enableRealTimeParsing) { parseContent(); } }, [parseContent, enableRealTimeParsing]); // 分析节点与引用资源的关联 const analyzeNodeGrounding = useCallback((node: MarkdownNode) => { if (!groundingMetadata?.grounding_supports || !parseResult) { return null; } // 计算节点在原始文本中的字节偏移位置(与grounding数据的字节偏移匹配) const nodeStartOffset = node.range?.start?.byte_offset || 0; const nodeEndOffset = node.range?.end?.byte_offset || 0; const nodeStartOffset1 = node.range?.start?.offset || 0; const nodeEndOffset1 = node.range?.end?.offset || 0; // 查找与当前节点位置重叠的grounding支持信息 const relatedSupports = groundingMetadata.grounding_supports.filter(support => { // grounding数据使用字节偏移 const segmentStart = support.segment.startIndex; const segmentEnd = support.segment.endIndex; const hasOverlap = (nodeEndOffset <= segmentEnd && nodeStartOffset >= segmentStart); // 检查节点范围与grounding片段是否有重叠 return hasOverlap; }); console.log({ relatedSupports, nodeStartOffset, nodeEndOffset, nodeStartOffset1, nodeEndOffset1 }) if (relatedSupports.length > 0) { // 获取相关的来源信息 const relatedSources = relatedSupports.flatMap(support => support.groundingChunkIndices.map(index => groundingMetadata.sources[index]) ).filter(Boolean); const analysisResult = { node: { type: node.node_type, content: node.content.substring(0, 100) + (node.content.length > 100 ? '...' : ''), position: { start: nodeStartOffset, end: nodeEndOffset, line: node.range?.start?.line, column: node.range?.start?.column } }, groundingInfo: { supportCount: relatedSupports.length, sourceCount: relatedSources.length, sources: relatedSources.map(source => ({ title: source.title, uri: source.uri, snippet: source.content?.snippet || 'No snippet available' })), segments: relatedSupports.map(support => ({ start: support.segment.startIndex, end: support.segment.endIndex, chunkIndices: support.groundingChunkIndices })) } }; console.log('🔗 节点引用分析:', analysisResult); return analysisResult; } return null; }, [groundingMetadata, parseResult]); // 渲染单个Markdown节点 const renderNode = useCallback((node: MarkdownNode, depth: number = 0, index: number = 0): React.ReactNode => { const key = `${node.range?.start?.line || 0}-${node.range?.start?.column || 0}-${node.range?.start?.offset || 0}-${depth}-${index}`; // 分析当前节点的引用关联 const groundingAnalysis = analyzeNodeGrounding(node); // 创建引用指示器组件 const GroundingIndicator = groundingAnalysis ? ( { console.log('📚 点击查看引用详情:', groundingAnalysis); // 这里可以添加弹窗或侧边栏显示详细引用信息 }} > 📚 {groundingAnalysis.groundingInfo.sourceCount} ) : null; switch (node.node_type) { case MarkdownNodeType.Document: return (
{node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex))}
); case MarkdownNodeType.Heading: const level = parseInt(node.attributes.level || '1'); const HeadingTag = `h${Math.min(level, 6)}` as keyof JSX.IntrinsicElements; const headingClasses = { 1: 'text-lg font-bold mb-2', 2: 'text-base font-semibold mb-2', 3: 'font-medium mb-1', 4: 'font-medium mb-1 text-sm', 5: 'font-medium mb-1 text-sm', 6: 'font-medium mb-1 text-xs' }; return ( {node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex))} {GroundingIndicator} ); case MarkdownNodeType.Paragraph: return (

{node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex))} {GroundingIndicator}

); case MarkdownNodeType.List: const isOrdered = node.content.trim().match(/^\d+\./); const ListTag = isOrdered ? 'ol' : 'ul'; const listClass = isOrdered ? 'list-decimal ml-4 mb-2' : 'list-disc ml-4 mb-2'; return ( {node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex))} ); case MarkdownNodeType.ListItem: return (
  • {node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex))}
  • ); case MarkdownNodeType.CodeBlock: const language = node.attributes.language || ''; return (
                
                  {markdownService.extractTextContent(node)}
                
              
    ); case MarkdownNodeType.InlineCode: return ( {markdownService.extractTextContent(node)} ); case MarkdownNodeType.Link: const url = node.attributes.url || '#'; const title = node.attributes.title; return ( {node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex))} ); case MarkdownNodeType.Image: const src = node.attributes.src || ''; const alt = node.attributes.alt || ''; return ( {alt} ); case MarkdownNodeType.Strong: return ( {node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex))} ); case MarkdownNodeType.Emphasis: return ( {node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex))} ); case MarkdownNodeType.Blockquote: return (
    {node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex))}
    ); case MarkdownNodeType.Text: return {node.content}; case MarkdownNodeType.LineBreak: return
    ; default: return (
    {node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex))}
    ); } }, [analyzeNodeGrounding]); return (
    {/* 加载状态 */} {isLoading && (
    解析中...
    )} {/* 错误状态 */} {error && (
    解析错误
    {error}
    )} {/* Markdown内容渲染 */} {enableMarkdown && parseResult && !isLoading && !error ? (
    {renderNode(parseResult.root)}
    ) : !enableMarkdown ? (
    {content}
    ) : null} {/* 解析统计信息 */} {showStatistics && parseResult && (
    解析统计
    节点数: {parseResult.statistics.total_nodes}
    错误数: {parseResult.statistics.error_nodes}
    解析时间: {parseResult.statistics.parse_time_ms}ms
    最大深度: {parseResult.statistics.max_depth}
    )} {/* 验证结果 */} {validation && !validation.is_valid && (
    文档验证警告
    {validation.issues.slice(0, 3).map((issue, index) => (
    • {issue.message}
    ))} {validation.issues.length > 3 && (
    还有 {validation.issues.length - 3} 个问题...
    )}
    )} {/* 显示引用来源信息(如果有的话) */} {enableReferences && groundingMetadata?.sources && groundingMetadata.sources.length > 0 && (
    基于 {groundingMetadata.sources.length} 个来源的信息
    {groundingMetadata.sources.slice(0, 5).map((source, index) => ( {index + 1} ))} {groundingMetadata.sources.length > 5 && ( +{groundingMetadata.sources.length - 5} 更多 )}
    )}
    ); }; export default EnhancedMarkdownRenderer;