import React, { useState, useEffect } from 'react'; import { useAuth } from '../contexts/AuthContext'; interface UserPreferences { defaultView: string; itemsPerPage: number; enableAnimations: boolean; enableOcr: boolean; theme: string; privacySettings: { collections_public: boolean; decks_public: boolean; }; ocrSettings: { preferred_service: 'openai' | 'ollama'; openai_api_key: string; ollama_url: string; auto_add_to_collection: boolean; confidence_threshold: number; }; } const Settings: React.FC = () => { const { user } = useAuth(); const [preferences, setPreferences] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const [activeTab, setActiveTab] = useState('general'); const [isTestingOpenAI, setIsTestingOpenAI] = useState(false); const [isTestingOllama, setIsTestingOllama] = useState(false); const [testResults, setTestResults] = useState<{ openai?: string; ollama?: string }>({}); useEffect(() => { loadPreferences(); }, [user]); // eslint-disable-line react-hooks/exhaustive-deps const loadPreferences = async () => { if (!user) return; try { const token = localStorage.getItem('tcg_vault_token'); const response = await fetch('/api/user/preferences', { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); setPreferences(data.preferences); } else { setMessage({ type: 'error', text: 'Failed to load preferences' }); } } catch (error) { console.error('Error loading preferences:', error); setMessage({ type: 'error', text: 'Error loading preferences' }); } finally { setIsLoading(false); } }; const savePreferences = async () => { if (!preferences) return; setIsSaving(true); try { const token = localStorage.getItem('tcg_vault_token'); const response = await fetch('/api/user/preferences', { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(preferences) }); const data = await response.json(); if (response.ok) { setMessage({ type: 'success', text: 'Settings saved successfully!' }); setPreferences(data.preferences); } else { setMessage({ type: 'error', text: data.error || 'Failed to save settings' }); } } catch (error) { console.error('Error saving preferences:', error); setMessage({ type: 'error', text: 'Error saving settings' }); } finally { setIsSaving(false); } }; const testOpenAI = async () => { if (!preferences?.ocrSettings.openai_api_key) { setTestResults(prev => ({ ...prev, openai: '❌ API key required' })); return; } setIsTestingOpenAI(true); try { const response = await fetch('https://api.openai.com/v1/models', { headers: { 'Authorization': `Bearer ${preferences.ocrSettings.openai_api_key}`, }, }); if (response.ok) { setTestResults(prev => ({ ...prev, openai: '✅ API key valid' })); } else { setTestResults(prev => ({ ...prev, openai: `❌ API error: ${response.status}` })); } } catch (error: any) { setTestResults(prev => ({ ...prev, openai: `❌ Connection failed: ${error.message}` })); } finally { setIsTestingOpenAI(false); } }; const testOllama = async () => { if (!preferences?.ocrSettings.ollama_url) return; setIsTestingOllama(true); try { const response = await fetch(`${preferences.ocrSettings.ollama_url}/api/tags`); if (response.ok) { const data = await response.json(); const hasVisionModel = data.models?.some((model: any) => model.name.includes('llava') || model.name.includes('vision') ); if (hasVisionModel) { setTestResults(prev => ({ ...prev, ollama: '✅ Ollama with vision models available' })); } else { setTestResults(prev => ({ ...prev, ollama: '⚠️ Ollama running but no vision models found' })); } } else { setTestResults(prev => ({ ...prev, ollama: `❌ Ollama error: ${response.status}` })); } } catch (error: any) { setTestResults(prev => ({ ...prev, ollama: `❌ Cannot reach Ollama: ${error.message}` })); } finally { setIsTestingOllama(false); } }; const updatePreference = (key: keyof UserPreferences, value: any) => { if (!preferences) return; setPreferences({ ...preferences, [key]: value }); }; const updateOcrSetting = (key: keyof UserPreferences['ocrSettings'], value: any) => { if (!preferences) return; setPreferences({ ...preferences, ocrSettings: { ...preferences.ocrSettings, [key]: value } }); }; if (isLoading) { return (
); } if (!preferences) { return (

Failed to load preferences

); } return (

Settings

Manage your account preferences and OCR configuration

{/* Message Display */} {message && (
{message.text}
)} {/* Tab Navigation */}
{/* Tab Content */}
{activeTab === 'general' && (

General Preferences

)} {activeTab === 'ocr' && (

AI OCR Configuration

{/* Service Selection */}
{/* OpenAI Settings */}

OpenAI Configuration

updateOcrSetting('openai_api_key', e.target.value)} placeholder="sk-..." className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500" />

Get your API key from{' '} OpenAI Platform

{testResults.openai && ( {testResults.openai} )}
{/* Ollama Settings */}

Ollama Configuration

updateOcrSetting('ollama_url', e.target.value)} placeholder="http://localhost:11434" className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500" />

Requires LLaVA or similar vision model:{' '} ollama pull llava

{testResults.ollama && ( {testResults.ollama} )}
{/* OCR Options */}
updateOcrSetting('confidence_threshold', parseInt(e.target.value))} className="w-full" />

Minimum confidence required to accept OCR results

)} {activeTab === 'privacy' && (

Privacy Settings

)}
{/* Save Button */}
); }; export default Settings;