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:
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user