import { FormEvent, useEffect, useRef, useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { AlertCircle, Check, Copy, Eye, EyeOff, KeyRound, Plus, Plug, RefreshCw, Route, Sparkles, Trash2, X, } from 'lucide-react'; import { useI18n } from '../i18n'; type CoreConfigSettings = { apiKeys: CoreApiKey[]; pluginsEnabled: boolean; routingStrategy: string; }; type CoreApiKey = { apiKey: string; remark: string; builtIn: boolean; }; type ConfigAction = 'add-key' | 'delete-key' | 'plugins' | 'routing' | null; type NoticeTone = 'success' | 'error'; const ROUTING_OPTIONS = [ { value: 'round-robin', labelKey: 'config.routing.roundRobin' }, { value: 'fill-first', labelKey: 'config.routing.fillFirst' }, ] as const; export function ConfigPanelPage() { const { t } = useI18n(); const [settings, setSettings] = useState(null); const [loading, setLoading] = useState(true); const [loadError, setLoadError] = useState(''); const [busyAction, setBusyAction] = useState(null); const [addDialogOpen, setAddDialogOpen] = useState(false); const [deleteIndex, setDeleteIndex] = useState(null); const [newApiKey, setNewApiKey] = useState(''); const [newApiKeyRemark, setNewApiKeyRemark] = useState(''); const [showApiKey, setShowApiKey] = useState(false); const [formError, setFormError] = useState(''); const [copiedIndex, setCopiedIndex] = useState(null); const [notice, setNotice] = useState<{ message: string; tone: NoticeTone } | null>(null); const noticeTimerRef = useRef(null); const copyTimerRef = useRef(null); useEffect(() => { void loadSettings(); return () => { if (noticeTimerRef.current !== null) { window.clearTimeout(noticeTimerRef.current); } if (copyTimerRef.current !== null) { window.clearTimeout(copyTimerRef.current); } }; }, []); const showNotice = (message: string, tone: NoticeTone) => { if (noticeTimerRef.current !== null) { window.clearTimeout(noticeTimerRef.current); } setNotice({ message, tone }); noticeTimerRef.current = window.setTimeout(() => { setNotice(null); noticeTimerRef.current = null; }, 3200); }; async function loadSettings() { setLoading(true); setLoadError(''); try { const result = await invoke('get_core_config_settings'); setSettings(result); } catch (error) { setSettings(null); setLoadError(String(error)); } finally { setLoading(false); } } const runMutation = async ( action: Exclude, command: string, args: Record, successMessage: string, ) => { setBusyAction(action); try { const result = await invoke(command, args); setSettings(result); setLoadError(''); showNotice(successMessage, 'success'); return true; } catch (error) { showNotice(String(error), 'error'); void loadSettings(); return false; } finally { setBusyAction(null); } }; const openAddDialog = () => { setNewApiKey(''); setNewApiKeyRemark(''); setShowApiKey(false); setFormError(''); setAddDialogOpen(true); }; const closeAddDialog = () => { if (busyAction === 'add-key') { return; } setAddDialogOpen(false); setFormError(''); }; const generateApiKey = () => { const bytes = new Uint8Array(24); crypto.getRandomValues(bytes); const value = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join(''); setNewApiKey(`sk-${value}`); setShowApiKey(true); setFormError(''); }; const submitApiKey = async (event: FormEvent) => { event.preventDefault(); const apiKey = newApiKey.trim(); if (!apiKey) { setFormError(t('config.error.emptyKey')); return; } if (!/^[\x21-\x7e]+$/.test(apiKey)) { setFormError(t('config.error.invalidKey')); return; } if (settings?.apiKeys.some((entry) => entry.apiKey === apiKey)) { setFormError(t('config.error.duplicateKey')); return; } const remark = newApiKeyRemark.trim(); if (remark.length > 80) { setFormError(t('config.error.remarkTooLong')); return; } const saved = await runMutation( 'add-key', 'add_core_api_key', { apiKey, remark }, t('config.notice.keyAdded'), ); if (saved) { setAddDialogOpen(false); setNewApiKey(''); setNewApiKeyRemark(''); } }; const confirmDelete = async () => { if (deleteIndex === null) { return; } const deleted = await runMutation( 'delete-key', 'delete_core_api_key', { apiKey: selectedDeleteKey }, t('config.notice.keyDeleted'), ); if (deleted) { setDeleteIndex(null); } }; const copyApiKey = async (apiKey: string, index: number) => { try { await navigator.clipboard.writeText(apiKey); setCopiedIndex(index); showNotice(t('config.notice.keyCopied'), 'success'); if (copyTimerRef.current !== null) { window.clearTimeout(copyTimerRef.current); } copyTimerRef.current = window.setTimeout(() => { setCopiedIndex(null); copyTimerRef.current = null; }, 1800); } catch { showNotice(t('config.notice.keyCopyFailed'), 'error'); } }; const togglePlugins = async (enabled: boolean) => { await runMutation( 'plugins', 'set_core_plugins_enabled', { enabled }, enabled ? t('config.notice.pluginsEnabled') : t('config.notice.pluginsDisabled'), ); }; const changeRoutingStrategy = async (strategy: string) => { if (strategy === settings?.routingStrategy) { return; } await runMutation( 'routing', 'set_core_routing_strategy', { strategy }, t('config.notice.routingUpdated'), ); }; const controlsDisabled = loading || settings === null || busyAction !== null; const selectedDeleteKey = deleteIndex === null ? '' : settings?.apiKeys[deleteIndex]?.apiKey || ''; return (
{settings?.apiKeys.length ?? 0}
{loading ? ( Array.from({ length: 5 }, (_, index) => ( )) ) : loadError ? (
) : settings && settings.apiKeys.length > 0 ? ( settings.apiKeys.map((entry, index) => (
{String(index + 1).padStart(2, '0')}
{entry.remark || t('config.keys.noRemark')}
{maskApiKey(entry.apiKey)}
{entry.builtIn ? ( {t('config.keys.builtIn')} ) : ( )}
)) ) : (
)}
{loading ? t('common.loading') : settings === null ? t('common.unavailable') : settings.pluginsEnabled ? t('common.enabled') : t('common.disabled')}
{t('config.plugins.enable')}
{loading ? t('common.loading') : settings === null ? t('common.unavailable') : routingStrategyLabel(settings.routingStrategy, t)}
{ROUTING_OPTIONS.map((option) => ( ))}
{addDialogOpen ? (
{ if (event.currentTarget === event.target) closeAddDialog(); }}>
void submitApiKey(event)} >
{formError || ' '}
) : null} {deleteIndex !== null ? (
{ if (event.currentTarget === event.target && busyAction !== 'delete-key') { setDeleteIndex(null); } }}>
{maskApiKey(selectedDeleteKey)}
) : null} {notice ? (
{notice.tone === 'success' ? (
) : null}
); } function maskApiKey(apiKey: string) { const value = apiKey.trim(); if (!value) { return ''; } const visible = value.length < 4 ? 1 : 2; return `${value.slice(0, visible)}${'*'.repeat(Math.max(6, 10 - visible * 2))}${value.slice(-visible)}`; } function routingStrategyLabel(strategy: string | undefined, t: ReturnType['t']) { if (!strategy) { return t('common.loading'); } const option = ROUTING_OPTIONS.find((item) => item.value === strategy); return option ? t(option.labelKey) : strategy; }