/** * FaceSwapRefInput — inline reference image picker for Face Swap. * * Additive component — rendered below the Face Swap button in IdentityTools * when user clicks Face Swap and no reference is yet set. * * Compact inline variant — upload zone + URL input. * Same pattern as AvatarStudio reference upload. */ import React, { useState, useCallback, useRef } from 'react'; import { Upload, X, Loader2, Check } from 'lucide-react'; import { resolveFileUrl } from '../resolveFileUrl'; // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- export function FaceSwapRefInput({ backendUrl, apiKey, onReferenceReady, onCancel, }) { const [uploading, setUploading] = useState(false); const [preview, setPreview] = useState(null); const [urlInput, setUrlInput] = useState(''); const [error, setError] = useState(null); const fileInputRef = useRef(null); const handleFileUpload = useCallback(async (file) => { setUploading(true); setError(null); // Show local preview immediately const localPreview = URL.createObjectURL(file); setPreview(localPreview); // Upload to backend const formData = new FormData(); formData.append('file', file); const base = (backendUrl || '').replace(/\/+$/, ''); const headers = {}; if (apiKey) headers['x-api-key'] = apiKey; try { const res = await fetch(`${base}/upload`, { method: 'POST', headers, body: formData, }); if (res.ok) { const data = await res.json(); const uploadedUrl = data.url || data.file_url || ''; if (uploadedUrl) { onReferenceReady(uploadedUrl); } else { setError('Upload succeeded but no URL returned'); } } else { setError(`Upload failed: ${res.status}`); } } catch (e) { setError(e instanceof Error ? e.message : 'Upload failed'); } finally { setUploading(false); } }, [backendUrl, apiKey, onReferenceReady]); const handleUrlSubmit = useCallback(() => { const trimmed = urlInput.trim(); if (trimmed) { onReferenceReady(trimmed); } }, [urlInput, onReferenceReady]); return (