'use client'; import { Fragment, useState, useEffect, useCallback, useMemo } from 'react'; import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild, Listbox, ListboxButton, ListboxOptions, ListboxOption, Button, Input, TabGroup, TabList, Tab, TabPanels, TabPanel, } from '@headlessui/react'; import { useTheme } from '@/contexts/ThemeContext'; import { useConfig } from '@/contexts/ConfigContext'; import { ChevronUpDownIcon, CheckIcon, SettingsIcon } from '@/components/icons/Icons'; import { indexedDBService } from '@/utils/indexedDB'; import { useDocuments } from '@/contexts/DocumentContext'; import { setItem, getItem } from '@/utils/indexedDB'; import { ConfirmDialog } from '@/components/ConfirmDialog'; import { THEMES } from '@/contexts/ThemeContext'; const isDev = process.env.NEXT_PUBLIC_NODE_ENV !== 'production' || process.env.NODE_ENV == null; const themes = THEMES.map(id => ({ id, name: id.charAt(0).toUpperCase() + id.slice(1) })); export function SettingsModal() { const [isOpen, setIsOpen] = useState(false); const { theme, setTheme } = useTheme(); const { apiKey, baseUrl, ttsModel, ttsInstructions, updateConfig, updateConfigKey } = useConfig(); const { refreshPDFs, refreshEPUBs, clearPDFs, clearEPUBs } = useDocuments(); const [localApiKey, setLocalApiKey] = useState(apiKey); const [localBaseUrl, setLocalBaseUrl] = useState(baseUrl); const [localTTSModel, setLocalTTSModel] = useState(ttsModel); const [customModel, setCustomModel] = useState(''); const [localTTSInstructions, setLocalTTSInstructions] = useState(ttsInstructions); const [isSyncing, setIsSyncing] = useState(false); const [isLoading, setIsLoading] = useState(false); const selectedTheme = themes.find(t => t.id === theme) || themes[0]; const [showClearLocalConfirm, setShowClearLocalConfirm] = useState(false); const [showClearServerConfirm, setShowClearServerConfirm] = useState(false); const ttsModels = useMemo(() => [ { id: 'tts-1', name: 'TTS-1' }, { id: 'tts-1-hd', name: '($$) TTS-1-HD' }, { id: 'gpt-4o-mini-tts', name: '($$$) GPT-4o Mini TTS' }, { id: 'kokoro', name: 'Kokoro' }, { id: 'custom', name: 'Custom Model' } ], []); // set firstVisit on initial load const checkFirstVist = useCallback(async () => { if (!isDev) return; const firstVisit = await getItem('firstVisit'); if (firstVisit == null) { await setItem('firstVisit', 'true'); setIsOpen(true); } }, [setIsOpen]); useEffect(() => { checkFirstVist(); setLocalApiKey(apiKey); setLocalBaseUrl(baseUrl); setLocalTTSModel(ttsModel); setLocalTTSInstructions(ttsInstructions); // Set custom model if current model is not in predefined list if (!ttsModels.some(m => m.id === ttsModel) && ttsModel !== '') { setCustomModel(ttsModel); setLocalTTSModel('custom'); } }, [apiKey, baseUrl, ttsModel, ttsModels, ttsInstructions, checkFirstVist]); const handleSync = async () => { try { setIsSyncing(true); await indexedDBService.syncToServer(); } catch (error) { console.error('Sync failed:', error); } finally { setIsSyncing(false); } }; const handleLoad = async () => { try { setIsLoading(true); await indexedDBService.loadFromServer(); await Promise.all([refreshPDFs(), refreshEPUBs()]); } catch (error) { console.error('Load failed:', error); } finally { setIsLoading(false); } }; const handleClearLocal = async () => { await clearPDFs(); await clearEPUBs(); setShowClearLocalConfirm(false); }; const handleClearServer = async () => { try { const response = await fetch('/api/documents', { method: 'DELETE', }); if (!response.ok) { throw new Error('Failed to delete server documents'); } } catch (error) { console.error('Delete failed:', error); } setShowClearServerConfirm(false); }; const handleInputChange = (type: 'apiKey' | 'baseUrl' | 'ttsModel', value: string) => { if (type === 'apiKey') { setLocalApiKey(value === '' ? '' : value); } else if (type === 'baseUrl') { setLocalBaseUrl(value === '' ? '' : value); } else if (type === 'ttsModel') { setLocalTTSModel(value === '' ? 'tts-1' : value); } }; const resetToCurrent = useCallback(() => { setIsOpen(false); setLocalApiKey(apiKey); setLocalBaseUrl(baseUrl); setLocalTTSModel(ttsModel); setLocalTTSInstructions(ttsInstructions); if (!ttsModels.some(m => m.id === ttsModel) && ttsModel !== '') { setCustomModel(ttsModel); setLocalTTSModel('custom'); } }, [apiKey, baseUrl, ttsModel, ttsInstructions, ttsModels]); const tabs = [ { name: 'Appearance', icon: '✨' }, { name: 'API', icon: '🔑' }, { name: 'Documents', icon: '📄' } ]; return (