fix: 优化markdown解析器

This commit is contained in:
imeepos
2025-07-22 16:43:17 +08:00
parent ba2ff0a2b0
commit eb9ec73889
3 changed files with 478 additions and 58 deletions

View File

@@ -7,6 +7,8 @@ import {
MarkdownNodeType,
ValidationResult
} from '../types/markdown';
import ImageCard from './ImageCard';
import ImagePreviewModal from './ImagePreviewModal';
/**
* 增强Markdown渲染器属性接口
@@ -45,6 +47,9 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [validation, setValidation] = useState<ValidationResult | null>(null);
const [selectedGrounding, setSelectedGrounding] = useState<any>(null);
const [showGroundingModal, setShowGroundingModal] = useState(false);
const [previewSource, setPreviewSource] = useState<any>(null);
// 解析Markdown内容
const parseContent = useCallback(async () => {
@@ -93,6 +98,28 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
}
}, [parseContent, enableRealTimeParsing]);
// 显示grounding详情
const showGroundingDetails = useCallback((groundingAnalysis: any) => {
setSelectedGrounding(groundingAnalysis);
setShowGroundingModal(true);
}, []);
// 关闭grounding详情
const closeGroundingDetails = useCallback(() => {
setShowGroundingModal(false);
setSelectedGrounding(null);
}, []);
// 查看大图
const handleViewLarge = useCallback((source: any) => {
setPreviewSource(source);
}, []);
// 关闭图片预览
const handleClosePreview = useCallback(() => {
setPreviewSource(null);
}, []);
// 分析节点与引用资源的关联
const analyzeNodeGrounding = useCallback((node: MarkdownNode) => {
if (!groundingMetadata?.grounding_supports || !parseResult) {
@@ -102,31 +129,31 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
// 计算节点在原始文本中的字节偏移位置与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);
const hasOverlap = (nodeStartOffset <= segmentEnd && nodeEndOffset >= segmentStart);
// 检查节点范围与grounding片段是否有重叠
return hasOverlap;
});
console.log({
relatedSupports,
nodeStartOffset,
nodeEndOffset,
nodeStartOffset1,
nodeEndOffset1
})
if (relatedSupports.length > 0) {
// 获取相关的来源信息
console.log('🔍 Related supports:', relatedSupports);
const relatedSources = relatedSupports.flatMap(support =>
support.groundingChunkIndices.map(index => groundingMetadata.sources[index])
).filter(Boolean);
console.log('📚 Related sources:', relatedSources);
console.log('🖼️ Source content examples:', relatedSources.map(s => ({
title: s.title,
uri: s.uri,
contentType: typeof s.content,
contentKeys: s.content ? Object.keys(s.content) : null,
contentSample: s.content
})));
const analysisResult = {
node: {
type: node.node_type,
@@ -141,11 +168,39 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
groundingInfo: {
supportCount: relatedSupports.length,
sourceCount: relatedSources.length,
sources: relatedSources.map(source => ({
title: source.title,
uri: source.uri,
snippet: source.content?.snippet || 'No snippet available'
})),
sources: relatedSources.map(source => {
// 解析content字段中的图片信息
let imageData = null;
if (source.content) {
// 如果content是字符串尝试解析为JSON
if (typeof source.content === 'string') {
try {
imageData = JSON.parse(source.content);
} catch {
imageData = { description: source.content };
}
} else if (source.content.text) {
// 如果content有text字段使用text字段
try {
imageData = typeof source.content.text === 'string'
? JSON.parse(source.content.text)
: source.content.text;
} catch {
imageData = { description: source.content.text };
}
} else {
// 直接使用content对象
imageData = source.content;
}
}
return {
title: source.title,
uri: source.uri,
content: imageData,
snippet: imageData?.description || imageData?.snippet || 'No description available'
};
}),
segments: relatedSupports.map(support => ({
start: support.segment.startIndex,
end: support.segment.endIndex,
@@ -172,10 +227,11 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
title={`引用了 ${groundingAnalysis.groundingInfo.sourceCount} 个来源`}
onClick={() => {
console.log('📚 点击查看引用详情:', groundingAnalysis);
// 这里可以添加弹窗或侧边栏显示详细引用信息
// 显示详细引用信息,包括图片
showGroundingDetails(groundingAnalysis);
}}
>
📚 {groundingAnalysis.groundingInfo.sourceCount}
{groundingAnalysis.groundingInfo.sourceCount}
</span>
) : null;
@@ -201,7 +257,6 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
return (
<HeadingTag key={key} className={headingClasses[level as keyof typeof headingClasses] || headingClasses[3]}>
{node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex))}
{GroundingIndicator}
</HeadingTag>
);
@@ -209,7 +264,6 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
return (
<p key={key} className="mb-2">
{node.children.map((child, childIndex) => renderNode(child, depth + 1, childIndex))}
{GroundingIndicator}
</p>
);
@@ -297,7 +351,7 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
);
case MarkdownNodeType.Text:
return <span key={key}>{node.content}</span>;
return <span key={key}>{node.content} {GroundingIndicator}</span>;
case MarkdownNodeType.LineBreak:
return <br key={key} />;
@@ -381,8 +435,14 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
{groundingMetadata.sources.slice(0, 5).map((source, index) => (
<span
key={index}
className="inline-flex items-center px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded-full"
className="inline-flex items-center px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded-full cursor-pointer hover:bg-blue-100"
title={source.title || `来源 ${index + 1}`}
onClick={() => showGroundingDetails({
groundingInfo: {
sourceCount: 1,
sources: [source]
}
})}
>
{index + 1}
</span>
@@ -395,6 +455,57 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
</div>
</div>
)}
{/* Grounding详情模态框 */}
{showGroundingModal && selectedGrounding && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white rounded-lg shadow-xl max-w-4xl max-h-[90vh] overflow-hidden">
{/* 模态框头部 */}
<div className="flex items-center justify-between p-4 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-900"></h3>
<button
onClick={closeGroundingDetails}
className="text-gray-400 hover:text-gray-600 transition-colors"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* 模态框内容 */}
<div className="p-4 overflow-y-auto max-h-[calc(90vh-8rem)]">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{selectedGrounding.groundingInfo?.sources?.map((source: any, index: number) => (
<div key={index} className="relative">
<ImageCard
source={source}
showDetails={true}
onViewLarge={handleViewLarge}
className="shadow-lg border border-gray-200 hover:shadow-xl transition-shadow"
/>
</div>
))}
</div>
{/* 如果没有图片,显示提示信息 */}
{(!selectedGrounding.groundingInfo?.sources || selectedGrounding.groundingInfo.sources.length === 0) && (
<div className="text-center py-8 text-gray-500">
<div className="text-4xl mb-2">🖼</div>
<p></p>
</div>
)}
</div>
</div>
</div>
)}
{/* 图片预览模态框 */}
<ImagePreviewModal
isOpen={!!previewSource}
source={previewSource}
onClose={handleClosePreview}
/>
</div>
);
};