'use client'; import React, { useEffect, useState, useCallback } from 'react'; import { Settings, Loader2, Check, Cpu, Image as ImageIcon, Key, Zap, CheckCircle2, XCircle, } from 'lucide-react'; import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { getHeader } from '@/app/(presentation-generator)/services/api/header'; import { toast } from 'sonner'; interface SystemSettings { llm_provider: string; llm_model: string; image_provider: string; anthropic_api_key_set: boolean; openai_api_key_set: boolean; google_api_key_set: boolean; available_llm_providers: string[]; available_image_providers: string[]; } const PROVIDER_LABELS: Record = { google: 'Google Gemini', gemini_flash: 'Gemini Flash (Image Generation)', pexels: 'Pexels (Stock Photos)', pixabay: 'Pixabay (Stock Photos)', }; interface ConnectionTestResult { ok: boolean; error?: string; latency_ms?: number; } export default function SettingsPage() { const [settings, setSettings] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); // Form state const [llmProvider, setLlmProvider] = useState(''); const [llmModel, setLlmModel] = useState(''); const [imageProvider, setImageProvider] = useState(''); const [googleKey, setGoogleKey] = useState(''); // Model listing const [availableModels, setAvailableModels] = useState([]); const [loadingModels, setLoadingModels] = useState(false); // Connection tests const [testResults, setTestResults] = useState>({}); const [testingProvider, setTestingProvider] = useState(null); useEffect(() => { loadSettings(); }, []); const loadModels = useCallback(async (provider: string) => { setLoadingModels(true); setAvailableModels([]); try { const res = await fetch(`/api/v1/admin/settings/models?provider=${provider}`, { headers: getHeader(), }); if (res.ok) { const data = await res.json(); setAvailableModels(data.models || []); } } catch { // Silently fail — user can still type manually } finally { setLoadingModels(false); } }, []); // Load models when provider changes useEffect(() => { if (llmProvider) { loadModels(llmProvider); } }, [llmProvider, loadModels]); const loadSettings = async () => { setLoading(true); setError(null); try { const res = await fetch('/api/v1/admin/settings', { headers: getHeader() }); if (res.status === 403) { setError('Super admin access required'); return; } if (!res.ok) throw new Error('Failed to load settings'); const data: SystemSettings = await res.json(); setSettings(data); setLlmProvider(data.llm_provider); setLlmModel(data.llm_model); setImageProvider(data.image_provider); } catch (e) { setError('Failed to load settings'); } finally { setLoading(false); } }; const handleSave = async () => { setSaving(true); try { const body: Record = {}; if (llmProvider !== settings?.llm_provider) body.llm_provider = llmProvider; if (llmModel !== settings?.llm_model) body.llm_model = llmModel; if (imageProvider !== settings?.image_provider) body.image_provider = imageProvider; if (googleKey) body.google_api_key = googleKey; if (Object.keys(body).length === 0) { toast.info('No changes to save'); setSaving(false); return; } const res = await fetch('/api/v1/admin/settings', { method: 'PUT', headers: { ...getHeader(), 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); if (!res.ok) { const err = await res.json(); throw new Error(err.detail || 'Save failed'); } toast.success('Settings saved'); setGoogleKey(''); loadSettings(); } catch (e) { toast.error(e instanceof Error ? e.message : 'Save failed'); } finally { setSaving(false); } }; const testConnection = async () => { setTestingProvider('google'); try { const body: Record = { provider: 'google' }; if (googleKey) body.api_key = googleKey; const res = await fetch('/api/v1/admin/settings/test-connection', { method: 'POST', headers: { ...getHeader(), 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); const result: ConnectionTestResult = await res.json(); setTestResults((prev) => ({ ...prev, google: result })); if (result.ok) { toast.success(`Google Gemini: Connected (${result.latency_ms}ms)`); } else { toast.error(`Google Gemini: ${result.error}`); } } catch { setTestResults((prev) => ({ ...prev, google: { ok: false, error: 'Request failed' } })); toast.error('Connection test failed'); } finally { setTestingProvider(null); } }; if (loading) { return (
); } if (error) { return (

System Settings

{error}

); } return (

System Settings

Oliver DeckForge uses Google Gemini for all AI operations. Settings are persisted to the database and survive container restarts.

Get your Google AI API key:{' '} https://aistudio.google.com/app/apikey

{/* Gemini Configuration */}

Google Gemini (LLM)

{availableModels.length > 0 ? ( ) : ( setLlmModel(e.target.value)} placeholder={loadingModels ? 'Loading models...' : 'e.g. gemini-2.0-flash-exp'} /> )}

Recommended: gemini-2.0-flash-exp (fast & cheap)

{/* Image Generation */}

Image Generation

{/* API Key */}

Google API Key

Leave blank to keep existing key. Enter a new value to update.

setGoogleKey(e.target.value)} placeholder={settings?.google_api_key_set ? '••••••••••••' : 'AIza...'} className="flex-1" />
); }