fix: render chat markdown formatting

This commit is contained in:
2026-06-22 13:16:32 +08:00
parent 2998c01111
commit 0cfbcbf56f
2 changed files with 118 additions and 3 deletions
+83 -3
View File
@@ -1,7 +1,7 @@
"use client";
import Link from "next/link";
import type { KeyboardEventHandler } from "react";
import type { KeyboardEventHandler, ReactNode } from "react";
import { ClockCircleOutlined, MessageOutlined, ReloadOutlined, SendOutlined } from "@ant-design/icons";
import { Alert, Button, Card, Empty, Input, Space, Spin, Tag, Typography, message } from "antd";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -654,6 +654,86 @@ function normalizeMessagesForDisplay(items: ChatMessage[], lastQuestion: string,
});
}
function renderInlineMarkdown(text: string): ReactNode[] {
const parts: ReactNode[] = [];
const pattern = /\*\*([^*]+)\*\*/g;
let lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = pattern.exec(text)) !== null) {
if (match.index > lastIndex) {
parts.push(text.slice(lastIndex, match.index));
}
parts.push(<strong key={`bold-${match.index}`}>{match[1]}</strong>);
lastIndex = pattern.lastIndex;
}
if (lastIndex < text.length) {
parts.push(text.slice(lastIndex));
}
return parts.length ? parts : [text];
}
function renderChatMessageContent(content: string) {
const lines = content.replace(/\r\n/g, "\n").split("\n");
return (
<div className="chat-message-markdown">
{lines.map((rawLine, index) => {
const line = rawLine.trim();
if (!line) {
return <div className="chat-message-break" key={`break-${index}`} />;
}
const headingMatch = line.match(/^#{1,6}\s+(.+)$/);
if (headingMatch) {
return (
<div className="chat-message-heading" key={`heading-${index}`}>
{renderInlineMarkdown(headingMatch[1])}
</div>
);
}
const boldHeadingMatch = line.match(/^\*\*([^*]+)\*\*[:]?$/);
if (boldHeadingMatch) {
return (
<div className="chat-message-heading" key={`bold-heading-${index}`}>
{boldHeadingMatch[1]}
</div>
);
}
const orderedMatch = line.match(/^(\d+)[.)、]\s+(.+)$/);
if (orderedMatch) {
return (
<div className="chat-message-list-row" key={`ordered-${index}`}>
<span className="chat-message-list-marker">{orderedMatch[1]}.</span>
<span>{renderInlineMarkdown(orderedMatch[2])}</span>
</div>
);
}
const bulletMatch = line.match(/^[-*•]\s+(.+)$/);
if (bulletMatch) {
return (
<div className="chat-message-list-row" key={`bullet-${index}`}>
<span className="chat-message-list-marker"></span>
<span>{renderInlineMarkdown(bulletMatch[1])}</span>
</div>
);
}
return (
<p className="chat-message-paragraph" key={`paragraph-${index}`}>
{renderInlineMarkdown(line)}
</p>
);
})}
</div>
);
}
function mergeAssistantContent(previous: string, incoming: string) {
if (!incoming) {
return previous;
@@ -1560,7 +1640,7 @@ export default function ChatPage() {
{fallbackMessages.map((item, index) => (
<div className={`chat-message chat-message-${item.role}`} key={`${item.role}-${index}`}>
<div className="chat-message-role">{item.role === "user" ? "你" : "AI"}</div>
<div className="chat-message-content">{item.content}</div>
<div className="chat-message-content">{renderChatMessageContent(item.content)}</div>
</div>
))}
</div>
@@ -1572,7 +1652,7 @@ export default function ChatPage() {
{displayMessages.map((item, index) => (
<div className={`chat-message chat-message-${item.role}`} key={`${item.role}-${index}`}>
<div className="chat-message-role">{item.role === "user" ? "你" : "AI"}</div>
<div className="chat-message-content">{item.content}</div>
<div className="chat-message-content">{renderChatMessageContent(item.content)}</div>
</div>
))}
</div>
+35
View File
@@ -590,6 +590,41 @@ body {
color: #64748b;
}
.chat-message-markdown {
display: flex;
flex-direction: column;
gap: 8px;
white-space: normal;
}
.chat-message-heading {
margin-top: 4px;
font-size: 15px;
font-weight: 800;
color: #0f172a;
}
.chat-message-paragraph {
margin: 0;
}
.chat-message-list-row {
display: grid;
grid-template-columns: 28px minmax(0, 1fr);
gap: 6px;
align-items: start;
}
.chat-message-list-marker {
color: #2563eb;
font-weight: 700;
text-align: right;
}
.chat-message-break {
height: 4px;
}
.chat-review-layout {
display: flex;
flex-direction: column;