fix: optimize visualization hot paths and refine bgp brief workspace

This commit is contained in:
rayd1o
2026-04-10 02:12:29 +08:00
parent 83839b8b11
commit 749e6e76b6
18 changed files with 776 additions and 343 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "planet-frontend",
"version": "0.24.5",
"version": "0.24.6",
"private": true,
"packageManager": "bun@1",
"dependencies": {

View File

@@ -76,6 +76,11 @@ export default function MarkdownRenderer({ markdown, className }: MarkdownRender
continue
}
if (trimmed.startsWith('<!--') && trimmed.endsWith('-->')) {
index += 1
continue
}
if (trimmed.startsWith('```')) {
const codeLines: string[] = []
index += 1
@@ -94,6 +99,12 @@ export default function MarkdownRenderer({ markdown, className }: MarkdownRender
continue
}
if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) {
nodes.push(<hr key={`block-${index}`} />)
index += 1
continue
}
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/)
if (headingMatch) {
const level = headingMatch[1].length
@@ -155,6 +166,50 @@ export default function MarkdownRenderer({ markdown, className }: MarkdownRender
continue
}
const tableHeaderCells = parseTableRow(trimmed)
const tableDividerCells = lines[index + 1] ? parseTableDivider(lines[index + 1].trim()) : null
if (
tableHeaderCells &&
tableHeaderCells.length > 0 &&
tableDividerCells &&
tableDividerCells.length === tableHeaderCells.length
) {
const bodyRows: string[][] = []
index += 2
while (index < lines.length) {
const rowCells = parseTableRow(lines[index].trim())
if (!rowCells || rowCells.length !== tableHeaderCells.length) {
break
}
bodyRows.push(rowCells)
index += 1
}
nodes.push(
<div key={`block-${index}`} className="markdown-renderer__table-wrap">
<table className="markdown-renderer__table">
<thead>
<tr>
{tableHeaderCells.map((cell, cellIndex) => (
<th key={`head-${cellIndex}`}>{renderInlineMarkdown(cell)}</th>
))}
</tr>
</thead>
<tbody>
{bodyRows.map((row, rowIndex) => (
<tr key={`row-${rowIndex}`}>
{row.map((cell, cellIndex) => (
<td key={`cell-${rowIndex}-${cellIndex}`}>{renderInlineMarkdown(cell)}</td>
))}
</tr>
))}
</tbody>
</table>
</div>,
)
continue
}
const paragraphLines: string[] = []
while (index < lines.length && lines[index].trim()) {
paragraphLines.push(lines[index].trim())
@@ -165,3 +220,23 @@ export default function MarkdownRenderer({ markdown, className }: MarkdownRender
return <div className={className ? `markdown-renderer ${className}` : 'markdown-renderer'}>{nodes}</div>
}
function parseTableRow(line: string): string[] | null {
if (!line.includes('|')) {
return null
}
const normalized = line.replace(/^\|/, '').replace(/\|$/, '')
const cells = normalized.split('|').map((cell) => cell.trim())
return cells.length > 0 ? cells : null
}
function parseTableDivider(line: string): string[] | null {
const cells = parseTableRow(line)
if (!cells || cells.length === 0) {
return null
}
const isDivider = cells.every((cell) => /^:?-{3,}:?$/.test(cell))
return isDivider ? cells : null
}

View File

@@ -1006,11 +1006,45 @@ body {
font-size: 13px;
}
.bgp-page__brief-tabpane {
width: 100%;
height: 100%;
min-height: 0;
overflow: auto;
padding-right: 4px;
scrollbar-width: thin;
scrollbar-color: rgba(148, 163, 184, 0.88) transparent;
}
.bgp-page__brief-tabpane::-webkit-scrollbar {
width: 8px;
}
.bgp-page__brief-tabpane::-webkit-scrollbar-thumb {
border-radius: 999px;
background: rgba(148, 163, 184, 0.88);
}
.bgp-page__brief-tabpane::-webkit-scrollbar-track {
background: transparent;
}
.bgp-page__brief-card {
display: flex;
flex-direction: column;
gap: 14px;
min-height: 100%;
display: block;
}
.bgp-page__brief-body {
display: grid;
gap: 12px;
}
.bgp-page__brief-content {
min-height: 360px;
overflow: visible;
padding: 14px 16px;
border-radius: 14px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(246, 248, 251, 0.98));
border: 1px solid rgba(5, 5, 5, 0.08);
}
.bgp-page__brief-head {
@@ -1045,24 +1079,16 @@ body {
gap: 12px;
}
.bgp-page__brief-card > * + * {
margin-top: 14px;
}
.bgp-page__brief-meta .ant-descriptions-view {
background: #f7f8fa;
border-radius: 12px;
padding: 8px 12px;
}
.bgp-page__brief-content {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
padding: 14px 16px;
border-radius: 14px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(246, 248, 251, 0.98));
border: 1px solid rgba(5, 5, 5, 0.08);
scrollbar-width: thin;
scrollbar-color: rgba(148, 163, 184, 0.88) transparent;
}
.markdown-renderer {
color: #262626;
font-size: 13px;
@@ -1103,7 +1129,9 @@ body {
.markdown-renderer ul,
.markdown-renderer ol,
.markdown-renderer blockquote,
.markdown-renderer pre {
.markdown-renderer pre,
.markdown-renderer hr,
.markdown-renderer__table-wrap {
margin: 0 0 0.9em;
}
@@ -1132,6 +1160,43 @@ body {
color: #e2e8f0;
}
.markdown-renderer hr {
border: 0;
border-top: 1px solid rgba(15, 23, 42, 0.12);
}
.markdown-renderer__table-wrap {
overflow-x: auto;
}
.markdown-renderer__table {
width: 100%;
border-collapse: collapse;
min-width: 520px;
font-size: 13px;
background: rgba(255, 255, 255, 0.96);
border: 1px solid rgba(15, 23, 42, 0.08);
border-radius: 10px;
}
.markdown-renderer__table th,
.markdown-renderer__table td {
padding: 10px 12px;
text-align: left;
vertical-align: top;
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
}
.markdown-renderer__table th {
font-weight: 600;
color: #111827;
background: rgba(248, 250, 252, 0.95);
}
.markdown-renderer__table tbody tr:last-child td {
border-bottom: 0;
}
.markdown-renderer code {
padding: 0.08em 0.32em;
border-radius: 6px;

View File

@@ -519,60 +519,62 @@ function BGP() {
}
const briefTabContent = (
<div className="bgp-page__brief-card">
<div className="bgp-page__brief-head">
<div>
<Text strong>BGP AI </Text>
<div className="bgp-page__brief-subtitle">
Markdown
<div className="bgp-page__brief-tabpane">
<div className="bgp-page__brief-card">
<div className="bgp-page__brief-head">
<div>
<Text strong>BGP AI </Text>
<div className="bgp-page__brief-subtitle">
Markdown
</div>
</div>
<div className="bgp-page__brief-actions">
<Select
className="bgp-page__brief-select"
placeholder="选择历史简报"
value={selectedBriefId || undefined}
options={formatBriefOptions(briefOptions)}
onChange={(value) => void handleBriefSelectionChange(value)}
disabled={briefLoading || briefDetailLoading || briefOptions.length === 0}
/>
<Button
type="primary"
icon={<ReloadOutlined />}
loading={briefLoading}
onClick={() => void handleGenerateBrief()}
>
AI
</Button>
</div>
</div>
<div className="bgp-page__brief-actions">
<Select
className="bgp-page__brief-select"
placeholder="选择历史简报"
value={selectedBriefId || undefined}
options={formatBriefOptions(briefOptions)}
onChange={(value) => void handleBriefSelectionChange(value)}
disabled={briefLoading || briefDetailLoading || briefOptions.length === 0}
/>
<Button
type="primary"
icon={<ReloadOutlined />}
loading={briefLoading}
onClick={() => void handleGenerateBrief()}
>
AI
</Button>
</div>
</div>
{briefLoading || briefDetailLoading ? (
<div className="bgp-page__brief-loading">
<Spin />
<Text type="secondary">
{briefLoading ? '正在整理 BGP 事实并生成简报...' : '正在加载已保存的 BGP 简报...'}
</Text>
</div>
) : brief ? (
<Space direction="vertical" size={12} style={{ width: '100%' }}>
<Descriptions size="small" column={compactViewport ? 1 : 3} className="bgp-page__brief-meta">
<Descriptions.Item label="Provider">{brief.provider || '-'}</Descriptions.Item>
<Descriptions.Item label="模型">{brief.model || '-'}</Descriptions.Item>
<Descriptions.Item label="请求ID">{brief.request_id || '-'}</Descriptions.Item>
<Descriptions.Item label="生成时间" span={compactViewport ? 1 : 3}>
{formatDateTimeZhCN(brief.generated_at)}
</Descriptions.Item>
</Descriptions>
<div className="bgp-page__brief-content">
<MarkdownRenderer markdown={brief.content_markdown} />
{briefLoading || briefDetailLoading ? (
<div className="bgp-page__brief-loading">
<Spin />
<Text type="secondary">
{briefLoading ? '正在整理 BGP 事实并生成简报...' : '正在加载已保存的 BGP 简报...'}
</Text>
</div>
</Space>
) : (
<div className="bgp-page__brief-empty">
<Text type="secondary"> BGP Markdown </Text>
</div>
)}
) : brief ? (
<div className="bgp-page__brief-body">
<Descriptions size="small" column={compactViewport ? 1 : 3} className="bgp-page__brief-meta">
<Descriptions.Item label="Provider">{brief.provider || '-'}</Descriptions.Item>
<Descriptions.Item label="模型">{brief.model || '-'}</Descriptions.Item>
<Descriptions.Item label="请求ID">{brief.request_id || '-'}</Descriptions.Item>
<Descriptions.Item label="生成时间" span={compactViewport ? 1 : 3}>
{formatDateTimeZhCN(brief.generated_at)}
</Descriptions.Item>
</Descriptions>
<div className="bgp-page__brief-content">
<MarkdownRenderer markdown={brief.content_markdown} />
</div>
</div>
) : (
<div className="bgp-page__brief-empty">
<Text type="secondary"> BGP Markdown </Text>
</div>
)}
</div>
</div>
)