| import React, { useState, useEffect, useRef } from 'react'; |
| import { SlidersHorizontal, Sparkles, Brain } from 'lucide-react'; |
|
|
| const ImagePreview = ({ imageData, fileName, onResolutionChange, onEnhanceToggle, isEnhanced, onReasoningModeToggle, useReasoning }) => { |
| const [resolution, setResolution] = useState(100); |
| const canvasRef = useRef(null); |
| const [originalDimensions, setOriginalDimensions] = useState({ width: 0, height: 0 }); |
| const [currentDimensions, setCurrentDimensions] = useState({ width: 0, height: 0 }); |
|
|
| useEffect(() => { |
| if (!imageData || !canvasRef.current) return; |
|
|
| const canvas = canvasRef.current; |
| const ctx = canvas.getContext('2d'); |
| const img = new Image(); |
|
|
| img.onload = () => { |
| if (!originalDimensions.width) { |
| setOriginalDimensions({ width: img.width, height: img.height }); |
| } |
|
|
| |
| const newWidth = Math.floor(img.width * (resolution / 100)); |
| const newHeight = Math.floor(img.height * (resolution / 100)); |
| |
| setCurrentDimensions({ width: newWidth, height: newHeight }); |
|
|
| |
| const displayWidth = Math.min(400, newWidth); |
| const displayHeight = Math.floor(newHeight * (displayWidth / newWidth)); |
|
|
| canvas.width = displayWidth; |
| canvas.height = displayHeight; |
|
|
| |
| ctx.drawImage(img, 0, 0, displayWidth, displayHeight); |
|
|
| |
| if (onResolutionChange) { |
| const resizedCanvas = document.createElement('canvas'); |
| resizedCanvas.width = newWidth; |
| resizedCanvas.height = newHeight; |
| const resizedCtx = resizedCanvas.getContext('2d'); |
| resizedCtx.drawImage(img, 0, 0, newWidth, newHeight); |
| const resizedDataUrl = resizedCanvas.toDataURL('image/jpeg', 0.95); |
| onResolutionChange(resizedDataUrl, resolution); |
| } |
| }; |
|
|
| img.src = imageData; |
| }, [imageData, resolution]); |
|
|
| const handleResolutionChange = (e) => { |
| const newResolution = parseInt(e.target.value); |
| setResolution(newResolution); |
| }; |
|
|
| return ( |
| <div className="bg-white rounded-lg shadow-md p-4 space-y-3"> |
| <div className="flex items-center justify-between"> |
| <h4 className="text-sm font-semibold text-gray-700">Preview: {fileName}</h4> |
| <span className="text-xs text-gray-500"> |
| {currentDimensions.width} × {currentDimensions.height}px |
| </span> |
| </div> |
| |
| <div className="bg-gray-50 rounded-lg p-3 flex justify-center"> |
| <canvas ref={canvasRef} className="rounded shadow-sm" /> |
| </div> |
| |
| {/* Enhance Button */} |
| <button |
| onClick={() => onEnhanceToggle && onEnhanceToggle()} |
| className={`w-full py-2 px-4 rounded-lg font-medium transition-all flex items-center justify-center gap-2 ${ |
| isEnhanced |
| ? 'bg-purple-600 hover:bg-purple-700 text-white shadow-lg' |
| : 'bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white shadow-md' |
| }`} |
| > |
| <Sparkles className="w-4 h-4" /> |
| {isEnhanced ? 'Enhanced ✓' : 'Enhance Image'} |
| </button> |
| |
| {isEnhanced && ( |
| <div className="bg-purple-50 border border-purple-200 rounded p-2 text-xs text-purple-700"> |
| ✨ Image will be enhanced with OpenCV (CLAHE, denoising, sharpening) before processing |
| </div> |
| )} |
| |
| {/* Reasoning Mode Toggle */} |
| <button |
| onClick={() => onReasoningModeToggle && onReasoningModeToggle()} |
| className={`w-full py-2 px-4 rounded-lg font-medium transition-all flex items-center justify-center gap-2 ${ |
| useReasoning |
| ? 'bg-blue-600 hover:bg-blue-700 text-white shadow-lg' |
| : 'bg-gradient-to-r from-blue-500 to-cyan-500 hover:from-blue-600 hover:to-cyan-600 text-white shadow-md' |
| }`} |
| > |
| <Brain className="w-4 h-4" /> |
| {useReasoning ? 'Reasoning ✓' : 'Simple Mode'} |
| </button> |
| |
| {useReasoning && ( |
| <div className="bg-blue-50 border border-blue-200 rounded p-2 text-xs text-blue-700"> |
| 🧠 VLM will use 2-step reasoning: first analyze document structure, then extract fields |
| </div> |
| )} |
| |
| <div className="space-y-2"> |
| <div className="flex items-center justify-between"> |
| <label className="text-sm font-medium text-gray-700 flex items-center gap-2"> |
| <SlidersHorizontal className="w-4 h-4" /> |
| Resolution |
| </label> |
| <span className="text-sm font-bold text-primary-600">{resolution}%</span> |
| </div> |
| |
| <input |
| type="range" |
| min="10" |
| max="100" |
| value={resolution} |
| onChange={handleResolutionChange} |
| className="w-full h-2 rounded-lg cursor-pointer" |
| style={{ accentColor: '#2563eb' }} // Tailwind blue-600 |
| /> |
| |
| |
| <div className="flex justify-between text-xs text-gray-500"> |
| <span>Low Quality</span> |
| <span>Original</span> |
| </div> |
| |
| {resolution < 100 && ( |
| <div className="bg-blue-50 border border-blue-200 rounded p-2 text-xs text-blue-700"> |
| 💡 Lower resolution = faster processing & lower cost |
| </div> |
| )} |
| </div> |
| |
| <div className="text-xs text-gray-500 space-y-1"> |
| <div className="flex justify-between"> |
| <span>Original:</span> |
| <span className="font-medium">{originalDimensions.width} × {originalDimensions.height}px</span> |
| </div> |
| <div className="flex justify-between"> |
| <span>Processing:</span> |
| <span className="font-medium text-primary-600">{currentDimensions.width} × {currentDimensions.height}px</span> |
| </div> |
| </div> |
| </div> |
| ); |
| }; |
|
|
| export default ImagePreview; |
|
|