fix: markdown render

This commit is contained in:
imeepos 2025-07-22 13:53:25 +08:00
parent 98254ddc09
commit 31d97834dc
2 changed files with 62 additions and 124 deletions

View File

@ -73,6 +73,19 @@ export const EnhancedChatMessageV2: React.FC<EnhancedChatMessageV2Props> = ({
const groundingMetadata = message.metadata?.grounding_metadata;
const sources = message.metadata?.sources || [];
// 调试信息
console.log('🔍 EnhancedChatMessageV2 Debug:', {
messageId: message.id,
messageType: message.type,
contentLength: message.content.length,
hasMetadata: !!message.metadata,
hasGroundingMetadata: !!groundingMetadata,
groundingSupportsCount: groundingMetadata?.grounding_supports?.length || 0,
sourcesCount: sources.length,
enableReferences,
groundingMetadata
});
// 复制消息内容
const handleCopy = useCallback(async () => {
try {

View File

@ -1,20 +1,7 @@
import React, { useMemo, useState } from 'react';
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { GroundingMetadata, GroundingSupport, GroundingSource } from '../types/ragGrounding';
import { ReferenceFootnote } from './ReferenceFootnote';
/**
*
*/
interface TextSegmentWithReference {
text: string;
startIndex: number;
endIndex: number;
groundingSupport?: GroundingSupport;
sources?: GroundingSource[];
referenceIndex?: number;
}
import { GroundingMetadata } from '../types/ragGrounding';
/**
* Markdown渲染器属性接口
@ -30,108 +17,39 @@ interface EnhancedMarkdownRendererProps {
enableMarkdown?: boolean;
/** 自定义样式类名 */
className?: string;
/** 引用点击回调 */
onReferenceClick?: (sources: GroundingSource[], index: number) => void;
}
/**
* Markdown渲染器组件
* grounding高亮
* Markdown内容
*/
export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> = ({
content,
groundingMetadata,
enableReferences = true,
enableMarkdown = true,
className = '',
onReferenceClick
className = ''
}) => {
const [selectedReference, setSelectedReference] = useState<number | null>(null);
// 解析文本片段和引用信息
const textSegments = useMemo(() => {
if (!groundingMetadata?.grounding_supports?.length) {
return [{ text: content, startIndex: 0, endIndex: content.length }];
}
// 调试信息
console.log('📝 EnhancedMarkdownRenderer Debug:', {content, groundingMetadata});
const segments: TextSegmentWithReference[] = [];
const supports = [...groundingMetadata.grounding_supports].sort(
(a, b) => a.start_index - b.start_index
);
let currentIndex = 0;
let referenceCounter = 1;
supports.forEach((support) => {
// 添加支持前的文本
if (currentIndex < support.start_index) {
segments.push({
text: content.slice(currentIndex, support.start_index),
startIndex: currentIndex,
endIndex: support.start_index
});
}
// 获取相关来源
const sources = groundingMetadata.sources?.filter(source =>
support.grounding_chunk_indices?.includes(source.chunk_index || 0)
) || [];
// 添加支持的文本片段
segments.push({
text: content.slice(support.start_index, support.end_index),
startIndex: support.start_index,
endIndex: support.end_index,
groundingSupport: support,
sources,
referenceIndex: sources.length > 0 ? referenceCounter++ : undefined
});
currentIndex = support.end_index;
});
// 添加剩余文本
if (currentIndex < content.length) {
segments.push({
text: content.slice(currentIndex),
startIndex: currentIndex,
endIndex: content.length
});
}
return segments;
}, [content, groundingMetadata]);
// 处理引用点击
const handleReferenceClick = (sources: GroundingSource[], index: number) => {
setSelectedReference(selectedReference === index ? null : index);
onReferenceClick?.(sources, index);
};
// 渲染文本片段
const renderSegment = (segment: TextSegmentWithReference, index: number) => {
const hasReference = segment.referenceIndex && segment.sources?.length;
const isHighlighted = hasReference && selectedReference === segment.referenceIndex;
return (
<span
key={index}
className={`
${hasReference ? 'relative text-highlight' : ''}
${isHighlighted ? 'bg-blue-100 rounded px-1' : ''}
`}
>
{enableMarkdown ? (
// 暂时禁用角标功能直接渲染Markdown以避免重复渲染问题
// TODO: 未来需要实现更复杂的Markdown + 角标集成方案
return (
<div className={`enhanced-markdown-renderer ${className}`}>
{enableMarkdown ? (
<div className="prose prose-sm max-w-none leading-relaxed">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
p: ({ children }) => <>{children}</>,
h1: ({ children }) => <strong className="text-lg font-bold">{children}</strong>,
h2: ({ children }) => <strong className="text-base font-semibold">{children}</strong>,
h3: ({ children }) => <strong className="font-medium">{children}</strong>,
ul: ({ children }) => <div className="ml-4">{children}</div>,
ol: ({ children }) => <div className="ml-4">{children}</div>,
li: ({ children }) => <div className="flex items-start"><span className="mr-2"></span><span>{children}</span></div>,
p: ({ children }) => <p className="mb-2">{children}</p>,
h1: ({ children }) => <h1 className="text-lg font-bold mb-2">{children}</h1>,
h2: ({ children }) => <h2 className="text-base font-semibold mb-2">{children}</h2>,
h3: ({ children }) => <h3 className="font-medium mb-1">{children}</h3>,
ul: ({ children }) => <ul className="list-disc ml-4 mb-2">{children}</ul>,
ol: ({ children }) => <ol className="list-decimal ml-4 mb-2">{children}</ol>,
li: ({ children }) => <li className="mb-1">{children}</li>,
strong: ({ children }) => <strong>{children}</strong>,
em: ({ children }) => <em>{children}</em>,
code: ({ children }) => (
@ -140,36 +58,43 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
</code>
),
blockquote: ({ children }) => (
<blockquote className="border-l-4 border-gray-300 pl-4 italic text-gray-600">
<blockquote className="border-l-4 border-gray-300 pl-4 italic text-gray-600 mb-2">
{children}
</blockquote>
),
}}
>
{segment.text}
{content}
</ReactMarkdown>
) : (
segment.text
)}
{/* 角标引用 */}
{enableReferences && hasReference && (
<ReferenceFootnote
index={segment.referenceIndex!}
sources={segment.sources!}
className="ml-1 align-super"
onClick={(sources) => handleReferenceClick(sources, segment.referenceIndex!)}
/>
)}
</span>
);
};
</div>
) : (
<div className="leading-relaxed whitespace-pre-wrap">{content}</div>
)}
return (
<div className={`enhanced-markdown-renderer ${className}`}>
<div className="prose prose-sm max-w-none leading-relaxed">
{textSegments.map(renderSegment)}
</div>
{/* 显示引用来源信息(如果有的话) */}
{enableReferences && groundingMetadata?.sources && groundingMetadata.sources.length > 0 && (
<div className="mt-4 pt-3 border-t border-gray-200">
<div className="text-xs text-gray-500 mb-2">
{groundingMetadata.sources.length}
</div>
<div className="flex flex-wrap gap-1">
{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"
title={source.title || `来源 ${index + 1}`}
>
{index + 1}
</span>
))}
{groundingMetadata.sources.length > 5 && (
<span className="text-xs text-gray-400">
+{groundingMetadata.sources.length - 5}
</span>
)}
</div>
</div>
)}
</div>
);
};