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:
Randall Stillwell 2025-07-22 12:19:08 -05:00
parent cf05ea8daf
commit e720ffdc70

View file

@ -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>