diff --git a/ai_knowledge_base_v2/apps/admin-web/src/components/StreamingMarkdownMessage.vue b/ai_knowledge_base_v2/apps/admin-web/src/components/StreamingMarkdownMessage.vue index 18ce47d..b466758 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/components/StreamingMarkdownMessage.vue +++ b/ai_knowledge_base_v2/apps/admin-web/src/components/StreamingMarkdownMessage.vue @@ -13,7 +13,15 @@ const markdown = new MarkdownIt({ breaks: true, html: false, linkify: true }); const parsed = computed(() => splitReasoning(props.content)); const answer = computed(() => parsed.value.answer.replace(/^\s+/, "")); const reasoning = computed(() => props.reasoning || parsed.value.reasoning); -const renderedContent = computed(() => answer.value.trim() ? markdown.render(answer.value) : ""); +const renderedContent = computed(() => answer.value.trim() ? markdown.render(normalizeStandaloneHeadings(answer.value)) : ""); + +function normalizeStandaloneHeadings(content: string) { + return content.replace(/^([ \t]*)\*\*(.+)\*\*[ \t]*$/gm, (line, indent: string, title: string) => { + const normalizedTitle = title.trim(); + if (!normalizedTitle || normalizedTitle.length > 48 || normalizedTitle.includes("**")) return line; + return `${indent}### ${normalizedTitle}`; + }); +} function splitReasoning(content: string) { const lower = content.toLowerCase(); diff --git a/ai_knowledge_base_v2/apps/admin-web/src/styles.css b/ai_knowledge_base_v2/apps/admin-web/src/styles.css index d094ebf..a0bac47 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/styles.css +++ b/ai_knowledge_base_v2/apps/admin-web/src/styles.css @@ -1384,38 +1384,43 @@ textarea { overflow-wrap: anywhere; color: #263832; font-size: 14px; - line-height: 1.75; + line-height: 1.68; } .agent-preview-markdown > :first-child { margin-top: 0; } .agent-preview-markdown > :last-child { margin-bottom: 0; } -.agent-preview-markdown p { margin: 0 0 10px; } +.agent-preview-markdown p { margin: 0 0 8px; } .agent-preview-markdown h1, .agent-preview-markdown h2, .agent-preview-markdown h3, -.agent-preview-markdown h4 { - margin: 16px 0 8px; +.agent-preview-markdown h4, +.agent-preview-markdown h5, +.agent-preview-markdown h6 { + margin: 13px 0 5px; color: #1f352e; - line-height: 1.45; + line-height: 1.42; + font-weight: 700; } -.agent-preview-markdown h1 { font-size: 19px; } +.agent-preview-markdown h1 { font-size: 18px; } .agent-preview-markdown h2 { font-size: 17px; } -.agent-preview-markdown h3, +.agent-preview-markdown h3 { font-size: 16px; } .agent-preview-markdown h4 { font-size: 15px; } +.agent-preview-markdown h5, +.agent-preview-markdown h6 { font-size: 14px; } .agent-preview-markdown ul, .agent-preview-markdown ol { - margin: 8px 0 10px; - padding-left: 22px; + margin: 6px 0 8px; + padding-left: 21px; } -.agent-preview-markdown li + li { margin-top: 5px; } +.agent-preview-markdown li + li { margin-top: 3px; } .agent-preview-markdown strong { color: #1f352e; font-weight: 700; } .agent-preview-markdown blockquote { - margin: 10px 0; + margin: 8px 0; padding: 7px 10px; border-left: 3px solid #8cc7b6; background: #eef7f4; @@ -1464,6 +1469,12 @@ textarea { .agent-preview-markdown a { color: #0f735d; font-weight: 700; } +.agent-preview-markdown hr { + margin: 12px 0; + border: 0; + border-top: 1px solid #dbe7e3; +} + .agent-preview-generation-state { display: flex; align-items: center; diff --git a/ai_knowledge_base_v2/apps/user-client/src/components/ChatMessage.vue b/ai_knowledge_base_v2/apps/user-client/src/components/ChatMessage.vue index f986afc..7e8dd84 100644 --- a/ai_knowledge_base_v2/apps/user-client/src/components/ChatMessage.vue +++ b/ai_knowledge_base_v2/apps/user-client/src/components/ChatMessage.vue @@ -23,9 +23,17 @@ const reasoning = computed(() => props.reasoning || parsed.value.reasoning); const hasAnswer = computed(() => answer.value.trim().length > 0); const renderedContent = computed(() => { if (props.role !== "assistant") return answer.value; - return hasAnswer.value ? markdown.render(answer.value) : ""; + return hasAnswer.value ? markdown.render(normalizeStandaloneHeadings(answer.value)) : ""; }); +function normalizeStandaloneHeadings(content: string) { + return content.replace(/^([ \t]*)\*\*(.+)\*\*[ \t]*$/gm, (line, indent: string, title: string) => { + const normalizedTitle = title.trim(); + if (!normalizedTitle || normalizedTitle.length > 48 || normalizedTitle.includes("**")) return line; + return `${indent}### ${normalizedTitle}`; + }); +} + function splitReasoning(content: string) { const lower = content.toLowerCase(); let answer = ""; diff --git a/ai_knowledge_base_v2/apps/user-client/src/styles.css b/ai_knowledge_base_v2/apps/user-client/src/styles.css index 86b69ad..32a01b3 100644 --- a/ai_knowledge_base_v2/apps/user-client/src/styles.css +++ b/ai_knowledge_base_v2/apps/user-client/src/styles.css @@ -1321,7 +1321,7 @@ textarea:focus-visible { white-space: pre-wrap; color: inherit; font-size: 15px; - line-height: 1.78; + line-height: 1.7; letter-spacing: 0; } @@ -1369,35 +1369,39 @@ textarea:focus-visible { .markdown-content > :first-child { margin-top: 0; } .markdown-content > :last-child { margin-bottom: 0; } -.markdown-content p { margin: 0 0 12px; } +.markdown-content p { margin: 0 0 8px; } .markdown-content h1, .markdown-content h2, .markdown-content h3, -.markdown-content h4 { - margin: 20px 0 9px; +.markdown-content h4, +.markdown-content h5, +.markdown-content h6 { + margin: 15px 0 6px; color: var(--chat-text); - line-height: 1.45; + line-height: 1.42; font-weight: 700; } .markdown-content h1 { font-size: 20px; } .markdown-content h2 { font-size: 18px; } -.markdown-content h3, +.markdown-content h3 { font-size: 17px; } .markdown-content h4 { font-size: 16px; } +.markdown-content h5, +.markdown-content h6 { font-size: 15px; } .markdown-content ul, .markdown-content ol { - margin: 8px 0 13px; - padding-left: 22px; + margin: 6px 0 9px; + padding-left: 21px; } -.markdown-content li + li { margin-top: 6px; } +.markdown-content li + li { margin-top: 3px; } .markdown-content strong { color: var(--chat-text); font-weight: 700; } .markdown-content blockquote { - margin: 12px 0; - padding: 8px 12px; + margin: 9px 0; + padding: 7px 11px; border-left: 3px solid #8cc7b6; background: var(--chat-brand-soft); color: #526b63; @@ -1405,6 +1409,12 @@ textarea:focus-visible { .markdown-content blockquote p { margin: 0; } +.markdown-content hr { + margin: 14px 0; + border: 0; + border-top: 1px solid var(--chat-border); +} + .chat-composer { position: relative; z-index: 6; diff --git a/ai_knowledge_base_v2/design-qa.md b/ai_knowledge_base_v2/design-qa.md index e98ad77..eee85f4 100644 --- a/ai_knowledge_base_v2/design-qa.md +++ b/ai_knowledge_base_v2/design-qa.md @@ -51,6 +51,41 @@ final result: passed --- +# Markdown 长回答排版修复验收(2026-07-17) + +## 对照范围 + +- 问题截图:`/var/folders/hn/vg5lxj0j60d2cp0k6vv_k0mw0000gn/T/codex-clipboard-dc66a8a3-c423-4661-b29a-9c917754b888.png` +- 修复后完整页面:`test_records/screenshots/Markdown排版_修复后.png` +- 视口:1135 × 874,用户端 430px 固定聊天容器。 +- 状态:真实历史消息接口,包含 H1、H2、独立加粗小标题、正文、列表和引用块。 + +## 比较历史 + +- [P1] 修复前:模型常用的独立 `**加粗文本**` 只显示为加粗正文,没有标题字号和上下间距层级。 +- [P1] 修复前:正文行高、段落底部间距、列表条目间距和引用块上下间距叠加,长回答出现过大断层。 +- 修复:独立且不超过 48 字的加粗行按 H3 小标题渲染;普通行内加粗保持原语义。 +- 修复:用户端与后台预览统一收紧段落、列表、引用和标题间距,并补齐 H5、H6 和分隔线样式。 + +## 必查视觉面 + +- 字体与排版:用户端实测 H1/H2/H3/正文为 20/18/17/15px,标题层级清楚;正文行高 25.5px。 +- 间距与布局:标题上下间距为 15/6px,段落底部 8px,列表条目 3px,引用块顶部 9px;没有大块空白或水平溢出。 +- 颜色与令牌:继续使用现有深墨绿正文、雾绿引用背景和边框令牌,未引入新色系。 +- 图像与资源:本次为文本排版修复,无新增图像、图标或外部资源。 +- 文案与内容:Markdown 原文未被更改,只在展示层识别独立加粗小标题。 + +## 交互与工程检查 + +- DOM 实测确认独立加粗行已渲染为 H3,H1、H2、列表和引用均为正确语义节点。 +- 完整视图已能清晰判断字号、间距、列表和引用块,无需额外放大局部截图。 +- 用户端与后台预览 `npm run build`:通过。 +- 未发现剩余 P0、P1、P2 级问题。 + +final result: passed + +--- + # 后台登录页视觉重构验收(2026-07-13) ## 对照范围 diff --git a/ai_knowledge_base_v2/test_records/screenshots/Markdown排版_修复后.png b/ai_knowledge_base_v2/test_records/screenshots/Markdown排版_修复后.png new file mode 100644 index 0000000..7b4f29d Binary files /dev/null and b/ai_knowledge_base_v2/test_records/screenshots/Markdown排版_修复后.png differ