import { useState, useRef, useEffect, useCallback } from "react"; import DraggableContainer from "./DraggableContainer"; import PromptInput from "./PromptInput"; import GlassButton from "./GlassButton"; import GlassContainer from "./GlassContainer"; import { useVLMContext } from "../context/useVLMContext"; import { PROMPTS, GLASS_EFFECTS } from "../constants"; import type { ImageAnalysisResult } from "../types"; interface ImageAnalysisViewProps { images: File[]; onBackToUpload: () => void; } export default function ImageAnalysisView({ images, onBackToUpload }: ImageAnalysisViewProps) { const [results, setResults] = useState([]); const [currentPrompt, setCurrentPrompt] = useState(PROMPTS.default); const [isAnalyzing, setIsAnalyzing] = useState(false); const [currentImageIndex, setCurrentImageIndex] = useState(0); const [selectedImageUrl, setSelectedImageUrl] = useState(""); const [copyStatus, setCopyStatus] = useState<{ [key: string]: 'success' | 'error' | null }>({}); const [copyAllStatus, setCopyAllStatus] = useState<'success' | 'error' | null>(null); const { isLoaded, runInference } = useVLMContext(); const abortControllerRef = useRef(null); // Create preview URL for selected image useEffect(() => { if (images[currentImageIndex]) { const url = URL.createObjectURL(images[currentImageIndex]); setSelectedImageUrl(url); return () => URL.revokeObjectURL(url); } }, [images, currentImageIndex]); const analyzeAllImages = useCallback(async () => { if (!isLoaded || isAnalyzing) return; setIsAnalyzing(true); setResults([]); abortControllerRef.current?.abort(); abortControllerRef.current = new AbortController(); const analysisResults: ImageAnalysisResult[] = []; try { for (let i = 0; i < images.length; i++) { if (abortControllerRef.current.signal.aborted) break; setCurrentImageIndex(i); const file = images[i]; try { const caption = await runInference(file, currentPrompt); analysisResults.push({ file, caption }); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); analysisResults.push({ file, caption: "", error: errorMsg }); } setResults([...analysisResults]); } } catch (error) { console.error("Analysis interrupted:", error); } finally { setIsAnalyzing(false); } }, [images, currentPrompt, isLoaded, runInference, isAnalyzing]); const handlePromptChange = useCallback((prompt: string) => { setCurrentPrompt(prompt); }, []); const handleImageSelect = useCallback((index: number) => { setCurrentImageIndex(index); }, []); const copyToClipboard = useCallback(async (text: string, itemKey?: string) => { try { await navigator.clipboard.writeText(text); if (itemKey) { setCopyStatus(prev => ({ ...prev, [itemKey]: 'success' })); setTimeout(() => setCopyStatus(prev => ({ ...prev, [itemKey]: null })), 2000); } return true; } catch (error) { console.error('Failed to copy text:', error); try { // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = text; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); if (itemKey) { setCopyStatus(prev => ({ ...prev, [itemKey]: 'success' })); setTimeout(() => setCopyStatus(prev => ({ ...prev, [itemKey]: null })), 2000); } return true; } catch (fallbackError) { if (itemKey) { setCopyStatus(prev => ({ ...prev, [itemKey]: 'error' })); setTimeout(() => setCopyStatus(prev => ({ ...prev, [itemKey]: null })), 2000); } return false; } } }, []); const stopAnalysis = useCallback(() => { abortControllerRef.current?.abort(); setIsAnalyzing(false); }, []); const copyAllCaptions = useCallback(async () => { const captionsText = results .filter(result => result.caption && !result.error) .map((result, index) => `Image ${index + 1} (${result.file.name}): ${result.caption}`) .join('\n\n'); if (captionsText) { const success = await copyToClipboard(captionsText); setCopyAllStatus(success ? 'success' : 'error'); setTimeout(() => setCopyAllStatus(null), 2000); } }, [results, copyToClipboard]); useEffect(() => { return () => { abortControllerRef.current?.abort(); }; }, []); return (
{/* Main image display */}
{/* Image preview */}
{selectedImageUrl && ( {`Preview )}
{/* Sidebar with image thumbnails and results */}
{/* Controls */}
Back to Upload {!isAnalyzing ? ( Analyze All ) : ( Stop )}
{results.length > 0 && !isAnalyzing && (
r.caption && !r.error).length === 0} > {copyAllStatus === 'success' ? ( <> Copied! ) : copyAllStatus === 'error' ? ( <> Failed to Copy ) : ( <> Copy All Captions )}
)} {isAnalyzing && (
Analyzing image {currentImageIndex + 1} of {images.length}...
)}
{/* Image list with results */}
{images.map((file, index) => { const result = results.find(r => r.file === file); const isSelected = index === currentImageIndex; const isProcessing = isAnalyzing && index === currentImageIndex; return (
handleImageSelect(index)} >
{/* Thumbnail */}
{file.name} URL.revokeObjectURL((e.target as HTMLImageElement).src)} />
{/* Content */}
{file.name}
{isProcessing && (
Processing...
)} {result && (
{result.error ? (
Error: {result.error}
) : (
{result.caption}
{result.caption && ( )}
)}
)}
); })}
{/* Draggable Prompt Input - Bottom Left */}
); }