返回 AiToEarn
1 /**
2 * MediaMentionPromptText - 只读提示词媒体提及渲染
3 * 将提示词中的 @Image / @Video / @Audio 资源引用展示为内联 chip。
4 */
5
6 'use client'
7
8 import type { ReactNode } from 'react'
9 import { FileImage, Music, Play } from 'lucide-react'
10 import { memo, useMemo } from 'react'
11 import { OssImage } from '@/components/common/OssImage'
12 import { useVideoThumbnail } from '@/hooks/useVideoThumbnail'
13 import { cn } from '@/utils/className'
14 import { getOssUrl } from '@/utils/oss'
15 import { getDraftBoxMediaFileName } from '../../utils/mediaFileName'
16 import styles from './MediaMentionPromptText.module.scss'
17
18 type MediaMentionType = 'image' | 'video' | 'audio'
19
20 interface MediaMentionAsset {
21 token: string
22 type: MediaMentionType
23 src: string
24 displayName: string
25 }
26
27 interface MediaMentionPromptTextProps {
28 prompt: string
29 imageUrls?: string[]
30 videoUrls?: string[]
31 audioUrls?: string[]
32 className?: string
33 compact?: boolean
34 }
35
36 const MEDIA_MENTION_TOKEN_PATTERN = /(@(?:Image[1-9]|Video[1-3]|Audio[1-3]))/g
37
38 function createMentionAssets(imageUrls: string[], videoUrls: string[], audioUrls: string[]) {
39 const assets = new Map<string, MediaMentionAsset>()
40
41 imageUrls.slice(0, 9).forEach((url, index) => {
42 const token = `@Image${index + 1}`
43 const src = getOssUrl(url)
44 assets.set(token, {
45 token,
46 type: 'image',
47 src,
48 displayName: getDraftBoxMediaFileName(url),
49 })
50 })
51
52 videoUrls.slice(0, 3).forEach((url, index) => {
53 const token = `@Video${index + 1}`
54 const src = getOssUrl(url)
55 assets.set(token, {
56 token,
57 type: 'video',
58 src,
59 displayName: getDraftBoxMediaFileName(url),
60 })
61 })
62
63 audioUrls.slice(0, 3).forEach((url, index) => {
64 const token = `@Audio${index + 1}`
65 const src = getOssUrl(url)
66 assets.set(token, {
67 token,
68 type: 'audio',
69 src,
70 displayName: getDraftBoxMediaFileName(url),
71 })
72 })
73
74 return assets
75 }
76
77 const MediaMentionChip = memo(({ asset, compact }: { asset: MediaMentionAsset, compact: boolean }) => {
78 const videoThumbnailUrl = useVideoThumbnail(asset.type === 'video' ? asset.src : null)
79 const title = `${asset.displayName} · ${asset.token}`
80
81 return (
82 <span className={cn(styles.chip, compact && styles.chipCompact)} title={title}>
83 <span className={cn(styles.thumb, asset.type === 'audio' && styles.audioThumb)} aria-hidden="true">
84 {asset.type === 'image'
85 ? (
86 <OssImage
87 src={asset.src}
88 alt=""
89 fill
90 className="object-cover"
91 sizes={compact ? '18px' : '20px'}
92 />
93 )
94 : asset.type === 'video' && videoThumbnailUrl
95 ? (
96 <OssImage
97 src={videoThumbnailUrl}
98 alt=""
99 fill
100 className="object-cover"
101 sizes={compact ? '18px' : '20px'}
102 unoptimized={videoThumbnailUrl.startsWith('data:')}
103 />
104 )
105 : asset.type === 'audio'
106 ? <Music className={styles.thumbIcon} />
107 : asset.type === 'video'
108 ? <Play className={styles.thumbIcon} />
109 : <FileImage className={styles.thumbIcon} />}
110 </span>
111 <span className={styles.name}>{asset.displayName}</span>
112 </span>
113 )
114 })
115
116 MediaMentionChip.displayName = 'MediaMentionChip'
117
118 const MediaMentionPromptText = memo(({
119 prompt,
120 imageUrls = [],
121 videoUrls = [],
122 audioUrls = [],
123 className,
124 compact = false,
125 }: MediaMentionPromptTextProps) => {
126 const assetsByToken = useMemo(
127 () => createMentionAssets(imageUrls, videoUrls, audioUrls),
128 [audioUrls, imageUrls, videoUrls],
129 )
130
131 const nodes = useMemo<ReactNode[]>(() => {
132 return prompt.split(MEDIA_MENTION_TOKEN_PATTERN).filter(Boolean).map((part, index) => {
133 const asset = assetsByToken.get(part)
134 if (!asset)
135 return part
136
137 return <MediaMentionChip key={`${part}-${index}`} asset={asset} compact={compact} />
138 })
139 }, [assetsByToken, compact, prompt])
140
141 return (
142 <span className={cn(styles.root, compact && styles.rootCompact, className)}>
143 {nodes}
144 </span>
145 )
146 })
147
148 MediaMentionPromptText.displayName = 'MediaMentionPromptText'
149
150 export default MediaMentionPromptText
151
151 lines Plain Text