fix: 按 speaker.type 区分消息左右与头像,并改为 iMessage 气泡样式

main
sp mac bookpro 2605 2026-06-08 01:15:12 +08:00
parent b1df40e983
commit e6116c1c80
5 changed files with 26 additions and 19 deletions

View File

@ -15,7 +15,11 @@ export interface ToolCallTrace {
export interface ChatMessage { export interface ChatMessage {
id: string; id: string;
role: 'user' | 'assistant'; role: 'user' | 'assistant' | 'agent' | 'system';
speaker?: {
id: string;
type: 'user' | 'agent';
};
content: string; content: string;
reasoning?: string | null; reasoning?: string | null;
parentId?: string | null; parentId?: string | null;
@ -73,4 +77,3 @@ export const ChatAttachmentsAPI = {
return api.post<{ files: ChatAttachment[] }>('/chat/attachments', fd).then((r) => r.data); return api.post<{ files: ChatAttachment[] }>('/chat/attachments', fd).then((r) => r.data);
} }
}; };

View File

@ -60,7 +60,7 @@ export default function ChatBody(props: {
agentList={agentList} agentList={agentList}
currentAgentId={currentAgentId} currentAgentId={currentAgentId}
highlighted={highlightId === m.id} highlighted={highlightId === m.id}
branch={m.role === 'assistant' && m.parentId ? branches[m.parentId] : undefined} branch={((m as any)?.speaker?.type ? (m as any).speaker.type === 'agent' : m.role === 'assistant') && m.parentId ? branches[m.parentId] : undefined}
busy={sending} busy={sending}
onRegenerate={onRegenerate} onRegenerate={onRegenerate}
onSwitchBranch={onSwitchBranch} onSwitchBranch={onSwitchBranch}
@ -76,7 +76,9 @@ export default function ChatBody(props: {
const streamingAgent = agentList.find(a => a.id === streamingAgentId); const streamingAgent = agentList.find(a => a.id === streamingAgentId);
if (streamingAgent) { if (streamingAgent) {
return ( return (
<Avatar src={streamingAgent.avatar} size={36} style={{ flexShrink: 0, marginTop: 2 }} /> <Avatar src={streamingAgent.avatar} size={36} style={{ flexShrink: 0, marginTop: 2, backgroundColor: '#52c41a' }}>
{streamingAgent.name?.charAt(0)?.toUpperCase() || 'A'}
</Avatar>
); );
} }
return null; return null;

View File

@ -14,7 +14,7 @@ function summarize(content: string) {
export default function ChatOutline(props: { messages: ChatMessage[]; onJump: (id: string) => void; activeId?: string | null }) { export default function ChatOutline(props: { messages: ChatMessage[]; onJump: (id: string) => void; activeId?: string | null }) {
const { messages, onJump, activeId } = props; const { messages, onJump, activeId } = props;
const items = messages.filter((m) => m.role === 'assistant'); const items = messages.filter((m) => ((m as any)?.speaker?.type ? (m as any).speaker.type === 'agent' : m.role === 'assistant'));
if (items.length === 0) return null; if (items.length === 0) return null;
@ -38,4 +38,3 @@ export default function ChatOutline(props: { messages: ChatMessage[]; onJump: (i
</aside> </aside>
); );
} }

View File

@ -19,8 +19,12 @@ export default function MessageItem(props: {
}) { }) {
const { message, agentList, currentAgentId, highlighted, branch, busy, onRegenerate, onSwitchBranch, onCopy } = props; const { message, agentList, currentAgentId, highlighted, branch, busy, onRegenerate, onSwitchBranch, onCopy } = props;
const speakerType = (message as any)?.speaker?.type as ('user' | 'agent' | undefined);
const isUser = speakerType ? speakerType === 'user' : message.role === 'user';
const bubbleRole = isUser ? 'user' : 'assistant';
// 获取回答者 Agent 信息 // 获取回答者 Agent 信息
const answerAgentId = message.agent_id || (message.role === 'assistant' ? currentAgentId : undefined); const answerAgentId = message.agent_id || (!isUser ? currentAgentId : undefined);
const answerAgent = answerAgentId ? agentList.find(a => a.id === answerAgentId) : undefined; const answerAgent = answerAgentId ? agentList.find(a => a.id === answerAgentId) : undefined;
const hasBranches = !!branch && branch.total > 1; const hasBranches = !!branch && branch.total > 1;
const activeIdx = branch?.activeIndex ?? 0; const activeIdx = branch?.activeIndex ?? 0;
@ -49,9 +53,8 @@ export default function MessageItem(props: {
transition: 'background 0.4s, padding 0.4s' transition: 'background 0.4s, padding 0.4s'
}} }}
> >
{message.role === 'assistant' ? ( {!isUser ? (
<div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}> <div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
{/* AGENT: 头像在左侧,内容在右侧(靠左对齐) */}
<Avatar <Avatar
src={answerAgent?.avatar} src={answerAgent?.avatar}
size={36} size={36}
@ -74,7 +77,7 @@ export default function MessageItem(props: {
{answerAgent?.name || 'AI'} {answerAgent?.name || 'AI'}
</span> </span>
</div> </div>
<div className={`bubble ${message.role}`}> <div className={`bubble ${bubbleRole}`}>
<Markdown>{message.content}</Markdown> <Markdown>{message.content}</Markdown>
</div> </div>
<div className="monica-msg-actions"> <div className="monica-msg-actions">
@ -124,8 +127,7 @@ export default function MessageItem(props: {
</div> </div>
) : ( ) : (
<div style={{ display: 'flex', gap: 12, alignItems: 'flex-start', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', gap: 12, alignItems: 'flex-start', justifyContent: 'flex-end' }}>
{/* 用户: 头像在右侧,内容在左侧(靠右对齐) */} <div style={{ flex: 1, minWidth: 0, maxWidth: '78%', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
<div style={{ maxWidth: '78%', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
<div style={{ <div style={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
@ -140,7 +142,7 @@ export default function MessageItem(props: {
</span> </span>
</div> </div>
<div className={`bubble ${message.role}`}> <div className={`bubble ${bubbleRole}`}>
{message.content.includes('![image](') ? ( {message.content.includes('![image](') ? (
<Markdown>{message.content}</Markdown> <Markdown>{message.content}</Markdown>
) : ( ) : (

View File

@ -628,6 +628,7 @@ body {
.bubble { .bubble {
max-width: 78%; max-width: 78%;
display: inline-block;
padding: 14px 18px; padding: 14px 18px;
border-radius: 14px; border-radius: 14px;
margin-bottom: 14px; margin-bottom: 14px;
@ -638,18 +639,18 @@ body {
} }
.bubble.user { .bubble.user {
background: var(--color-brand-soft); background: #0a84ff;
color: var(--color-text); color: #ffffff;
margin-left: auto; margin-left: auto;
border-bottom-right-radius: 5px; border-bottom-right-radius: 5px;
} }
.bubble.assistant { .bubble.assistant {
background: #ffffff; background: #e9e9eb;
color: var(--color-text); color: #111827;
border: 1px solid var(--color-border); border: 0;
border-bottom-left-radius: 5px; border-bottom-left-radius: 5px;
box-shadow: var(--shadow-xs); box-shadow: none;
} }
.bubble.assistant p { .bubble.assistant p {