返回 oh-my-ppt
MessageBubble.tsx
根目录 / src / renderer / src / components / session-detail / ai-panel / MessageBubble.tsx
1 import dayjs from 'dayjs'
2 import { Image as ImageIcon, Video } from 'lucide-react'
3 import { cn } from '@renderer/lib/utils'
4 import type { Message } from '@renderer/store'
5 import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/Tooltip'
6 import { useT } from '@renderer/i18n'
7
8 export function MessageBubble({ message }: { message: Message }): React.JSX.Element {
9 const t = useT()
10 const cleanMessageContent = (content: string): string =>
11 content.replace(
12 /[((](?:目标)?选择器[::]\s*[^)\n]{8,}[))]/g,
13 t('sessionDetail.selectorLocated')
14 )
15 const isUser = message.role === 'user'
16 const selectorText =
17 typeof message.selector === 'string' && message.selector.trim().length > 0
18 ? message.selector.trim()
19 : ''
20 const imagePaths = Array.isArray(message.image_paths)
21 ? message.image_paths
22 .map((item) => String(item || '').trim())
23 .filter((item) => item.startsWith('./images/'))
24 .slice(0, 10)
25 : []
26 const videoPaths = Array.isArray(message.video_paths)
27 ? message.video_paths
28 .map((item) => String(item || '').trim())
29 .filter((item) => item.startsWith('./videos/'))
30 .slice(0, 10)
31 : []
32 const mediaPaths = [...imagePaths, ...videoPaths]
33
34 return (
35 <div className={cn('flex w-full min-w-0', isUser ? 'justify-end' : 'justify-start')}>
36 <div
37 className={cn(
38 'min-w-0 overflow-hidden rounded-[1.15rem] border px-3 py-2 shadow-[0_6px_14px_rgba(74,59,42,0.08)]',
39 selectorText ? 'w-full max-w-[238px]' : 'w-fit max-w-[238px]',
40 isUser
41 ? 'border-[#d6e3c8]/78 bg-[#fbfef6]/90 text-[#34402c]'
42 : 'border-[#ded2bd]/78 bg-[#fffaf1]/88 text-[#3f372b]'
43 )}
44 >
45 <div className="space-y-1">
46 {isUser && selectorText && (
47 <Tooltip>
48 <TooltipTrigger asChild>
49 <div className="flex w-full min-w-0 items-center overflow-hidden rounded-full border border-[#c7d9b4]/62 bg-[#e6f1dc]/72 px-1.5 py-0.5 text-[10px] font-semibold tracking-[0.08em] text-[#4b5f3b]">
50 <span className="mr-1 shrink-0">{t('sessionDetail.selectorBadge')}</span>
51 <span className="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap font-normal tracking-normal">
52 {selectorText}
53 </span>
54 </div>
55 </TooltipTrigger>
56 <TooltipContent className="whitespace-pre-wrap break-all">
57 {selectorText}
58 </TooltipContent>
59 </Tooltip>
60 )}
61 {isUser && mediaPaths.length > 0 && (
62 <div className="flex flex-wrap gap-1.5">
63 {mediaPaths.map((mediaPath) => (
64 <Tooltip key={mediaPath}>
65 <TooltipTrigger asChild>
66 <span className="inline-flex max-w-full items-center gap-1 rounded-full border border-[#c7d9b4]/62 bg-[#e6f1dc]/72 px-1.5 py-0.5 text-[10px] font-medium text-[#4b5f3b]">
67 {mediaPath.startsWith('./videos/') ? (
68 <Video className="h-3 w-3 shrink-0" />
69 ) : (
70 <ImageIcon className="h-3 w-3 shrink-0" />
71 )}
72 <span className="min-w-0 max-w-[140px] overflow-hidden text-ellipsis whitespace-nowrap">
73 {mediaPath}
74 </span>
75 </span>
76 </TooltipTrigger>
77 <TooltipContent className="whitespace-pre-wrap break-all">
78 {mediaPath}
79 </TooltipContent>
80 </Tooltip>
81 ))}
82 </div>
83 )}
84 <p className="whitespace-pre-wrap break-words text-[13px] leading-5">
85 {cleanMessageContent(message.content)}
86 </p>
87 <p className="text-[11px] leading-4 text-muted-foreground">
88 {dayjs(message.created_at * 1000).format('YYYY-MM-DD HH:mm:ss')}
89 </p>
90 </div>
91 </div>
92 </div>
93 )
94 }
95
95 lines Plain Text