/** * Hook for managing local file staging before sending to Hugging Face Space. */ import { useState, useCallback } from 'react'; import type { GenerationRouteState, ImageFile, ViewLabel } from '../types'; const VIEW_LABELS: ViewLabel[] = ['Front', 'Back', 'Left', 'Right', 'Top', 'Bottom']; const MAX_FILES = VIEW_LABELS.length; const REQUIRED_FILES = 4; interface UseUploadReturn { files: ImageFile[]; addFiles: (newFiles: File[]) => void; removeFile: (id: string) => void; reorderFiles: (fromIndex: number, toIndex: number) => void; beginSession: () => GenerationRouteState | null; error: string | null; clearError: () => void; } function relabelFiles(files: ImageFile[]): ImageFile[] { return files.map((image, index) => ({ ...image, label: VIEW_LABELS[index] ?? `View ${index + 1}`, })); } export function useUpload(): UseUploadReturn { const [files, setFiles] = useState([]); const [error, setError] = useState(null); const generateThumbnail = useCallback((file: File): Promise => { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = (e) => resolve(e.target?.result as string); reader.readAsDataURL(file); }); }, []); const createImageFile = useCallback(async (file: File, index: number): Promise => { const thumbnail = await generateThumbnail(file); return { id: `img-${Date.now()}-${index}`, file, thumbnail, filename: file.name, size: file.size, label: VIEW_LABELS[index] ?? `View ${index + 1}`, }; }, [generateThumbnail]); const addFiles = useCallback(async (newFiles: File[]) => { setError(null); const currentCount = files.length; const remainingSlots = MAX_FILES - currentCount; if (remainingSlots <= 0) return; const filesToAdd = newFiles.slice(0, remainingSlots); const newImageFiles = await Promise.all( filesToAdd.map((file, i) => createImageFile(file, currentCount + i)) ); setFiles((prev) => relabelFiles([...prev, ...newImageFiles])); }, [files.length, createImageFile]); const removeFile = useCallback((id: string) => { setFiles((prev) => relabelFiles(prev.filter((f) => f.id !== id))); }, []); const reorderFiles = useCallback((fromIndex: number, toIndex: number) => { setFiles((prev) => { const newFiles = [...prev]; const [removed] = newFiles.splice(fromIndex, 1); newFiles.splice(toIndex, 0, removed); return relabelFiles(newFiles); }); }, []); const beginSession = useCallback((): GenerationRouteState | null => { if (files.length < REQUIRED_FILES) { setError('Please provide at least Front, Back, Left, and Right views (4 images minimum).'); return null; } return { sourceImages: files.map((item) => item.file), }; }, [files]); const clearError = useCallback(() => { setError(null); }, []); return { files, addFiles, removeFile, reorderFiles, beginSession, error, clearError, }; }