diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 559e8e3..8ee9a2f 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,9 +1,8 @@ import { useCallback, useState, useRef, useEffect } from 'react'; -import { Menu, Sparkles, LogOut, Volume2, VolumeOff, Cpu, Bot, Download, Minimize2, Info, Copy, Check } from 'lucide-react'; +import { Menu, Sparkles, LogOut, Cpu, Bot, Download, Minimize2, Info, Copy, Check, Settings } from 'lucide-react'; import type { ConnectionStatus, Session, ChatMessage } from '../types'; import { useT } from '../hooks/useLocale'; -import { LanguageSelector } from './LanguageSelector'; -import { ThemeSwitcher } from './ThemeSwitcher'; +import { SettingsModal } from './SettingsModal'; import { sessionDisplayName } from '../lib/sessionName'; import { messagesToMarkdown, downloadFile } from '../lib/exportChat'; @@ -25,6 +24,7 @@ export function Header({ status, sessionKey, onToggleSidebar, activeSessionData, const t = useT(); const sessionLabel = activeSessionData ? sessionDisplayName(activeSessionData) : (sessionKey.split(':').pop() || sessionKey); const [showSessionInfo, setShowSessionInfo] = useState(false); + const [settingsOpen, setSettingsOpen] = useState(false); const sessionInfoRef = useRef(null); // Close popover on outside click @@ -77,16 +77,6 @@ export function Header({ status, sessionKey, onToggleSidebar, activeSessionData, )}
- {onToggleSound && ( - - )} {messages && messages.length > 0 && ( )} - - + {status === 'connected' ? (
@@ -154,6 +150,7 @@ export function Header({ status, sessionKey, onToggleSidebar, activeSessionData,
); })()} + setSettingsOpen(false)} soundEnabled={soundEnabled} onToggleSound={onToggleSound} /> ); } diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx new file mode 100644 index 0000000..c23ba2a --- /dev/null +++ b/src/components/SettingsModal.tsx @@ -0,0 +1,222 @@ +import { useEffect } from 'react'; +import { X, Settings, Sun, Moon, Monitor, Laptop, Check, Volume2, VolumeOff } from 'lucide-react'; +import { useTheme } from '../hooks/useTheme'; +import { useSendShortcut } from '../hooks/useSendShortcut'; +import { useT, useLocale } from '../hooks/useLocale'; +import { setLocale, supportedLocales, localeLabels } from '../lib/i18n'; +import type { ThemeName, AccentColor } from '../contexts/ThemeContextDef'; +import type { TranslationKey } from '../lib/i18n'; + +const themeOptions: { value: ThemeName; icon: typeof Sun; labelKey: TranslationKey }[] = [ + { value: 'system', icon: Laptop, labelKey: 'theme.system' }, + { value: 'dark', icon: Moon, labelKey: 'theme.dark' }, + { value: 'light', icon: Sun, labelKey: 'theme.light' }, + { value: 'oled', icon: Monitor, labelKey: 'theme.oled' }, +]; + +const accentOptions: { value: AccentColor; color: string }[] = [ + { value: 'cyan', color: '#22d3ee' }, + { value: 'violet', color: '#8b5cf6' }, + { value: 'emerald', color: '#10b981' }, + { value: 'amber', color: '#f59e0b' }, + { value: 'rose', color: '#f43f5e' }, + { value: 'blue', color: '#3b82f6' }, +]; + +const isMac = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.userAgent); + +interface Props { + open: boolean; + onClose: () => void; + soundEnabled?: boolean; + onToggleSound?: () => void; +} + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function ToggleSwitch({ checked, onChange, label }: { checked: boolean; onChange: () => void; label: string }) { + return ( + + ); +} + +export function SettingsModal({ open, onClose, soundEnabled, onToggleSound }: Props) { + const t = useT(); + const { theme, accent, setTheme, setAccent } = useTheme(); + const { sendOnEnter, toggle: toggleSendShortcut } = useSendShortcut(); + const currentLocale = useLocale(); + + useEffect(() => { + if (!open) return; + const handler = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }; + window.addEventListener('keydown', handler); + return () => window.removeEventListener('keydown', handler); + }, [open, onClose]); + + if (!open) return null; + + return ( +
+ {/* Backdrop */} +
+ + {/* Modal */} +
e.stopPropagation()} + > + {/* Header */} +
+
+ +

{t('settings.title')}

+
+ +
+ + {/* Body */} +
+ {/* Appearance */} + {t('settings.appearance')} + + {/* Theme mode */} +
+
{t('theme.mode')}
+
+ {themeOptions.map(opt => { + const Icon = opt.icon; + const active = theme === opt.value; + return ( + + ); + })} +
+
+ + {/* Accent color */} +
+
{t('theme.accent')}
+
+ {accentOptions.map(opt => ( + + ))} +
+
+ + {/* Language */} + {t('settings.language')} +
+ {supportedLocales.map(loc => ( + + ))} +
+ + {/* Chat */} + {t('settings.chat')} +
+
{t('settings.sendShortcut')}
+ +
+ + {/* Notifications */} + {onToggleSound && ( + <> + {t('settings.notifications')} +
+
+ {soundEnabled ? : } + {t('settings.notificationSound')} +
+ +
+ + )} +
+
+
+ ); +} diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts index 24a648e..7870871 100644 --- a/src/lib/i18n.ts +++ b/src/lib/i18n.ts @@ -153,6 +153,12 @@ const en = { 'shortcuts.searchMessages': 'Search messages', // Send shortcut setting + 'settings.title': 'Settings', + 'settings.appearance': 'Appearance', + 'settings.chat': 'Chat', + 'settings.notifications': 'Notifications', + 'settings.notificationSound': 'Notification sound', + 'settings.language': 'Language', 'settings.sendShortcut': 'Send with', 'settings.sendEnter': 'Enter', 'settings.sendCtrlEnter': 'Ctrl+Enter', @@ -304,6 +310,12 @@ const fr: Record = { 'search.next': 'Résultat suivant', 'shortcuts.searchMessages': 'Rechercher dans les messages', + 'settings.title': 'Paramètres', + 'settings.appearance': 'Apparence', + 'settings.chat': 'Chat', + 'settings.notifications': 'Notifications', + 'settings.notificationSound': 'Son de notification', + 'settings.language': 'Langue', 'settings.sendShortcut': 'Envoyer avec', 'settings.sendEnter': 'Entrée', 'settings.sendCtrlEnter': 'Ctrl+Entrée', @@ -454,6 +466,12 @@ const es: Record = { 'search.next': 'Resultado siguiente', 'shortcuts.searchMessages': 'Buscar en mensajes', + 'settings.title': 'Ajustes', + 'settings.appearance': 'Apariencia', + 'settings.chat': 'Chat', + 'settings.notifications': 'Notificaciones', + 'settings.notificationSound': 'Sonido de notificación', + 'settings.language': 'Idioma', 'settings.sendShortcut': 'Enviar con', 'settings.sendEnter': 'Enter', 'settings.sendCtrlEnter': 'Ctrl+Enter', @@ -606,6 +624,12 @@ const de: Record = { 'search.next': 'Nächster Treffer', 'shortcuts.searchMessages': 'In Nachrichten suchen', + 'settings.title': 'Einstellungen', + 'settings.appearance': 'Darstellung', + 'settings.chat': 'Chat', + 'settings.notifications': 'Benachrichtigungen', + 'settings.notificationSound': 'Benachrichtigungston', + 'settings.language': 'Sprache', 'settings.sendShortcut': 'Senden mit', 'settings.sendEnter': 'Enter', 'settings.sendCtrlEnter': 'Strg+Enter', @@ -756,6 +780,12 @@ const ja: Record = { 'search.next': '次の結果', 'shortcuts.searchMessages': 'メッセージを検索', + 'settings.title': '設定', + 'settings.appearance': '外観', + 'settings.chat': 'チャット', + 'settings.notifications': '通知', + 'settings.notificationSound': '通知音', + 'settings.language': '言語', 'settings.sendShortcut': '送信キー', 'settings.sendEnter': 'Enter', 'settings.sendCtrlEnter': 'Ctrl+Enter', @@ -906,6 +936,12 @@ const pt: Record = { 'search.next': 'Próximo resultado', 'shortcuts.searchMessages': 'Pesquisar mensagens', + 'settings.title': 'Configurações', + 'settings.appearance': 'Aparência', + 'settings.chat': 'Chat', + 'settings.notifications': 'Notificações', + 'settings.notificationSound': 'Som de notificação', + 'settings.language': 'Idioma', 'settings.sendShortcut': 'Tecla de envio', 'settings.sendEnter': 'Enter', 'settings.sendCtrlEnter': 'Ctrl+Enter', @@ -1056,6 +1092,12 @@ const zh: Record = { 'search.next': '下一个', 'shortcuts.searchMessages': '搜索消息', + 'settings.title': '设置', + 'settings.appearance': '外观', + 'settings.chat': '聊天', + 'settings.notifications': '通知', + 'settings.notificationSound': '通知声音', + 'settings.language': '语言', 'settings.sendShortcut': '发送方式', 'settings.sendEnter': 'Enter', 'settings.sendCtrlEnter': 'Ctrl+Enter', @@ -1206,6 +1248,12 @@ const it: Record = { 'search.next': 'Risultato successivo', 'shortcuts.searchMessages': 'Cerca nei messaggi', + 'settings.title': 'Impostazioni', + 'settings.appearance': 'Aspetto', + 'settings.chat': 'Chat', + 'settings.notifications': 'Notifiche', + 'settings.notificationSound': 'Suono di notifica', + 'settings.language': 'Lingua', 'settings.sendShortcut': 'Invia con', 'settings.sendEnter': 'Invio', 'settings.sendCtrlEnter': 'Ctrl+Invio',