diff --git a/src/pages/chat/components/messages/MessageItem.tsx b/src/pages/chat/components/messages/MessageItem.tsx index bae761b..2040e6c 100644 --- a/src/pages/chat/components/messages/MessageItem.tsx +++ b/src/pages/chat/components/messages/MessageItem.tsx @@ -1,8 +1,10 @@ import { Button, Dropdown, Space, Tag, Tooltip, Avatar } from 'antd'; +import { useMemo } from 'react'; import { CopyOutlined, SyncOutlined } from '@ant-design/icons'; import type { BranchInfo, ChatMessage } from '../../../../api'; import type { Agent } from '../../../../api/agents'; import Markdown from '../../../../components/Markdown'; +import { formatMessageContent } from '../../utils/format'; import type { CopyMode } from '../../utils/copy'; import { ReasoningView, RetrievedView, ToolCallView } from './MetaViews'; import './MessageItem.css'; @@ -21,6 +23,8 @@ export default function MessageItem(props: { }) { const { message, agentList, currentAgentId, highlighted, branch, busy, onRegenerate, onSwitchBranch, onCopy, isMobile } = props; + const formattedContent = useMemo(() => formatMessageContent(message.content), [message.content]); + const speakerType = (message as any)?.speaker?.type as ('user' | 'agent' | undefined); const speakerId = (message as any)?.speaker?.id as string | undefined; const isUser = speakerType ? speakerType === 'user' : message.role === 'user'; @@ -66,7 +70,7 @@ export default function MessageItem(props: {
- {message.content} + {formattedContent}
{hasBranches && ( @@ -86,8 +90,8 @@ export default function MessageItem(props: { trigger={['click']} menu={{ items: [ - { key: 'plain', label: '复制纯文本', onClick: () => onCopy?.(message.content, 'plain') }, - { key: 'markdown', label: '复制 Markdown', onClick: () => onCopy?.(message.content, 'markdown') } + { key: 'plain', label: '复制纯文本', onClick: () => onCopy?.(formattedContent, 'plain') }, + { key: 'markdown', label: '复制 Markdown', onClick: () => onCopy?.(formattedContent, 'markdown') } ] }} > @@ -113,11 +117,11 @@ export default function MessageItem(props: {
- {message.content.includes('![image](') ? ( - {message.content} + {formattedContent.includes('![image](') ? ( + {formattedContent} ) : ( @$1') + __html: formattedContent.replace(/@([^\s]+)/g, '@$1') }} /> )}
diff --git a/src/pages/chat/utils/format.ts b/src/pages/chat/utils/format.ts new file mode 100644 index 0000000..45e6d22 --- /dev/null +++ b/src/pages/chat/utils/format.ts @@ -0,0 +1,18 @@ +/** + * 清理消息内容中的冗余换行 + * 1. 将 3 个及以上的换行替换为 2 个换行 + * 2. 将特定的 Markdown 块元素前后的双换行替换为单换行,以减少间距 + */ +export function formatMessageContent(content: string): string { + if (!content) return ''; + + return content + // 将 3 个及以上的换行替换为 2 个 + .replace(/\n{3,}/g, '\n\n') + // 针对 Markdown 块元素,将双换行缩小为单换行 + // 匹配 \n\n 后面跟着 #, -, *, >, --- 等 + .replace(/\n\n(?=[#\-*>]|---)/g, '\n') + // 匹配 #, -, *, >, --- 后面跟着 \n\n + // 由于 JavaScript 不支持所有环境下的 lookbehind,我们先处理上面的 + .replace(/([#\-*>]|---)\n\n/g, '$1\n'); +} diff --git a/src/styles.css b/src/styles.css index 2d30d5e..84c9e51 100644 --- a/src/styles.css +++ b/src/styles.css @@ -751,7 +751,7 @@ body { } .chat-body .messages-container { - max-width: 780px; + max-width: 1080px; width: 100%; margin: 0 auto; padding: 16px 12px 80px; @@ -785,6 +785,7 @@ body { border: 0; border-bottom-left-radius: 5px; box-shadow: none; + white-space: normal; } .bubble.assistant p {