fix: 优化界面展 及引用关系

This commit is contained in:
imeepos
2025-07-22 13:35:18 +08:00
parent cb5a137ec9
commit 98254ddc09
15 changed files with 2599 additions and 157 deletions

View File

@@ -0,0 +1,177 @@
import React, { useMemo, useState } 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;
}
/**
* 增强Markdown渲染器属性接口
*/
interface EnhancedMarkdownRendererProps {
/** 要渲染的文本内容 */
content: string;
/** Grounding元数据 */
groundingMetadata?: GroundingMetadata;
/** 是否启用角标引用 */
enableReferences?: boolean;
/** 是否启用Markdown渲染 */
enableMarkdown?: boolean;
/** 自定义样式类名 */
className?: string;
/** 引用点击回调 */
onReferenceClick?: (sources: GroundingSource[], index: number) => void;
}
/**
* 增强Markdown渲染器组件
* 支持角标引用和grounding高亮
*/
export const EnhancedMarkdownRenderer: React.FC<EnhancedMarkdownRendererProps> = ({
content,
groundingMetadata,
enableReferences = true,
enableMarkdown = true,
className = '',
onReferenceClick
}) => {
const [selectedReference, setSelectedReference] = useState<number | null>(null);
// 解析文本片段和引用信息
const textSegments = useMemo(() => {
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;
return (
<span
key={index}
className={`
${hasReference ? 'relative text-highlight' : ''}
${isHighlighted ? 'bg-blue-100 rounded px-1' : ''}
`}
>
{enableMarkdown ? (
<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>,
strong: ({ children }) => <strong>{children}</strong>,
em: ({ children }) => <em>{children}</em>,
code: ({ children }) => (
<code className="bg-gray-100 px-1 py-0.5 rounded text-sm font-mono">
{children}
</code>
),
blockquote: ({ children }) => (
<blockquote className="border-l-4 border-gray-300 pl-4 italic text-gray-600">
{children}
</blockquote>
),
}}
>
{segment.text}
</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>
);
};
export default EnhancedMarkdownRenderer;