Download web/src/components/chat/user-message.tsx from zlcy/study: direct link, hf CLI and curl.
- Browser
- Download file 5.71 kB
-
https://huggingface.co/spaces/zlcy/study/resolve/main/web/src/components/chat/user-message.tsx
- Command line
-
hf download hf://spaces/zlcy/study/web/src/components/chat/user-message.tsx
-
curl -L -o user-message.tsx https://huggingface.co/spaces/zlcy/study/resolve/main/web/src/components/chat/user-message.tsx
5.71 kB
| "use client"; | |
| import { Sparkles, Trash2 } from "lucide-react"; | |
| export type UserMessageMeta = { | |
| items: Array<{ label: string }>; | |
| }; | |
| type UserMessageProps = { | |
| /** 元信息行(第几轮 / 场景 / 平台 / 时间等) */ | |
| meta: string[]; | |
| /** 用户提示词文本 */ | |
| prompt: string; | |
| /** 参考图缩略图(dataURL 或 URL) */ | |
| referenceThumbnails: string[]; | |
| /** 参考图放大用原图(缺省回退缩略图) */ | |
| referenceFullImages?: string[]; | |
| /** 是否显示操作按钮 */ | |
| showActions?: boolean; | |
| onReusePrompt?: () => void; | |
| onReuseConfig?: () => void; | |
| onDeletePrompt?: () => void; | |
| onOpenLightbox?: (images: { id: string; src: string }[], index: number) => void; | |
| }; | |
| /** | |
| * 用户消息(画图页 / 商品视觉页共用) | |
| * | |
| * 右对齐纯文本 + 元信息行 + 复用提示词/复用配置/删除 + 参考图(可点击放大)。 | |
| * 两页面结构完全一致,仅数据不同,通过 props 泛化。 | |
| */ | |
| export function UserMessage({ | |
| meta, | |
| prompt, | |
| referenceThumbnails, | |
| referenceFullImages, | |
| showActions = true, | |
| onReusePrompt, | |
| onReuseConfig, | |
| onDeletePrompt, | |
| onOpenLightbox, | |
| }: UserMessageProps) { | |
| return ( | |
| <div className="flex justify-end"> | |
| <div className="max-w-[90%] px-1 py-1 text-[14px] leading-6 text-stone-900 sm:max-w-[82%] sm:text-[15px] sm:leading-7"> | |
| {meta.length > 0 && ( | |
| <div className="mb-1.5 flex flex-wrap justify-end gap-2 text-[11px] text-stone-400 sm:mb-2"> | |
| {meta.map((item, index) => ( | |
| <span key={index}>{item}</span> | |
| ))} | |
| </div> | |
| )} | |
| <div className="text-right">{prompt}</div> | |
| {showActions && (onReusePrompt || onReuseConfig || onDeletePrompt) && ( | |
| <div className="mt-2 flex flex-wrap justify-end gap-1.5"> | |
| {onReusePrompt && ( | |
| <button | |
| type="button" | |
| onClick={onReusePrompt} | |
| className="inline-flex items-center gap-1 rounded-full bg-stone-100 px-2.5 py-1 text-[11px] font-medium text-stone-600 transition hover:bg-stone-200 hover:text-stone-900" | |
| > | |
| 复用提示词 | |
| </button> | |
| )} | |
| {onReuseConfig && ( | |
| <button | |
| type="button" | |
| onClick={onReuseConfig} | |
| className="inline-flex items-center gap-1 rounded-full bg-stone-100 px-2.5 py-1 text-[11px] font-medium text-stone-600 transition hover:bg-stone-200 hover:text-stone-900" | |
| > | |
| 复用配置 | |
| </button> | |
| )} | |
| {onDeletePrompt && ( | |
| <button | |
| type="button" | |
| onClick={onDeletePrompt} | |
| className="inline-flex size-6 items-center justify-center rounded-full text-stone-300 transition hover:bg-rose-50 hover:text-rose-500" | |
| aria-label="删除提示词记录" | |
| > | |
| <Trash2 className="size-3" /> | |
| </button> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| /** | |
| * 本轮参考图(画图页 / 商品视觉页共用) | |
| * | |
| * 右侧缩略图列表,点击打开原图放大(Fancybox)。 | |
| */ | |
| export function ReferenceImages({ | |
| thumbnails, | |
| fullImages, | |
| taskPrefix, | |
| onOpenLightbox, | |
| onAddToEditor, | |
| }: { | |
| thumbnails: string[]; | |
| fullImages?: string[]; | |
| taskPrefix: string; | |
| onOpenLightbox: (images: { id: string; src: string }[], index: number) => void; | |
| onAddToEditor?: (src: string, index: number) => void | Promise<void>; | |
| }) { | |
| if (thumbnails.length === 0) return null; | |
| return ( | |
| <div className="flex justify-end"> | |
| <div className="flex flex-col items-end"> | |
| <div className="mb-2 text-xs font-medium text-stone-500">本轮参考图</div> | |
| <div className="flex flex-wrap justify-end gap-3"> | |
| {thumbnails.map((src, index) => { | |
| const fullSrc = fullImages?.[index] ?? src; | |
| return ( | |
| <div key={index} className="flex flex-col items-end gap-2"> | |
| <button | |
| type="button" | |
| onClick={() => { | |
| onOpenLightbox( | |
| thumbnails.map((s, i) => ({ | |
| id: `${taskPrefix}-ref-${i}`, | |
| src: fullImages?.[i] ?? s, | |
| })), | |
| index, | |
| ); | |
| }} | |
| className="group relative size-20 overflow-hidden rounded-lg border border-stone-200/80 bg-stone-100/60 text-left transition hover:border-stone-300 sm:size-24" | |
| aria-label={`预览参考图 ${index + 1}`} | |
| title="点击放大查看" | |
| > | |
| {/* eslint-disable-next-line @next/next/no-img-element */} | |
| <img | |
| src={src} | |
| alt={`参考图 ${index + 1}`} | |
| className="absolute inset-0 h-full w-full object-cover transition duration-200 group-hover:scale-[1.02]" | |
| /> | |
| </button> | |
| {onAddToEditor ? ( | |
| <button | |
| type="button" | |
| onClick={() => void onAddToEditor(fullSrc, index)} | |
| className="inline-flex items-center gap-1 rounded-full border border-stone-200 bg-white px-2.5 py-1 text-[11px] font-medium text-stone-700 transition hover:bg-stone-50" | |
| > | |
| <Sparkles className="size-3.5" /> | |
| 加入编辑 | |
| </button> | |
| ) : null} | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |