The drawer hand-rolled chat bubbles duplicating ChatBubble's shape. Add a mirrored layout prop (virtual user left, target right) and reuse the shared component.
69 lines
2.3 KiB
TypeScript
69 lines
2.3 KiB
TypeScript
import type { ReactNode } from 'react'
|
|
import { RobotOutlined, UserOutlined, WarningOutlined } from '@ant-design/icons'
|
|
import { colors } from '../tokens'
|
|
|
|
interface ChatBubbleProps {
|
|
role: 'user' | 'agent' | 'system'
|
|
content: ReactNode
|
|
meta?: ReactNode
|
|
pending?: boolean
|
|
/** 镜像布局:探索下钻里虚拟用户居左、被评对象居右(与常规聊天相反) */
|
|
mirrored?: boolean
|
|
}
|
|
|
|
export default function ChatBubble({ role, content, meta, pending, mirrored }: ChatBubbleProps) {
|
|
const isUser = role === 'user'
|
|
const isAgent = role === 'agent'
|
|
const isSystem = role === 'system'
|
|
|
|
const bg = isUser ? colors.chatUser : isAgent ? colors.chatAgent : colors.chatSystemError
|
|
const border = isSystem ? '#ffccc7' : 'transparent'
|
|
const textColor = isSystem ? '#cf1322' : colors.text
|
|
const alignRight = isSystem ? false : isUser ? !mirrored : !!mirrored
|
|
const align = alignRight ? 'flex-end' : 'flex-start'
|
|
const radius = alignRight ? '10px 10px 2px 10px' : '10px 10px 10px 2px'
|
|
|
|
const icon = isUser
|
|
? <UserOutlined style={{ color: colors.primary }} />
|
|
: isAgent
|
|
? <RobotOutlined style={{ color: '#52c41a' }} />
|
|
: <WarningOutlined style={{ color: '#cf1322' }} />
|
|
const label = isUser ? '用户' : isAgent ? '智能体' : '系统'
|
|
|
|
return (
|
|
<div style={{ display: 'flex', justifyContent: align, marginBottom: 6 }}>
|
|
<div style={{ maxWidth: '82%', display: 'flex', flexDirection: 'column', alignItems: alignRight ? 'flex-end' : 'flex-start', gap: 2 }}>
|
|
<div style={{ display: 'flex', gap: 4, alignItems: 'center', fontSize: 11, color: colors.textMuted }}>
|
|
{!isUser && icon}
|
|
<span>{label}</span>
|
|
{isUser && icon}
|
|
{meta}
|
|
</div>
|
|
<div
|
|
style={{
|
|
background: bg,
|
|
border: `1px solid ${border}`,
|
|
borderRadius: radius,
|
|
padding: '6px 10px',
|
|
fontSize: 13,
|
|
lineHeight: 1.55,
|
|
color: textColor,
|
|
wordBreak: 'break-word',
|
|
whiteSpace: 'pre-wrap',
|
|
}}
|
|
>
|
|
{pending ? <TypingDots /> : content}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function TypingDots() {
|
|
return (
|
|
<span className="typing-dots" aria-label="等待回复中">
|
|
<span /><span /><span />
|
|
</span>
|
|
)
|
|
}
|