"use client"; import { ArrowUp, Check, ChevronDown, ImagePlus, LoaderCircle, X } from "lucide-react"; import { useEffect, useMemo, useRef, useState, type ClipboardEvent, type RefObject } from "react"; import { ImageLightbox } from "@/components/image-lightbox"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; type ImageComposerProps = { prompt: string; imageCount: string; imageSize: string; availableQuota: string; activeTaskCount: number; referenceImages: Array<{ name: string; dataUrl: string }>; textareaRef: RefObject; fileInputRef: RefObject; onPromptChange: (value: string) => void; onImageCountChange: (value: string) => void; onImageSizeChange: (value: string) => void; onSubmit: () => void | Promise; onPickReferenceImage: () => void; onReferenceImageChange: (files: File[]) => void | Promise; onRemoveReferenceImage: (index: number) => void; }; export function ImageComposer({ prompt, imageCount, imageSize, availableQuota, activeTaskCount, referenceImages, textareaRef, fileInputRef, onPromptChange, onImageCountChange, onImageSizeChange, onSubmit, onPickReferenceImage, onReferenceImageChange, onRemoveReferenceImage, }: ImageComposerProps) { const [lightboxOpen, setLightboxOpen] = useState(false); const [lightboxIndex, setLightboxIndex] = useState(0); const [isSizeMenuOpen, setIsSizeMenuOpen] = useState(false); const [sizeMenuPos, setSizeMenuPos] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); const sizeMenuRef = useRef(null); const sizeMenuBtnRef = useRef(null); const lightboxImages = useMemo( () => referenceImages.map((image, index) => ({ id: `${image.name}-${index}`, src: image.dataUrl })), [referenceImages], ); const imageSizeOptions = [ { value: "", label: "未指定" }, { value: "1:1", label: "1:1 (正方形)" }, { value: "16:9", label: "16:9 (横版)" }, { value: "4:3", label: "4:3 (横版)" }, { value: "3:4", label: "3:4 (竖版)" }, { value: "9:16", label: "9:16 (竖版)" }, ]; const imageSizeLabel = imageSizeOptions.find((option) => option.value === imageSize)?.label || "未指定"; useEffect(() => { if (!isSizeMenuOpen) { return; } const handlePointerDown = (event: MouseEvent) => { if (!sizeMenuRef.current?.contains(event.target as Node)) { setIsSizeMenuOpen(false); } }; window.addEventListener("mousedown", handlePointerDown); return () => { window.removeEventListener("mousedown", handlePointerDown); }; }, [isSizeMenuOpen]); const handleTextareaPaste = (event: ClipboardEvent) => { const imageFiles = Array.from(event.clipboardData.files).filter((file) => file.type.startsWith("image/")); if (imageFiles.length === 0) { return; } event.preventDefault(); void onReferenceImageChange(imageFiles); }; return (
{ void onReferenceImageChange(Array.from(event.target.files || [])); }} /> {referenceImages.length > 0 ? (
{referenceImages.map((image, index) => (
))}
) : null}
{ textareaRef.current?.focus(); }} >