From d915fcd61abf39330e3159af5cd608cf4d324bcc Mon Sep 17 00:00:00 2001 From: duanshuwen Date: Wed, 22 Apr 2026 22:37:26 +0800 Subject: [PATCH] feat: add avatars for user and assistant in chat message list --- src/assets/images/ai_avatar.png | Bin 0 -> 3653 bytes src/assets/images/me_avatar.png | Bin 0 -> 1616 bytes src/components/chat/ChatMessageList.tsx | 220 ++++++++++++++++++------ 3 files changed, 168 insertions(+), 52 deletions(-) create mode 100644 src/assets/images/ai_avatar.png create mode 100644 src/assets/images/me_avatar.png diff --git a/src/assets/images/ai_avatar.png b/src/assets/images/ai_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..5c577165b6a30ea5b2370863e469d99d5739b694 GIT binary patch literal 3653 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 X3ShY{4>|sX00000NkvXXu0mjf^b9Q{ literal 0 HcmV?d00001 diff --git a/src/assets/images/me_avatar.png b/src/assets/images/me_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..8802f9440a04cb41911470482e2512efa2248f93 GIT binary patch literal 1616 zcmYLI2~bm46pcm-g9TG`Fn|<9*#wnEgh{nZfFgp(R#~(n3Ml9Z5(K3RG-{zEsVpi| z5J5#0F^-_2f+1{Lm7s>rtR%7}6vC2#VA6Q|KBeuQ``>&2+;i@|@6P|f1o(UF=^E?e za5z1m9iBmG--10lcnq#~JQ({3%wCw71*QN-=AfPn^&(&))eJo2L6Zo^W)^4-0$>S1 zJ_T|l5kj*NCTF1$5zxwqX95_V#+XRR;=`yCSR!C@F$uyOk?GX(h3NdC&_$Ziltj=X zg4RB06T@>cyb!}55_lKLG>CCt+|BhNfV63Zx2_|6tsr#dy4ymbUiNrFwdL1Oj2j3PVE^6Em|lYc?TsadFwa z*$o+)O!n}I*t<6-=J4T!#KgoCCz6tqQd3Xm&}f$~6&Dwml$4j3-?>Al(`##M84N~K zQ&VeO+w(b(t*)%L}gH#xtqewA-^h%agQK^C*O zy@vnul_yTdE?XJ|p9U8Hv~0GN!VsPMIw&rwo|KZNvG&g745TL{G#tf;56bv1gzOe` z1Gjh569I=hX7#mNbI+g4Vg%#YixOT0v~rvMt$Si8_$Fl2a!noY-c{}0er0rS;xbzv zUlr9{y2s>|uu;c8drr8P=SI9>$!VrV$5NKH)VSBtaw5(Kh8fO0$>Wsje9qdj?Lp1# z_M4VUFJ?r~MQ2;R{H(FZ{k>5-{YB)Wja^IQvi*}ePEv~FHOnNDtBusap_nTm`;t5y ze537zxAS*3*>~^q-G9M6^aFM02L0OBj>kvTM0RJ)29C4CMR}RluO1Fvy8Ft@nvcD7 z_bfGM2a!T;tfLI434xKs*wggOS>dNbmy0GXmUz)OX*XvU>|3OhOuy|xNDTaO33Xj) z6`3F}+n-&v@18ElUqQZ2u{5>V_-XaJqzVhqbdr8xsa?nRX-69QvOPs>t5#sPIR`7| zPR@-<-e6m~`qo!F`k(5q3`xqRb^5$C;OV;Fi(a0~^iH=~n-p`rOllA?S+H{F2=z_j zm8C^)AyWIerI#pPg z%TDPEW>YH@7` z%6G2pV@?>yAJZ_rBT1XzeEIFRk1fjuy#-#b$NN-GjcX<{UT`aKtm4hi^>(~>$!p2j X#4R{0rv?w9{~PDy) { + return classes.filter(Boolean).join(' '); +} + +function AttachmentPreview({ + messageRole, + attachment, +}: { + messageRole: ChatMessageItem['role']; + attachment: NonNullable[number]; +}) { + const isImage = attachment.mimeType.startsWith('image/'); + const imageSizeClass = messageRole === 'user' ? 'h-24 w-24' : 'h-28 w-28'; + + if (isImage && attachment.preview) { + return ( +
+ {attachment.fileName} +
+ ); + } + + return ( +
+ {isImage ? : } + {attachment.fileName} +
+ ); +} + +function AssistantMeta({ + content, + time, +}: { + content: string; + time: string; +}) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + if (!content.trim() || !navigator?.clipboard?.writeText) { + return; + } + + await navigator.clipboard.writeText(content); + setCopied(true); + window.setTimeout(() => setCopied(false), 1500); + }; + + return ( +
+ {time} + {content.trim() ? ( + + ) : null} +
+ ); +} + function ChatMessageList({ messages, loading, showWelcomeState }: ChatMessageListProps) { const containerRef = useRef(null); const { t } = useI18n(); @@ -18,13 +98,13 @@ function ChatMessageList({ messages, loading, showWelcomeState }: ChatMessageLis const container = containerRef.current; if (!container) return; container.scrollTop = container.scrollHeight; - }, [messages]); + }, [loading, messages]); return ( -
-
+
+
{loading ? ( -
+
{t('conversation.messageList.loading')}
) : null} @@ -32,61 +112,97 @@ function ChatMessageList({ messages, loading, showWelcomeState }: ChatMessageLis {messages.map((message) => (
-
- {message.role === 'assistant' - ? t('conversation.messageList.assistantBadge') - : t('conversation.messageList.userBadge')} -
- -
-
-
{message.name}
-
{message.time}
+ {message.role === 'assistant' ? ( +
+ aiAvatar
-

{message.content}

- {message.attachments && message.attachments.length > 0 ? ( -
- {message.attachments.map((attachment, index) => { - const attachmentKey = attachment.filePath || `${attachment.fileName}-${index}`; - const isImage = attachment.mimeType.startsWith('image/') && Boolean(attachment.preview); + ) : null} - if (isImage && attachment.preview) { - return ( -
- {attachment.fileName} -
- ); - } +
+ {message.role === 'assistant' && message.isStreaming ? ( + <> +
+ {t('conversation.messageList.streaming')} +
+ + + ) : null} - return ( -
- {attachment.fileName} -
- ); - })} + {message.role === 'user' && message.attachments && message.attachments.length > 0 ? ( +
+ {message.attachments.map((attachment, index) => ( + + ))}
) : null} - {message.isStreaming ? ( -
{t('conversation.messageList.streaming')}
+ + {message.content ? ( +
+

+ {message.content} +

+
) : null} + + {message.role === 'assistant' && message.attachments && message.attachments.length > 0 ? ( +
+ {message.attachments.map((attachment, index) => ( + + ))} +
+ ) : null} + + {message.role === 'assistant' ? ( + + ) : ( +
+ {message.time} +
+ )}
+ + {message.role === 'user' ? ( +
+ meAvatar +
+ ) : null}
))}