fix: render chat markdown formatting
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user