feat(ui): support markdown table rendering in evaluation field values

ReactTableTooltip now detects and renders markdown tables (| delimited)
in addition to existing tab-delimited tables. Table content is rendered
directly as an HTML table instead of showing raw markdown text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 19:19:53 +08:00
parent ebcaf05625
commit d0124b0121
+87 -14
View File
@@ -341,9 +341,12 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
const [showTooltip, setShowTooltip] = useState(false); const [showTooltip, setShowTooltip] = useState(false);
const [renderedContent, setRenderedContent] = useState<React.ReactNode>(null); const [renderedContent, setRenderedContent] = useState<React.ReactNode>(null);
const textRef = useRef<HTMLDivElement>(null); const textRef = useRef<HTMLDivElement>(null);
const isTableLike = content.includes('\t') && content.includes('\n'); const isTabTable = content.includes('\t') && content.includes('\n');
// 检测 markdown 表格:至少有 | 分隔符和分隔行 |---|
const isMdTable = content.includes('|') && /\|[-\s:]+\|/.test(content);
const isTableLike = isTabTable || isMdTable;
useEffect(() => { useEffect(() => {
const checkTextOverflow = () => { const checkTextOverflow = () => {
const element = textRef.current; const element = textRef.current;
@@ -352,33 +355,94 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
setShowTooltip(isTableLike || element.scrollHeight > element.clientHeight); setShowTooltip(isTableLike || element.scrollHeight > element.clientHeight);
} }
}; };
// 预渲染内容并缓存 // 预渲染内容并缓存
if (isTableLike) { if (isMdTable) {
setRenderedContent(renderMarkdownTable(content));
} else if (isTabTable) {
setRenderedContent(renderReactTable(content)); setRenderedContent(renderReactTable(content));
} else { } else {
setRenderedContent(content); setRenderedContent(content);
} }
requestAnimationFrame(checkTextOverflow); requestAnimationFrame(checkTextOverflow);
window.addEventListener('resize', checkTextOverflow); window.addEventListener('resize', checkTextOverflow);
return () => { return () => {
window.removeEventListener('resize', checkTextOverflow); window.removeEventListener('resize', checkTextOverflow);
}; };
}, [content, isTableLike]); }, [content, isTableLike]);
// 解析表格数据 // 解析表格数据tab分隔)
const parseTableData = (text: string) => { const parseTableData = (text: string) => {
const rows = text.split('\n').map(row => row.split('\t')); const rows = text.split('\n').map(row => row.split('\t'));
return rows; return rows;
}; };
// 渲染React表格 // 解析 markdown 表格
const parseMarkdownTable = (text: string): string[][] => {
const lines = text.split('\n').filter(l => l.trim());
const rows: string[][] = [];
for (const line of lines) {
// 跳过分隔行 |---|---|
if (/^\s*\|[-\s:]+\|/.test(line)) continue;
const cells = line.split('|')
.map(c => c.trim())
.filter((_, i, arr) => i > 0 && i < arr.length); // 去掉首尾空元素
if (cells.length > 0) rows.push(cells);
}
return rows;
};
// 渲染 markdown 表格
const renderMarkdownTable = (text: string) => {
try {
const tableData = parseMarkdownTable(text);
if (tableData.length === 0) return content;
return (
<div className="overflow-x-auto">
<table className="min-w-full border-collapse border border-gray-300">
<thead>
<tr>
{tableData[0].map((cell, cellIndex) => (
<th
key={`header-${cellIndex}`}
className="px-2 py-1 border border-gray-300 bg-gray-100 font-medium text-xs text-left whitespace-nowrap"
>
{cell || ' '}
</th>
))}
</tr>
</thead>
<tbody>
{tableData.slice(1).map((row, rowIndex) => (
<tr key={`row-${rowIndex}`}>
{row.map((cell, cellIndex) => (
<td
key={`cell-${rowIndex}-${cellIndex}`}
className="px-2 py-1 border border-gray-300 text-xs text-left"
>
{cell || ' '}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
} catch (error) {
console.error('Markdown表格渲染错误:', error);
return <div>{content}</div>;
}
};
// 渲染React表格(tab分隔)
const renderReactTable = (text: string) => { const renderReactTable = (text: string) => {
try { try {
const tableData = parseTableData(text); const tableData = parseTableData(text);
const hasHeader = tableData.length > 0; const hasHeader = tableData.length > 0;
return ( return (
<div> <div>
<table className="min-w-full border-collapse border border-gray-300"> <table className="min-w-full border-collapse border border-gray-300">
@@ -386,7 +450,7 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
<thead> <thead>
<tr> <tr>
{tableData[0].map((cell, cellIndex) => ( {tableData[0].map((cell, cellIndex) => (
<th <th
key={`header-${cellIndex}`} key={`header-${cellIndex}`}
className="px-2 py-1 border border-gray-300 bg-gray-100 font-medium text-xs text-left" className="px-2 py-1 border border-gray-300 bg-gray-100 font-medium text-xs text-left"
> >
@@ -400,7 +464,7 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
{tableData.slice(1).map((row, rowIndex) => ( {tableData.slice(1).map((row, rowIndex) => (
<tr key={`row-${rowIndex}`}> <tr key={`row-${rowIndex}`}>
{row.map((cell, cellIndex) => ( {row.map((cell, cellIndex) => (
<td <td
key={`cell-${rowIndex}-${cellIndex}`} key={`cell-${rowIndex}-${cellIndex}`}
className="px-2 py-1 border border-gray-300 text-xs text-left" className="px-2 py-1 border border-gray-300 text-xs text-left"
> >
@@ -421,10 +485,19 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
// 表格内容直接渲染表格组件,非表格内容保持原有行为
if (isTableLike) {
return (
<div className="text-xs p-1 rounded cursor-text w-full text-left">
{renderedContent}
</div>
);
}
return ( return (
<div className="text-xs p-1 rounded cursor-text w-full text-left"> <div className="text-xs p-1 rounded cursor-text w-full text-left">
{showTooltip ? ( {showTooltip ? (
<Tooltip <Tooltip
content={renderedContent} content={renderedContent}
placement="top" placement="top"
theme="light" theme="light"