refactor(frontend): reuse ChatBubble for exploration drill-down

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.
This commit is contained in:
sinohqb 2026-08-04 02:50:26 +08:00
parent ef4c094082
commit b26c432f3a
2 changed files with 16 additions and 31 deletions

View File

@ -7,9 +7,11 @@ interface ChatBubbleProps {
content: ReactNode content: ReactNode
meta?: ReactNode meta?: ReactNode
pending?: boolean pending?: boolean
/** 镜像布局:探索下钻里虚拟用户居左、被评对象居右(与常规聊天相反) */
mirrored?: boolean
} }
export default function ChatBubble({ role, content, meta, pending }: ChatBubbleProps) { export default function ChatBubble({ role, content, meta, pending, mirrored }: ChatBubbleProps) {
const isUser = role === 'user' const isUser = role === 'user'
const isAgent = role === 'agent' const isAgent = role === 'agent'
const isSystem = role === 'system' const isSystem = role === 'system'
@ -17,8 +19,9 @@ export default function ChatBubble({ role, content, meta, pending }: ChatBubbleP
const bg = isUser ? colors.chatUser : isAgent ? colors.chatAgent : colors.chatSystemError const bg = isUser ? colors.chatUser : isAgent ? colors.chatAgent : colors.chatSystemError
const border = isSystem ? '#ffccc7' : 'transparent' const border = isSystem ? '#ffccc7' : 'transparent'
const textColor = isSystem ? '#cf1322' : colors.text const textColor = isSystem ? '#cf1322' : colors.text
const align = isUser ? 'flex-end' : 'flex-start' const alignRight = isSystem ? false : isUser ? !mirrored : !!mirrored
const radius = isUser ? '10px 10px 2px 10px' : '10px 10px 10px 2px' const align = alignRight ? 'flex-end' : 'flex-start'
const radius = alignRight ? '10px 10px 2px 10px' : '10px 10px 10px 2px'
const icon = isUser const icon = isUser
? <UserOutlined style={{ color: colors.primary }} /> ? <UserOutlined style={{ color: colors.primary }} />
@ -29,7 +32,7 @@ export default function ChatBubble({ role, content, meta, pending }: ChatBubbleP
return ( return (
<div style={{ display: 'flex', justifyContent: align, marginBottom: 6 }}> <div style={{ display: 'flex', justifyContent: align, marginBottom: 6 }}>
<div style={{ maxWidth: '82%', display: 'flex', flexDirection: 'column', alignItems: isUser ? 'flex-end' : 'flex-start', gap: 2 }}> <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 }}> <div style={{ display: 'flex', gap: 4, alignItems: 'center', fontSize: 11, color: colors.textMuted }}>
{!isUser && icon} {!isUser && icon}
<span>{label}</span> <span>{label}</span>

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Collapse, Empty, Space, Spin, Tag } from 'antd' import { Collapse, Empty, Space, Spin, Tag } from 'antd'
import { UserOutlined, RobotOutlined } from '@ant-design/icons' import ChatBubble from './ChatBubble'
import { import {
explorationApi, explorationApi,
type ExplorationMessage, type ExplorationMessage,
@ -78,33 +78,15 @@ function SessionDetail({ session, messages }: {
<Empty description="暂无对话记录" image={Empty.PRESENTED_IMAGE_SIMPLE} /> <Empty description="暂无对话记录" image={Empty.PRESENTED_IMAGE_SIMPLE} />
)} )}
{messages && messages.map((m) => ( {messages && messages.map((m) => (
<div <ChatBubble
key={m.id} key={m.id}
style={{ display: 'flex', justifyContent: m.role === 'user' ? 'flex-start' : 'flex-end', marginBottom: 8 }} role={m.role === 'user' ? 'user' : 'agent'}
> mirrored
{m.role === 'user' ? ( content={m.content}
<div style={{ meta={m.role !== 'user' && m.latency_ms != null ? (
background: colors.chatUser, padding: '8px 14px', <span style={{ color: colors.textMuted, fontSize: 11 }}>{m.latency_ms}ms</span>
borderRadius: '12px 12px 12px 2px', maxWidth: '70%', ) : undefined}
fontSize: 13, lineHeight: 1.6, />
}}>
<UserOutlined style={{ marginRight: 6, color: '#1677ff' }} />
{m.content}
</div>
) : (
<div style={{
background: colors.chatAgent, padding: '8px 14px',
borderRadius: '12px 12px 2px 12px', maxWidth: '70%',
fontSize: 13, lineHeight: 1.6,
}}>
<RobotOutlined style={{ marginRight: 6, color: '#52c41a' }} />
{m.content}
{m.latency_ms != null && (
<span style={{ color: colors.textMuted, fontSize: 11, marginLeft: 8 }}>{m.latency_ms}ms</span>
)}
</div>
)}
</div>
))} ))}
{experience && ( {experience && (