import { ArrowUp, Camera, ImagePlus, Loader2, Plus } from 'lucide-react'; import { type ChangeEvent, useRef, useState } from 'react'; import { canUseNativeHostCapability, captureHostImageFile, type HostFileImportImageResult, importHostImageFile, } from '../../services/host-bridge/hostBridge'; import { puzzleReferenceImageDataUrlToFile, readPuzzleReferenceImageAsDataUrl, } from '../../services/puzzleReferenceImage'; import { PlatformIconButton } from '../common/PlatformIconButton'; import { PlatformStatusMessage } from '../common/PlatformStatusMessage'; import { PlatformSubpanel } from '../common/PlatformSubpanel'; import { PlatformTextField } from '../common/PlatformTextField'; import { PlatformUploadPreviewCard } from '../common/PlatformUploadPreviewCard'; export type CreativeAgentComposerImage = { imageUrl: string; thumbnailUrl: string; label: string; }; type CreativeAgentInputComposerProps = { isBusy: boolean; variant?: 'panel' | 'floating'; placeholder?: string; onSubmit: (payload: { text: string; image: CreativeAgentComposerImage | null; }) => void; }; export function CreativeAgentInputComposer({ isBusy, variant = 'panel', placeholder = '想做成什么拼图?', onSubmit, }: CreativeAgentInputComposerProps) { const [text, setText] = useState(''); const [image, setImage] = useState(null); const [imageError, setImageError] = useState(null); const imageInputRef = useRef(null); const canImportHostImage = canUseNativeHostCapability('file.importImage'); const canCaptureHostImage = canUseNativeHostCapability('file.captureImage'); const canSubmit = !isBusy && Boolean(text.trim() || image); const hostImageImportResultToFile = (result: HostFileImportImageResult) => puzzleReferenceImageDataUrlToFile( `data:${result.mimeType};base64,${result.base64Data}`, result.fileName, ); const setReferenceImageFromFile = async (file: File) => { try { const dataUrl = await readPuzzleReferenceImageAsDataUrl(file); setImage({ imageUrl: dataUrl, thumbnailUrl: dataUrl, label: file.name.trim() || '参考图', }); setImageError(null); } catch (error) { setImageError( error instanceof Error ? error.message : '参考图读取失败,请重试。', ); } }; const handleImageChange = async (event: ChangeEvent) => { const file = event.target.files?.[0]; event.currentTarget.value = ''; if (!file) { return; } await setReferenceImageFromFile(file); }; const openReferenceImagePicker = () => { if (isBusy) { return; } if (!canImportHostImage) { imageInputRef.current?.click(); return; } void (async () => { const importedImageFile = await importHostImageFile(); if (!importedImageFile) { return; } await setReferenceImageFromFile( hostImageImportResultToFile(importedImageFile), ); })(); }; const captureReferenceImage = () => { if (isBusy || !canCaptureHostImage) { return; } void (async () => { const capturedImageFile = await captureHostImageFile(); if (!capturedImageFile) { return; } await setReferenceImageFromFile( hostImageImportResultToFile(capturedImageFile), ); })(); }; const submit = () => { if (!canSubmit) { return; } onSubmit({ text: text.trim(), image, }); setText(''); setImage(null); setImageError(null); }; const floating = variant === 'floating'; const composerContent = ( <>
{floating ? ( ) : ( )} { void handleImageChange(event); }} className="hidden" /> } /> {canCaptureHostImage ? ( } /> ) : null} setText(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { event.preventDefault(); submit(); } }} className="min-h-11 flex-1" placeholder={placeholder} aria-label="智能创作输入" /> ) : ( ) } />
{image ? ( setImage(null)} className="mt-3" removeButtonProps={{ title: '移除参考图' }} /> ) : null} {imageError ? ( {imageError} ) : null} ); if (!floating) { return ( {composerContent} ); } return (
{composerContent}
); }