import { FormEvent, useEffect, useRef, useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { listen } from '@tauri-apps/api/event'; import { AlertCircle, Check, Copy, Clock3, Eye, EyeOff, KeyRound, Link2, Network, Pencil, Plus, RefreshCw, Route, ShieldCheck, Sparkles, Trash2, X, } from 'lucide-react'; import { useCoreRuntime, type CoreStatus } from '../coreRuntime'; import { useI18n } from '../i18n'; import { SpeedAliasesPage } from './SpeedAliasesPage'; import { ThinkingAliasesPage } from './ThinkingAliasesPage'; type CoreConfigSettings = { apiKeys: CoreApiKey[]; managementSecretConfigured: boolean; port: number; allowLan: boolean; routingStrategy: string; proxyUrl: string; routingSessionAffinity: boolean; routingSessionAffinityTtl: string; }; type CoreApiKey = { apiKey: string; remark: string; }; type ConfigAction = | 'add-key' | 'update-key' | 'delete-key' | 'management-secret' | 'routing' | 'network' | null; type NoticeTone = 'success' | 'error'; type ConfigSubpage = 'general' | 'network' | 'aliases' | 'speedAliases'; 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 { status: coreStatus, publishStatus, refreshStatus } = useCoreRuntime(); 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 [editingApiKey, setEditingApiKey] = useState(null); const [deleteIndex, setDeleteIndex] = useState(null); const [newApiKey, setNewApiKey] = useState(''); const [newApiKeyRemark, setNewApiKeyRemark] = useState(''); const [showApiKey, setShowApiKey] = useState(false); const [formError, setFormError] = useState(''); const [managementSecretDraft, setManagementSecretDraft] = useState(''); const [managementSecretConfirm, setManagementSecretConfirm] = useState(''); const [showManagementSecret, setShowManagementSecret] = useState(false); const [managementSecretError, setManagementSecretError] = useState(''); const [copiedIndex, setCopiedIndex] = useState(null); const [notice, setNotice] = useState<{ message: string; tone: NoticeTone } | null>(null); const [activeSubpage, setActiveSubpage] = useState('general'); const [portDraft, setPortDraft] = useState('8317'); const [allowLanDraft, setAllowLanDraft] = useState(false); const [proxyUrlDraft, setProxyUrlDraft] = useState(''); const [sessionAffinityDraft, setSessionAffinityDraft] = useState(false); const [sessionTtlDraft, setSessionTtlDraft] = useState(''); const [portError, setPortError] = useState(''); const [savedStatusVisible, setSavedStatusVisible] = useState(false); const noticeTimerRef = useRef(null); const copyTimerRef = useRef(null); useEffect(() => { let disposed = false; let stop: (() => void) | null = null; void loadSettings(); void listen('config-files-changed', () => { if (!disposed) void loadSettings(); }).then((unlisten) => { if (disposed) unlisten(); else stop = unlisten; }); return () => { disposed = true; stop?.(); 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); }; const applySettings = (result: CoreConfigSettings, syncNetworkDrafts = true) => { setSettings(result); if (syncNetworkDrafts) { setPortDraft(String(result.port)); setAllowLanDraft(result.allowLan); setProxyUrlDraft(result.proxyUrl); setSessionAffinityDraft(result.routingSessionAffinity); setSessionTtlDraft(result.routingSessionAffinityTtl); setPortError(''); } }; async function loadSettings(syncNetworkDrafts = true) { setLoading(true); setLoadError(''); try { const result = await invoke('get_core_config_settings'); applySettings(result, syncNetworkDrafts); } 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); applySettings(result, false); setLoadError(''); showNotice(successMessage, 'success'); return true; } catch (error) { if (settings) applySettings(settings, false); showNotice(t('config.error.saveFailed', { error: String(error) }), 'error'); void loadSettings(false); return false; } finally { setBusyAction(null); } }; const openAddDialog = () => { setEditingApiKey(null); setNewApiKey(''); setNewApiKeyRemark(''); setShowApiKey(false); setFormError(''); setAddDialogOpen(true); }; const openEditDialog = (entry: CoreApiKey) => { setEditingApiKey(entry.apiKey); setNewApiKey(entry.apiKey); setNewApiKeyRemark(entry.remark); setShowApiKey(false); setFormError(''); setAddDialogOpen(true); }; const closeAddDialog = () => { if (busyAction === 'add-key' || busyAction === 'update-key') { return; } setAddDialogOpen(false); setEditingApiKey(null); 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 generateManagementSecret = () => { const bytes = new Uint8Array(24); crypto.getRandomValues(bytes); const value = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join(''); const secret = `webui-${value}`; setManagementSecretDraft(secret); setManagementSecretConfirm(secret); setShowManagementSecret(true); setManagementSecretError(''); }; const saveManagementSecret = async (event: FormEvent) => { event.preventDefault(); const secretKey = managementSecretDraft.trim(); if (!secretKey) { setManagementSecretError(t('config.webuiKey.error.empty')); return; } if (secretKey === '123456') { setManagementSecretError(t('config.webuiKey.error.legacyDefault')); return; } if (secretKey.length > 512) { setManagementSecretError(t('config.webuiKey.error.tooLong')); return; } if (/[\u0000-\u001f\u007f-\u009f]/.test(secretKey)) { setManagementSecretError(t('config.webuiKey.error.invalid')); return; } if (secretKey !== managementSecretConfirm.trim()) { setManagementSecretError(t('config.webuiKey.error.mismatch')); return; } const saved = await runMutation( 'management-secret', 'set_core_management_secret_key', { secretKey }, t('config.webuiKey.notice.updated'), ); if (saved) { setManagementSecretDraft(''); setManagementSecretConfirm(''); setShowManagementSecret(false); setManagementSecretError(''); } }; 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 && entry.apiKey !== editingApiKey)) { setFormError(t('config.error.duplicateKey')); return; } const remark = newApiKeyRemark.trim(); if (remark.length > 80) { setFormError(t('config.error.remarkTooLong')); return; } const editing = editingApiKey !== null; const saved = await runMutation( editing ? 'update-key' : 'add-key', editing ? 'update_core_api_key' : 'add_core_api_key', editing ? { originalApiKey: editingApiKey, apiKey, remark } : { apiKey, remark }, editing ? t('config.notice.keyUpdated') : t('config.notice.keyAdded'), ); if (saved) { setAddDialogOpen(false); setEditingApiKey(null); 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 changeRoutingStrategy = async (strategy: string) => { if (strategy === settings?.routingStrategy) { return; } setSavedStatusVisible(false); const saved = await runMutation( 'routing', 'set_core_routing_strategy', { strategy }, t('config.notice.routingUpdated'), ); if (saved) setSavedStatusVisible(true); }; const saveNetworkRoutingSettings = async () => { if (!settings || busyAction !== null) return; const port = Number(portDraft); if (!Number.isInteger(port) || port < 1 || port > 65535) { setPortError(t('config.error.portRange')); showNotice(t('config.error.portRange'), 'error'); return; } const proxyUrl = proxyUrlDraft.trim(); const routingSessionAffinityTtl = sessionTtlDraft.trim(); const networkChanged = port !== settings.port || allowLanDraft !== settings.allowLan; setPortError(''); setSavedStatusVisible(false); setBusyAction('network'); try { const result = await invoke('save_network_routing_settings', { settings: { port, allowLan: allowLanDraft, proxyUrl, routingSessionAffinity: sessionAffinityDraft, routingSessionAffinityTtl, }, }); applySettings(result); setLoadError(''); setSavedStatusVisible(true); if (networkChanged && coreStatus?.running) { try { const status = await invoke('restart_core_process'); publishStatus(status); showNotice(t('config.notice.networkRestarted'), 'success'); } catch (error) { await refreshStatus(); showNotice(t('config.error.networkRestartFailed', { error: String(error) }), 'error'); } } else if (networkChanged) { showNotice(t('config.notice.networkNextStart'), 'success'); } else { showNotice(t('config.notice.networkUpdated'), 'success'); } } catch (error) { applySettings(settings); showNotice(t('config.error.saveFailed', { error: String(error) }), 'error'); void loadSettings(); } finally { setBusyAction(null); } }; const controlsDisabled = loading || settings === null || busyAction !== null; const networkRoutingDirty = Boolean(settings) && ( portDraft !== String(settings?.port) || allowLanDraft !== settings?.allowLan || proxyUrlDraft.trim() !== settings?.proxyUrl || sessionAffinityDraft !== settings?.routingSessionAffinity || sessionTtlDraft.trim() !== settings?.routingSessionAffinityTtl ); const networkStatusLabel = loading ? t('common.loading') : settings === null ? t('common.unavailable') : busyAction === 'network' || busyAction === 'routing' ? t('config.network.saving') : networkRoutingDirty ? t('config.network.unsaved') : savedStatusVisible ? t('config.network.saved') : ''; const networkStatusIsSaved = !loading && settings !== null && busyAction === null && !networkRoutingDirty && savedStatusVisible; const selectedDeleteKey = deleteIndex === null ? '' : settings?.apiKeys[deleteIndex]?.apiKey || ''; const deletingLastKey = deleteIndex !== null && settings?.apiKeys.length === 1; const keyMutationBusy = busyAction === 'add-key' || busyAction === 'update-key'; const managementSecretBusy = busyAction === 'management-secret'; return (
{activeSubpage === 'general' ? (
{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)}
)) ) : (
)}
{!loading && settings ? ( {settings.managementSecretConfigured ? t('config.webuiKey.configured') : t('config.webuiKey.unconfigured')} ) : null}
{t('config.webuiKey.heading')}

{t('config.webuiKey.description')}

{t('config.webuiKey.securityHint')}
void saveManagementSecret(event)} >
{managementSecretError || ' '}
) : activeSubpage === 'network' ? (
{networkStatusLabel ? ( {networkStatusLabel} ) : null}
{t('config.network.allowLan')} {t('config.network.allowLanHint')}
{t('config.network.sessionAffinity')} {t('config.network.sessionAffinityHint')}
{ROUTING_OPTIONS.map((option) => ( ))}
{loading ? t('common.loading') : settings === null ? t('common.unavailable') : routingStrategyLabel(settings.routingStrategy, t)}
) : activeSubpage === 'aliases' ? (
) : (
)} {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)} {deletingLastKey ? (
) : null}
) : 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; }