优化消息内容的换行显示,解决间距过大的问题

main
sp mac bookpro 2605 2026-07-22 17:44:20 +08:00
parent 5f947b4d64
commit 9c208cd8e1
3 changed files with 30 additions and 7 deletions

View File

@ -1,8 +1,10 @@
import { Button, Dropdown, Space, Tag, Tooltip, Avatar } from 'antd'; import { Button, Dropdown, Space, Tag, Tooltip, Avatar } from 'antd';
import { useMemo } from 'react';
import { CopyOutlined, SyncOutlined } from '@ant-design/icons'; import { CopyOutlined, SyncOutlined } from '@ant-design/icons';
import type { BranchInfo, ChatMessage } from '../../../../api'; import type { BranchInfo, ChatMessage } from '../../../../api';
import type { Agent } from '../../../../api/agents'; import type { Agent } from '../../../../api/agents';
import Markdown from '../../../../components/Markdown'; import Markdown from '../../../../components/Markdown';
import { formatMessageContent } from '../../utils/format';
import type { CopyMode } from '../../utils/copy'; import type { CopyMode } from '../../utils/copy';
import { ReasoningView, RetrievedView, ToolCallView } from './MetaViews'; import { ReasoningView, RetrievedView, ToolCallView } from './MetaViews';
import './MessageItem.css'; 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 { 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 speakerType = (message as any)?.speaker?.type as ('user' | 'agent' | undefined);
const speakerId = (message as any)?.speaker?.id as string | undefined; const speakerId = (message as any)?.speaker?.id as string | undefined;
const isUser = speakerType ? speakerType === 'user' : message.role === 'user'; const isUser = speakerType ? speakerType === 'user' : message.role === 'user';
@ -66,7 +70,7 @@ export default function MessageItem(props: {
</span> </span>
</div> </div>
<div className={`bubble ${bubbleRole}`}> <div className={`bubble ${bubbleRole}`}>
<Markdown>{message.content}</Markdown> <Markdown>{formattedContent}</Markdown>
<div className="message-item-actions"> <div className="message-item-actions">
{hasBranches && ( {hasBranches && (
<Space size={2}> <Space size={2}>
@ -86,8 +90,8 @@ export default function MessageItem(props: {
trigger={['click']} trigger={['click']}
menu={{ menu={{
items: [ items: [
{ key: 'plain', label: '复制纯文本', onClick: () => onCopy?.(message.content, 'plain') }, { key: 'plain', label: '复制纯文本', onClick: () => onCopy?.(formattedContent, 'plain') },
{ key: 'markdown', label: '复制 Markdown', onClick: () => onCopy?.(message.content, 'markdown') } { key: 'markdown', label: '复制 Markdown', onClick: () => onCopy?.(formattedContent, 'markdown') }
] ]
}} }}
> >
@ -113,11 +117,11 @@ export default function MessageItem(props: {
<div className="message-item-user"> <div className="message-item-user">
<div className="message-item-user-content-wrapper"> <div className="message-item-user-content-wrapper">
<div className={`bubble ${bubbleRole}`}> <div className={`bubble ${bubbleRole}`}>
{message.content.includes('![image](') ? ( {formattedContent.includes('![image](') ? (
<Markdown>{message.content}</Markdown> <Markdown>{formattedContent}</Markdown>
) : ( ) : (
<span dangerouslySetInnerHTML={{ <span dangerouslySetInnerHTML={{
__html: message.content.replace(/@([^\s]+)/g, '<span class="mention">@$1</span>') __html: formattedContent.replace(/@([^\s]+)/g, '<span class="mention">@$1</span>')
}} /> }} />
)} )}
</div> </div>

View File

@ -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');
}

View File

@ -751,7 +751,7 @@ body {
} }
.chat-body .messages-container { .chat-body .messages-container {
max-width: 780px; max-width: 1080px;
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
padding: 16px 12px 80px; padding: 16px 12px 80px;
@ -785,6 +785,7 @@ body {
border: 0; border: 0;
border-bottom-left-radius: 5px; border-bottom-left-radius: 5px;
box-shadow: none; box-shadow: none;
white-space: normal;
} }
.bubble.assistant p { .bubble.assistant p {