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 React, { useRef, useState, useEffect, useCallback } from 'react';
|
||||||
import ScanningToast from './ScanningToast';
|
import ScanningToast from './ScanningToast';
|
||||||
import { aiCardOCR, type CardOCRResult } from '../../services/aiOcr';
|
import { aiCardOCR, ollamaCardOCR, type CardOCRResult } from '../../services/aiOcr';
|
||||||
|
|
||||||
interface ScannedCard {
|
interface ScannedCard {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -44,6 +44,60 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
||||||
|
|
||||||
const [queueCount, setQueueCount] = useState(0);
|
const [queueCount, setQueueCount] = useState(0);
|
||||||
const [recentScans, setRecentScans] = useState<Set<string>>(new Set());
|
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
|
// Initialize camera
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -60,7 +114,7 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
||||||
|
|
||||||
// Auto-scan detection
|
// Auto-scan detection
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isStreaming || !isActive) return;
|
if (!isStreaming || !isActive || !isOcrConfigured) return;
|
||||||
|
|
||||||
const detectCardStabilization = () => {
|
const detectCardStabilization = () => {
|
||||||
if (scanState.isProcessing || queueCount >= maxQueueSize) return;
|
if (scanState.isProcessing || queueCount >= maxQueueSize) return;
|
||||||
|
|
@ -115,7 +169,7 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
||||||
clearTimeout(scanTimeoutRef.current);
|
clearTimeout(scanTimeoutRef.current);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [isStreaming, isActive, scanState.isProcessing, queueCount, maxQueueSize]);
|
}, [isStreaming, isActive, scanState.isProcessing, queueCount, maxQueueSize, isOcrConfigured]);
|
||||||
|
|
||||||
const startCamera = async () => {
|
const startCamera = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -180,6 +234,24 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
||||||
const triggerScan = useCallback(async () => {
|
const triggerScan = useCallback(async () => {
|
||||||
if (scanState.isProcessing || queueCount >= maxQueueSize) return;
|
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 => ({
|
setScanState(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
isScanning: false,
|
isScanning: false,
|
||||||
|
|
@ -194,8 +266,23 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
||||||
throw new Error('Failed to capture image');
|
throw new Error('Failed to capture image');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process with AI OCR
|
// Process with AI OCR - try OpenAI first, fallback to Ollama
|
||||||
const cardData = await aiCardOCR.analyzeCard(imageDataUrl);
|
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
|
// Create unique ID for this scan
|
||||||
const cardId = `scan_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
const cardId = `scan_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
|
@ -253,10 +340,24 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Scan processing error:', error);
|
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 => ({
|
setScanState(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
isProcessing: false,
|
isProcessing: false,
|
||||||
message: 'Scan failed, try again',
|
message: errorMessage,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -264,9 +365,9 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
||||||
...prev,
|
...prev,
|
||||||
message: '',
|
message: '',
|
||||||
}));
|
}));
|
||||||
}, 3000);
|
}, 4000);
|
||||||
}
|
}
|
||||||
}, [captureImage, onCardScanned, queueCount, maxQueueSize, recentScans, scanState.isProcessing]);
|
}, [captureImage, onCardScanned, queueCount, maxQueueSize, recentScans, scanState.isProcessing, isOcrConfigured]);
|
||||||
|
|
||||||
const manualScan = () => {
|
const manualScan = () => {
|
||||||
if (!scanState.isProcessing && queueCount < maxQueueSize) {
|
if (!scanState.isProcessing && queueCount < maxQueueSize) {
|
||||||
|
|
@ -281,9 +382,24 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
||||||
isVisible={scanState.isScanning || scanState.isProcessing || !!scanState.message}
|
isVisible={scanState.isScanning || scanState.isProcessing || !!scanState.message}
|
||||||
message={scanState.message}
|
message={scanState.message}
|
||||||
progress={scanState.isScanning ? ((3 - scanState.countdown) / 3) * 100 : undefined}
|
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 */}
|
{/* Camera Preview */}
|
||||||
<div className="relative bg-black rounded-lg overflow-hidden">
|
<div className="relative bg-black rounded-lg overflow-hidden">
|
||||||
<video
|
<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="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="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="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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -337,11 +455,17 @@ const AutoScanningCamera: React.FC<AutoScanningCameraProps> = ({
|
||||||
<div className="mt-4 text-center">
|
<div className="mt-4 text-center">
|
||||||
<button
|
<button
|
||||||
onClick={manualScan}
|
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"
|
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>
|
</button>
|
||||||
|
|
||||||
|
{!isOcrConfigured && (
|
||||||
|
<p className="text-sm text-gray-600 mt-2">
|
||||||
|
Go to Settings → OCR to configure your OpenAI API key
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue