fix: markdown render
This commit is contained in:
parent
98254ddc09
commit
31d97834dc
|
|
@ -73,6 +73,19 @@ export const EnhancedChatMessageV2: React.FC<EnhancedChatMessageV2Props> = ({
|
||||||
const groundingMetadata = message.metadata?.grounding_metadata;
|
const groundingMetadata = message.metadata?.grounding_metadata;
|
||||||
const sources = message.metadata?.sources || [];
|
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 () => {
|
const handleCopy = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,7 @@
|
||||||
import React, { useMemo, useState } from 'react';
|
import React from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import { GroundingMetadata, GroundingSupport, GroundingSource } from '../types/ragGrounding';
|
import { GroundingMetadata } from '../types/ragGrounding';
|
||||||
import { ReferenceFootnote } from './ReferenceFootnote';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文本片段与引用信息
|
|
||||||
*/
|
|
||||||
interface TextSegmentWithReference {
|
|
||||||
text: string;
|
|
||||||
startIndex: number;
|
|
||||||
endIndex: number;
|
|
||||||
groundingSupport?: GroundingSupport;
|
|
||||||
sources?: GroundingSource[];
|
|
||||||
referenceIndex?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 增强Markdown渲染器属性接口
|
* 增强Markdown渲染器属性接口
|
||||||
|
|
@ -30,108 +17,39 @@ interface EnhancedMarkdownRendererProps {
|
||||||
enableMarkdown?: boolean;
|
enableMarkdown?: boolean;
|
||||||
/** 自定义样式类名 */
|
/** 自定义样式类名 */
|
||||||
className?: string;
|
className?: string;
|
||||||
/** 引用点击回调 */
|
|
||||||
onReferenceClick?: (sources: GroundingSource[], index: number) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 增强Markdown渲染器组件
|
* 增强Markdown渲染器组件
|
||||||
* 支持角标引用和grounding高亮
|
* 暂时简化版本,专注于正确渲染Markdown内容
|
||||||
*/
|
*/
|
||||||
export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> = ({
|
export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> = ({
|
||||||
content,
|
content,
|
||||||
groundingMetadata,
|
groundingMetadata,
|
||||||
enableReferences = true,
|
enableReferences = true,
|
||||||
enableMarkdown = true,
|
enableMarkdown = true,
|
||||||
className = '',
|
className = ''
|
||||||
onReferenceClick
|
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedReference, setSelectedReference] = useState<number | null>(null);
|
|
||||||
|
|
||||||
// 解析文本片段和引用信息
|
// 调试信息
|
||||||
const textSegments = useMemo(() => {
|
console.log('📝 EnhancedMarkdownRenderer Debug:', {content, groundingMetadata});
|
||||||
if (!groundingMetadata?.grounding_supports?.length) {
|
|
||||||
return [{ text: content, startIndex: 0, endIndex: content.length }];
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
|
// 暂时禁用角标功能,直接渲染Markdown以避免重复渲染问题
|
||||||
|
// TODO: 未来需要实现更复杂的Markdown + 角标集成方案
|
||||||
return (
|
return (
|
||||||
<span
|
<div className={`enhanced-markdown-renderer ${className}`}>
|
||||||
key={index}
|
|
||||||
className={`
|
|
||||||
${hasReference ? 'relative text-highlight' : ''}
|
|
||||||
${isHighlighted ? 'bg-blue-100 rounded px-1' : ''}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{enableMarkdown ? (
|
{enableMarkdown ? (
|
||||||
|
<div className="prose prose-sm max-w-none leading-relaxed">
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
components={{
|
components={{
|
||||||
p: ({ children }) => <>{children}</>,
|
p: ({ children }) => <p className="mb-2">{children}</p>,
|
||||||
h1: ({ children }) => <strong className="text-lg font-bold">{children}</strong>,
|
h1: ({ children }) => <h1 className="text-lg font-bold mb-2">{children}</h1>,
|
||||||
h2: ({ children }) => <strong className="text-base font-semibold">{children}</strong>,
|
h2: ({ children }) => <h2 className="text-base font-semibold mb-2">{children}</h2>,
|
||||||
h3: ({ children }) => <strong className="font-medium">{children}</strong>,
|
h3: ({ children }) => <h3 className="font-medium mb-1">{children}</h3>,
|
||||||
ul: ({ children }) => <div className="ml-4">{children}</div>,
|
ul: ({ children }) => <ul className="list-disc ml-4 mb-2">{children}</ul>,
|
||||||
ol: ({ children }) => <div className="ml-4">{children}</div>,
|
ol: ({ children }) => <ol className="list-decimal ml-4 mb-2">{children}</ol>,
|
||||||
li: ({ children }) => <div className="flex items-start"><span className="mr-2">•</span><span>{children}</span></div>,
|
li: ({ children }) => <li className="mb-1">{children}</li>,
|
||||||
strong: ({ children }) => <strong>{children}</strong>,
|
strong: ({ children }) => <strong>{children}</strong>,
|
||||||
em: ({ children }) => <em>{children}</em>,
|
em: ({ children }) => <em>{children}</em>,
|
||||||
code: ({ children }) => (
|
code: ({ children }) => (
|
||||||
|
|
@ -140,36 +58,43 @@ export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> =
|
||||||
</code>
|
</code>
|
||||||
),
|
),
|
||||||
blockquote: ({ children }) => (
|
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}
|
{children}
|
||||||
</blockquote>
|
</blockquote>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{segment.text}
|
{content}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
) : (
|
|
||||||
segment.text
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 角标引用 */}
|
|
||||||
{enableReferences && hasReference && (
|
|
||||||
<ReferenceFootnote
|
|
||||||
index={segment.referenceIndex!}
|
|
||||||
sources={segment.sources!}
|
|
||||||
className="ml-1 align-super"
|
|
||||||
onClick={(sources) => handleReferenceClick(sources, segment.referenceIndex!)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`enhanced-markdown-renderer ${className}`}>
|
|
||||||
<div className="prose prose-sm max-w-none leading-relaxed">
|
|
||||||
{textSegments.map(renderSegment)}
|
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="leading-relaxed whitespace-pre-wrap">{content}</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue