diff --git a/src/lib/markdown.js b/src/lib/markdown.js
index 25e1cb0..09eaa38 100644
--- a/src/lib/markdown.js
+++ b/src/lib/markdown.js
@@ -89,6 +89,8 @@ export function renderMarkdown(text) {
const result = []
let inList = false
let listType = ''
+ let inTable = false
+ let tableRows = []
for (let i = 0; i < lines.length; i++) {
let line = lines[i]
@@ -100,6 +102,38 @@ export function renderMarkdown(text) {
continue
}
+ // 表格检测:表头分隔行 (|---|...|)
+ const isTableSeparator = /^\s*\|[\s\-:|]+\|\s*$/.test(line) ||
+ /^\s*[\-:]+(\s*\|\s*[\-:]+)+\s*$/.test(line)
+
+ // 检测是否可能是表格行
+ const isTableRow = /^\s*\|.*\|\s*$/.test(line) ||
+ /^\s*[^\|]+\s*\|\s*[^\|]+/.test(line)
+
+ // 如果在表格中,继续收集行
+ if (inTable) {
+ if (isTableRow && line.trim() !== '') {
+ tableRows.push(line)
+ continue
+ } else {
+ // 表格结束,渲染表格
+ result.push(renderTable(tableRows))
+ inTable = false
+ tableRows = []
+ }
+ }
+
+ // 检测表格开始:当前行是表格行,且下一行是分隔行
+ if (!inTable && isTableRow && i + 1 < lines.length) {
+ const nextLine = lines[i + 1]
+ if (/^\s*\|[\s\-:|]+\|\s*$/.test(nextLine) ||
+ /^\s*[\-:]+(\s*\|\s*[\-:]+)+\s*$/.test(nextLine)) {
+ inTable = true
+ tableRows.push(line)
+ continue
+ }
+ }
+
// 标题
const headingMatch = line.match(/^(#{1,3})\s+(.+)$/)
if (headingMatch) {
@@ -138,9 +172,69 @@ export function renderMarkdown(text) {
}
if (inList) result.push(`${listType}>`)
+ // 处理剩余的表格
+ if (inTable && tableRows.length > 0) {
+ result.push(renderTable(tableRows))
+ }
return result.join('\n')
}
+/**
+ * 渲染 Markdown 表格
+ * @param {string[]} rows - 表格行数组
+ * @returns {string} HTML 表格
+ */
+function renderTable(rows) {
+ if (!rows || rows.length < 2) return ''
+
+ const table = ['
']
+ let isHeaderRow = true
+ let hasSeparator = false
+
+ for (let i = 0; i < rows.length; i++) {
+ let row = rows[i].trim()
+
+ // 跳过空行
+ if (!row) continue
+ // 检测分隔行 (|---|...|)
+ const isSeparator = /^\s*\|[\s\-:|]+\|\s*$/.test(row) ||
+ /^\s*[\-:]+(\s*\|\s*[\-:]+)+\s*$/.test(row)
+ if (isSeparator) {
+ hasSeparator = true
+ continue
+ }
+
+ // 解析单元格
+ let cells = []
+ if (row.startsWith('|') && row.endsWith('|')) {
+ // 标准格式: | cell1 | cell2 |
+ cells = row.slice(1, -1).split('|')
+ } else {
+ // 简化格式: cell1 | cell2
+ cells = row.split('|')
+ }
+ // 清理单元格内容
+ cells = cells.map(cell => inlineFormat(cell.trim()))
+ if (cells.length === 0) continue
+
+ // 渲染行
+ const tag = isHeaderRow && !hasSeparator && i === 0 ? 'th' : 'td'
+ table.push(' ')
+ cells.forEach(cell => {
+ table.push(` <${tag}>${cell}${tag}>`)
+ })
+ table.push('
')
+
+ // 第一行后切换到数据行(如果有分隔行)
+ if (hasSeparator && i === 0) {
+ isHeaderRow = false
+ }
+ }
+
+ table.push('
')
+ return table.join('\n')
+}
+
function inlineFormat(text) {
return text
.replace(/\*\*(.+?)\*\*/g, '$1')
diff --git a/src/style/chat.css b/src/style/chat.css
index 1817cdb..1ae6f4f 100644
--- a/src/style/chat.css
+++ b/src/style/chat.css
@@ -304,6 +304,34 @@
.hl-func { color: #61afef; }
.hl-type { color: #e5c07b; }
+/* Markdown 表格 */
+.msg-ai .msg-bubble table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 8px 0;
+ font-size: 13px;
+}
+
+.msg-ai .msg-bubble th,
+.msg-ai .msg-bubble td {
+ border: 1px solid var(--border);
+ padding: 8px 12px;
+ text-align: left;
+}
+
+.msg-ai .msg-bubble th {
+ background: var(--bg-tertiary);
+ font-weight: 600;
+}
+
+.msg-ai .msg-bubble tr:nth-child(even) {
+ background: var(--bg-hover);
+}
+
+.msg-ai .msg-bubble tr:hover {
+ background: rgba(99, 102, 241, 0.08);
+}
+
/* 行内代码 */
.msg-ai .msg-bubble > code {
background: var(--bg-hover, rgba(255,255,255,0.08));