import React, { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Settings as SettingsIcon, Key, AlertCircle, CheckCircle, Eye, EyeOff, Zap, Server, Palette, Bell, Shield, Cpu, Globe, Wallet, ChevronRight, } from 'lucide-react'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Select, Toggle } from '@/components/ui/Select'; import { Badge } from '@/components/ui/Badge'; import { apiClient } from '@/api/client'; import type { SystemSettings } from '@/types'; import { classNames } from '@/utils/helpers'; interface SettingsProps { className?: string; } interface ApiKeyState { openai: string; anthropic: string; google: string; groq: string; } interface ModelOption { provider: string; model: string; name: string; description: string; default?: boolean; } interface SettingsData { api_keys_configured: Record; selected_model: { provider: string; model: string }; available_models: ModelOption[]; plugins_installed: string[]; } type SettingsSection = 'general' | 'api-keys' | 'models' | 'budget' | 'appearance' | 'notifications' | 'advanced'; const sectionConfig: { id: SettingsSection; label: string; icon: React.ElementType; description: string }[] = [ { id: 'general', label: 'General', icon: SettingsIcon, description: 'Basic application settings' }, { id: 'api-keys', label: 'API Keys', icon: Key, description: 'Configure provider API keys' }, { id: 'models', label: 'Models', icon: Cpu, description: 'AI model selection' }, { id: 'budget', label: 'Budget & Limits', icon: Wallet, description: 'API usage limits' }, { id: 'appearance', label: 'Appearance', icon: Palette, description: 'UI customization' }, { id: 'notifications', label: 'Notifications', icon: Bell, description: 'Alert preferences' }, { id: 'advanced', label: 'Advanced', icon: Shield, description: 'Advanced settings' }, ]; export const Settings: React.FC = ({ className }) => { const queryClient = useQueryClient(); const [activeSection, setActiveSection] = useState('general'); const [localSettings, setLocalSettings] = useState>({}); const [showKeys, setShowKeys] = useState>({}); const [budgetEnabled, setBudgetEnabled] = useState(false); const [apiKeys, setApiKeys] = useState({ openai: '', anthropic: '', google: '', groq: '', }); // Fetch settings from new API const { data: settingsData, isLoading: settingsLoading } = useQuery({ queryKey: ['client-settings'], queryFn: async () => { const res = await fetch('/api/settings/'); return res.json(); }, }); // Fetch API key requirement status const { data: keyRequired } = useQuery({ queryKey: ['api-key-required'], queryFn: async () => { const res = await fetch('/api/settings/api-key/required'); return res.json(); }, refetchInterval: 5000, }); const { data: health } = useQuery({ queryKey: ['health'], queryFn: () => apiClient.healthCheck(), refetchInterval: 10000, }); const normalizedHealthStatus = typeof health?.status === 'string' ? health.status.toLowerCase() : ''; const isBackendOnline = normalizedHealthStatus === 'healthy' || normalizedHealthStatus === 'ok' || normalizedHealthStatus === 'ready'; // Mutation to update API key const updateApiKeyMutation = useMutation({ mutationFn: async ({ provider, api_key }: { provider: string; api_key: string }) => { const res = await fetch('/api/settings/api-key', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider, api_key }), }); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['client-settings'] }); queryClient.invalidateQueries({ queryKey: ['api-key-required'] }); }, }); // Mutation to select model const selectModelMutation = useMutation({ mutationFn: async ({ provider, model }: { provider: string; model: string }) => { const res = await fetch('/api/settings/model', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider, model }), }); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['client-settings'] }); queryClient.invalidateQueries({ queryKey: ['api-key-required'] }); }, }); const handleSaveApiKey = (provider: string) => { const key = apiKeys[provider as keyof ApiKeyState]; if (key) { updateApiKeyMutation.mutate({ provider, api_key: key }); } }; const handleModelChange = (value: string) => { const [provider, model] = value.split('/'); selectModelMutation.mutate({ provider, model }); }; const toggleShowKey = (provider: string) => { setShowKeys((prev) => ({ ...prev, [provider]: !prev[provider] })); }; const providers = [ { id: 'groq', name: 'Groq', icon: '⚡', description: 'Fast inference (Recommended)' }, { id: 'google', name: 'Google', icon: '🔮', description: 'Gemini models' }, { id: 'openai', name: 'OpenAI', icon: '🤖', description: 'GPT-4 models' }, { id: 'anthropic', name: 'Anthropic', icon: '🧠', description: 'Claude models' }, ]; const modelOptions = (settingsData?.available_models ?? []).map((m) => ({ value: `${m.provider}/${m.model}`, label: `${m.name}${m.default ? ' (Default)' : ''}`, })); const currentModel = settingsData?.selected_model ? `${settingsData.selected_model.provider}/${settingsData.selected_model.model}` : 'groq/gpt-oss-120b'; const renderSectionContent = () => { switch (activeSection) { case 'general': return (

General Settings

Configure basic application behavior

{/* API Key Required Warning */} {keyRequired?.required && (

API Key Required

{keyRequired.message}

)}
setLocalSettings((prev) => ({ ...prev, enableWebSocket: checked }))} /> setLocalSettings((prev) => ({ ...prev, memoryPersistence: checked }))} /> setLocalSettings((prev) => ({ ...prev, autoSave: checked }))} /> setLocalSettings((prev) => ({ ...prev, debugMode: checked }))} />
{/* System Status */}

System Status

Backend

{isBackendOnline ? 'Connected' : 'Disconnected'}

Version

{health?.version || 'v0.1.0'}

); case 'api-keys': return (

API Keys

Configure your provider API keys. Server keys are used by default, but you can override them here.

{providers.map((provider) => { const isConfigured = settingsData?.api_keys_configured?.[provider.id] ?? false; return (
{provider.icon}
{provider.name}

{provider.description}

{isConfigured ? 'Active' : 'Not Set'}
setApiKeys((prev) => ({ ...prev, [provider.id]: e.target.value, })) } className="pr-10" />
); })}
{updateApiKeyMutation.isSuccess && (
API key saved successfully
)}
); case 'models': return (

AI Models

Select the AI model to use for scraping tasks

Active Model
{}} />
) : (

Budget limits are disabled. Enable to control API spending.

)} ); case 'appearance': return (

Appearance

Customize the look and feel of ScrapeRL

Theme

{}} /> {}} />
); case 'notifications': return (

Notifications

Configure when and how you receive alerts

{}} /> {}} /> {}} /> {}} />
); case 'advanced': return (

Advanced Settings

For power users and developers

{}} /> {}} /> {}} />

Data Management

); default: return null; } }; return (
{/* Left Sidebar */}

Settings

Configure your environment

{/* Status Footer */}
{isBackendOnline ? 'System Online' : 'System Offline'}
{/* Main Content */}
{settingsLoading ? (

Loading settings...

) : ( renderSectionContent() )}
); }; export default Settings;