Fix OCR configuration and error handling in scanner
🔧 Scanner OCR Fixes: - AutoScanningCamera now properly initializes OCR with saved API keys - Checks both localStorage and user preferences for OpenAI API key - Prevents auto-scanning when OCR is not configured - Better error messages for different failure scenarios: • 'OCR not configured' when no API key found • 'Invalid OpenAI API key' for 401 errors • 'API rate limit exceeded' for 429 errors • 'OCR service unavailable' for general failures 🎯 User Experience Improvements: - Yellow warning banner when OCR not configured - Manual scan button disabled until OCR configured - Clear instructions to configure API key in Settings - Fallback from OpenAI to Ollama if available - Extended error message display time (4 seconds) ✅ This should resolve the 'Scan failed, try again' issue Users now get clear guidance on what to do when OCR isn't working
This commit is contained in:
parent
cf05ea8daf
commit
e720ffdc70
1 changed files with 136 additions and 12 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useRef, useState, useEffect, useCallback } from 'react';
|
||||
import ScanningToast from './ScanningToast';
|
||||
import { aiCardOCR, type CardOCRResult } from '../../services/aiOcr';
|
||||
import { aiCardOCR, ollamaCardOCR, type CardOCRResult } from '../../services/aiOcr';
|
||||
|
||||
interface ScannedCard {
|
||||
id: string;
|
||||
|
|
@ -44,6 +44,60 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
|||
|
||||
const [queueCount, setQueueCount] = useState(0);
|
||||
const [recentScans, setRecentScans] = useState<Set<string>>(new Set());
|
||||
const [isOcrConfigured, setIsOcrConfigured] = useState(false);
|
||||
|
||||
// Initialize OCR services with saved settings
|
||||
useEffect(() => {
|
||||
const initializeOCR = async () => {
|
||||
try {
|
||||
// Check for saved API key in localStorage (from OCRSettings component)
|
||||
const savedApiKey = localStorage.getItem('openai_api_key');
|
||||
|
||||
// Also check user preferences (from Settings page)
|
||||
const token = localStorage.getItem('tcg_vault_token');
|
||||
if (token) {
|
||||
try {
|
||||
const response = await fetch('/api/user/preferences', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const userApiKey = data.preferences?.ocrSettings?.openai_api_key;
|
||||
|
||||
if (userApiKey) {
|
||||
aiCardOCR.setApiKey(userApiKey);
|
||||
setIsOcrConfigured(true);
|
||||
console.log('✅ OCR configured with user preferences API key');
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Could not load user preferences, checking localStorage...');
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to localStorage API key
|
||||
if (savedApiKey) {
|
||||
aiCardOCR.setApiKey(savedApiKey);
|
||||
setIsOcrConfigured(true);
|
||||
console.log('✅ OCR configured with localStorage API key');
|
||||
} else {
|
||||
console.log('❌ No OpenAI API key found. OCR scanning will not work.');
|
||||
setIsOcrConfigured(false);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error initializing OCR:', error);
|
||||
setIsOcrConfigured(false);
|
||||
}
|
||||
};
|
||||
|
||||
initializeOCR();
|
||||
}, []);
|
||||
|
||||
// Initialize camera
|
||||
useEffect(() => {
|
||||
|
|
@ -60,7 +114,7 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
|||
|
||||
// Auto-scan detection
|
||||
useEffect(() => {
|
||||
if (!isStreaming || !isActive) return;
|
||||
if (!isStreaming || !isActive || !isOcrConfigured) return;
|
||||
|
||||
const detectCardStabilization = () => {
|
||||
if (scanState.isProcessing || queueCount >= maxQueueSize) return;
|
||||
|
|
@ -115,7 +169,7 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
|||
clearTimeout(scanTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [isStreaming, isActive, scanState.isProcessing, queueCount, maxQueueSize]);
|
||||
}, [isStreaming, isActive, scanState.isProcessing, queueCount, maxQueueSize, isOcrConfigured]);
|
||||
|
||||
const startCamera = async () => {
|
||||
try {
|
||||
|
|
@ -180,6 +234,24 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
|||
const triggerScan = useCallback(async () => {
|
||||
if (scanState.isProcessing || queueCount >= maxQueueSize) return;
|
||||
|
||||
// Check if OCR is configured
|
||||
if (!isOcrConfigured) {
|
||||
setScanState(prev => ({
|
||||
...prev,
|
||||
isScanning: false,
|
||||
isProcessing: false,
|
||||
message: 'OCR not configured - please set up your API key in Settings',
|
||||
}));
|
||||
|
||||
setTimeout(() => {
|
||||
setScanState(prev => ({
|
||||
...prev,
|
||||
message: '',
|
||||
}));
|
||||
}, 4000);
|
||||
return;
|
||||
}
|
||||
|
||||
setScanState(prev => ({
|
||||
...prev,
|
||||
isScanning: false,
|
||||
|
|
@ -194,8 +266,23 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
|||
throw new Error('Failed to capture image');
|
||||
}
|
||||
|
||||
// Process with AI OCR
|
||||
const cardData = await aiCardOCR.analyzeCard(imageDataUrl);
|
||||
// Process with AI OCR - try OpenAI first, fallback to Ollama
|
||||
let cardData: CardOCRResult;
|
||||
|
||||
try {
|
||||
console.log('🤖 Trying OpenAI Vision API...');
|
||||
cardData = await aiCardOCR.analyzeCard(imageDataUrl);
|
||||
console.log('✅ OpenAI Vision result:', cardData);
|
||||
} catch (openaiError) {
|
||||
console.log('❌ OpenAI failed, trying Ollama:', openaiError);
|
||||
try {
|
||||
cardData = await ollamaCardOCR.analyzeCard(imageDataUrl);
|
||||
console.log('✅ Ollama Vision result:', cardData);
|
||||
} catch (ollamaError) {
|
||||
console.error('❌ All AI OCR methods failed');
|
||||
throw new Error('AI OCR services unavailable. Please configure OpenAI API key or run Ollama locally.');
|
||||
}
|
||||
}
|
||||
|
||||
// Create unique ID for this scan
|
||||
const cardId = `scan_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
|
@ -253,10 +340,24 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
|||
|
||||
} catch (error: any) {
|
||||
console.error('Scan processing error:', error);
|
||||
|
||||
let errorMessage = 'Scan failed, try again';
|
||||
|
||||
// Provide more specific error messages
|
||||
if (error.message.includes('OpenAI API key not configured')) {
|
||||
errorMessage = 'OpenAI API key not configured - check Settings';
|
||||
} else if (error.message.includes('OpenAI API error: 401')) {
|
||||
errorMessage = 'Invalid OpenAI API key - check Settings';
|
||||
} else if (error.message.includes('OpenAI API error: 429')) {
|
||||
errorMessage = 'API rate limit exceeded - wait a moment';
|
||||
} else if (error.message.includes('AI OCR services unavailable')) {
|
||||
errorMessage = 'OCR service unavailable - configure API key';
|
||||
}
|
||||
|
||||
setScanState(prev => ({
|
||||
...prev,
|
||||
isProcessing: false,
|
||||
message: 'Scan failed, try again',
|
||||
message: errorMessage,
|
||||
}));
|
||||
|
||||
setTimeout(() => {
|
||||
|
|
@ -264,9 +365,9 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
|||
...prev,
|
||||
message: '',
|
||||
}));
|
||||
}, 3000);
|
||||
}, 4000);
|
||||
}
|
||||
}, [captureImage, onCardScanned, queueCount, maxQueueSize, recentScans, scanState.isProcessing]);
|
||||
}, [captureImage, onCardScanned, queueCount, maxQueueSize, recentScans, scanState.isProcessing, isOcrConfigured]);
|
||||
|
||||
const manualScan = () => {
|
||||
if (!scanState.isProcessing && queueCount < maxQueueSize) {
|
||||
|
|
@ -281,9 +382,24 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
|||
isVisible={scanState.isScanning || scanState.isProcessing || !!scanState.message}
|
||||
message={scanState.message}
|
||||
progress={scanState.isScanning ? ((3 - scanState.countdown) / 3) * 100 : undefined}
|
||||
type={scanState.isProcessing ? 'processing' : scanState.message.includes('Added:') ? 'success' : 'scanning'}
|
||||
type={scanState.isProcessing ? 'processing' : scanState.message.includes('Added:') ? 'success' : scanState.message.includes('not configured') || scanState.message.includes('unavailable') ? 'error' : 'scanning'}
|
||||
/>
|
||||
|
||||
{/* OCR Configuration Warning */}
|
||||
{!isOcrConfigured && isStreaming && (
|
||||
<div className="mb-4 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<div className="flex items-center">
|
||||
<span className="text-yellow-600 text-xl mr-3">⚠️</span>
|
||||
<div>
|
||||
<p className="font-medium text-yellow-900">OCR Not Configured</p>
|
||||
<p className="text-yellow-700 text-sm">
|
||||
Please configure your OpenAI API key in Settings to enable card scanning.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Camera Preview */}
|
||||
<div className="relative bg-black rounded-lg overflow-hidden">
|
||||
<video
|
||||
|
|
@ -302,7 +418,9 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
|||
<div className="absolute inset-4 border-2 border-white border-dashed rounded-lg flex items-center justify-center">
|
||||
<div className="bg-black bg-opacity-50 text-white px-4 py-2 rounded-lg text-sm text-center">
|
||||
<div className="font-medium">Position card within this area</div>
|
||||
<div className="text-xs opacity-75">Auto-scan in 2-3 seconds</div>
|
||||
<div className="text-xs opacity-75">
|
||||
{isOcrConfigured ? 'Auto-scan in 2-3 seconds' : 'Configure OCR in Settings first'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -337,11 +455,17 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
|||
<div className="mt-4 text-center">
|
||||
<button
|
||||
onClick={manualScan}
|
||||
disabled={scanState.isProcessing || queueCount >= maxQueueSize}
|
||||
disabled={scanState.isProcessing || queueCount >= maxQueueSize || !isOcrConfigured}
|
||||
className="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white px-6 py-2 rounded-lg font-medium transition-colors"
|
||||
>
|
||||
{scanState.isProcessing ? 'Processing...' : 'Manual Scan'}
|
||||
{scanState.isProcessing ? 'Processing...' : !isOcrConfigured ? 'Configure OCR First' : 'Manual Scan'}
|
||||
</button>
|
||||
|
||||
{!isOcrConfigured && (
|
||||
<p className="text-sm text-gray-600 mt-2">
|
||||
Go to Settings → OCR to configure your OpenAI API key
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue