diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index 735d307..0543a08 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -19,12 +19,10 @@ import { IconChartLine, IconFileText, IconInfo, - IconKey, IconLayoutDashboard, IconScrollText, IconSettings, IconShield, - IconSlidersHorizontal, IconTimer, } from '@/components/ui/icons'; import { INLINE_LOGO_JPEG } from '@/assets/logoInline'; @@ -40,8 +38,6 @@ import { triggerHeaderRefresh } from '@/hooks/useHeaderRefresh'; const sidebarIcons: Record = { dashboard: , - settings: , - apiKeys: , aiProviders: , authFiles: , oauth: , @@ -357,14 +353,12 @@ export function MainLayout() { const navItems = [ { path: '/', label: t('nav.dashboard'), icon: sidebarIcons.dashboard }, - { path: '/settings', label: t('nav.basic_settings'), icon: sidebarIcons.settings }, - { path: '/api-keys', label: t('nav.api_keys'), icon: sidebarIcons.apiKeys }, + { path: '/config', label: t('nav.config_management'), icon: sidebarIcons.config }, { path: '/ai-providers', label: t('nav.ai_providers'), icon: sidebarIcons.aiProviders }, { path: '/auth-files', label: t('nav.auth_files'), icon: sidebarIcons.authFiles }, { path: '/oauth', label: t('nav.oauth', { defaultValue: 'OAuth' }), icon: sidebarIcons.oauth }, { path: '/quota', label: t('nav.quota_management'), icon: sidebarIcons.quota }, { path: '/usage', label: t('nav.usage_stats'), icon: sidebarIcons.usage }, - { path: '/config', label: t('nav.config_management'), icon: sidebarIcons.config }, ...(config?.loggingToFile ? [{ path: '/logs', label: t('nav.logs'), icon: sidebarIcons.logs }] : []), diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index bdb9c20..5005765 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -102,7 +102,7 @@ "oauth": "OAuth Login", "quota_management": "Quota Management", "usage_stats": "Usage Statistics", - "config_management": "Config Management", + "config_management": "Config Panel", "logs": "Logs Viewer", "system_info": "Management Center Info" }, @@ -812,7 +812,7 @@ "upgrade_required_desc": "The current server version does not support the logs viewing feature. Please upgrade to the latest version of CLI Proxy API to use this feature." }, "config_management": { - "title": "Config Management", + "title": "Config Panel", "editor_title": "Configuration File", "reload": "Reload", "save": "Save", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 02658d3..2a49a6b 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -102,7 +102,7 @@ "oauth": "OAuth 登录", "quota_management": "配额管理", "usage_stats": "使用统计", - "config_management": "配置管理", + "config_management": "配置面板", "logs": "日志查看", "system_info": "中心信息" }, @@ -417,7 +417,7 @@ "proxy_url_placeholder": "socks5://username:password@proxy_ip:port/", "prefix_proxy_invalid_json": "该凭证文件不是 JSON 对象,无法编辑。", "prefix_proxy_saved_success": "已更新 \"{{name}}\"", - "card_tools_title": "配置管理", + "card_tools_title": "配置面板", "quota_refresh_single": "刷新额度", "quota_refresh_hint": "仅刷新当前凭证的额度数据", "quota_refresh_success": "已刷新 \"{{name}}\" 的额度", @@ -812,7 +812,7 @@ "upgrade_required_desc": "当前服务器版本不支持日志查看功能,请升级到最新版本的 CLI Proxy API 以使用此功能。" }, "config_management": { - "title": "配置管理", + "title": "配置面板", "editor_title": "配置文件", "reload": "重新加载", "save": "保存", diff --git a/src/pages/ApiKeysPage.module.scss b/src/pages/ApiKeysPage.module.scss deleted file mode 100644 index 224958f..0000000 --- a/src/pages/ApiKeysPage.module.scss +++ /dev/null @@ -1,56 +0,0 @@ -@use '../styles/mixins' as *; - -.container { - width: 100%; -} - -.pageTitle { - font-size: 28px; - font-weight: 700; - color: var(--text-primary); - margin: 0 0 $spacing-xl 0; -} - -.content { - display: flex; - flex-direction: column; - gap: $spacing-lg; -} - -.header { - display: flex; - justify-content: space-between; - align-items: center; - gap: $spacing-md; - - @include mobile { - flex-direction: column; - align-items: stretch; - } -} - -.actions { - display: flex; - gap: $spacing-sm; -} - -.emptyState { - text-align: center; - padding: $spacing-2xl; - color: var(--text-secondary); - - i { - font-size: 48px; - margin-bottom: $spacing-md; - opacity: 0.5; - } - - h3 { - margin: 0 0 $spacing-sm 0; - color: var(--text-primary); - } - - p { - margin: 0; - } -} diff --git a/src/pages/ApiKeysPage.tsx b/src/pages/ApiKeysPage.tsx deleted file mode 100644 index 61573ed..0000000 --- a/src/pages/ApiKeysPage.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Card } from '@/components/ui/Card'; -import { Button } from '@/components/ui/Button'; -import { Input } from '@/components/ui/Input'; -import { Modal } from '@/components/ui/Modal'; -import { EmptyState } from '@/components/ui/EmptyState'; -import { LoadingSpinner } from '@/components/ui/LoadingSpinner'; -import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores'; -import { apiKeysApi } from '@/services/api'; -import { maskApiKey } from '@/utils/format'; -import { isValidApiKeyCharset } from '@/utils/validation'; -import styles from './ApiKeysPage.module.scss'; - -export function ApiKeysPage() { - const { t } = useTranslation(); - const { showNotification, showConfirmation } = useNotificationStore(); - const connectionStatus = useAuthStore((state) => state.connectionStatus); - - const config = useConfigStore((state) => state.config); - const fetchConfig = useConfigStore((state) => state.fetchConfig); - const updateConfigValue = useConfigStore((state) => state.updateConfigValue); - const clearCache = useConfigStore((state) => state.clearCache); - - const [apiKeys, setApiKeys] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); - const [modalOpen, setModalOpen] = useState(false); - const [editingIndex, setEditingIndex] = useState(null); - const [inputValue, setInputValue] = useState(''); - const [saving, setSaving] = useState(false); - - const disableControls = useMemo(() => connectionStatus !== 'connected', [connectionStatus]); - - const loadApiKeys = useCallback( - async (force = false) => { - setLoading(true); - setError(''); - try { - const result = (await fetchConfig('api-keys', force)) as string[] | undefined; - const list = Array.isArray(result) ? result : []; - setApiKeys(list); - } catch (err: any) { - setError(err?.message || t('notification.refresh_failed')); - } finally { - setLoading(false); - } - }, - [fetchConfig, t] - ); - - useEffect(() => { - loadApiKeys(); - }, [loadApiKeys]); - - useEffect(() => { - if (Array.isArray(config?.apiKeys)) { - setApiKeys(config.apiKeys); - } - }, [config?.apiKeys]); - - const openAddModal = () => { - setEditingIndex(null); - setInputValue(''); - setModalOpen(true); - }; - - const openEditModal = (index: number) => { - setEditingIndex(index); - setInputValue(apiKeys[index] ?? ''); - setModalOpen(true); - }; - - const closeModal = () => { - setModalOpen(false); - setInputValue(''); - setEditingIndex(null); - }; - - const handleSave = async () => { - const trimmed = inputValue.trim(); - if (!trimmed) { - showNotification(`${t('notification.please_enter')} ${t('notification.api_key')}`, 'error'); - return; - } - if (!isValidApiKeyCharset(trimmed)) { - showNotification(t('notification.api_key_invalid_chars'), 'error'); - return; - } - - const isEdit = editingIndex !== null; - const nextKeys = isEdit - ? apiKeys.map((key, idx) => (idx === editingIndex ? trimmed : key)) - : [...apiKeys, trimmed]; - - setSaving(true); - try { - if (isEdit && editingIndex !== null) { - await apiKeysApi.update(editingIndex, trimmed); - showNotification(t('notification.api_key_updated'), 'success'); - } else { - await apiKeysApi.replace(nextKeys); - showNotification(t('notification.api_key_added'), 'success'); - } - - setApiKeys(nextKeys); - updateConfigValue('api-keys', nextKeys); - clearCache('api-keys'); - closeModal(); - } catch (err: any) { - showNotification(`${t('notification.update_failed')}: ${err?.message || ''}`, 'error'); - } finally { - setSaving(false); - } - }; - - const handleDelete = (index: number) => { - const apiKeyToDelete = apiKeys[index]; - if (!apiKeyToDelete) { - showNotification(t('notification.delete_failed'), 'error'); - return; - } - - showConfirmation({ - title: t('common.delete'), - message: t('api_keys.delete_confirm'), - variant: 'danger', - onConfirm: async () => { - const latestKeys = useConfigStore.getState().config?.apiKeys; - const currentKeys = Array.isArray(latestKeys) ? latestKeys : []; - const deleteIndex = - currentKeys[index] === apiKeyToDelete - ? index - : currentKeys.findIndex((key) => key === apiKeyToDelete); - - if (deleteIndex < 0) { - showNotification(t('notification.delete_failed'), 'error'); - return; - } - - try { - await apiKeysApi.delete(deleteIndex); - const nextKeys = currentKeys.filter((_, idx) => idx !== deleteIndex); - setApiKeys(nextKeys); - updateConfigValue('api-keys', nextKeys); - clearCache('api-keys'); - showNotification(t('notification.api_key_deleted'), 'success'); - } catch (err: any) { - showNotification(`${t('notification.delete_failed')}: ${err?.message || ''}`, 'error'); - } - } - }); - }; - - const actionButtons = ( -
- - -
- ); - - return ( -
-

{t('api_keys.title')}

- - - {error &&
{error}
} - - {loading ? ( -
- -
- ) : apiKeys.length === 0 ? ( - - {t('api_keys.add_button')} - - } - /> - ) : ( -
- {apiKeys.map((key, index) => ( -
-
-
#{index + 1}
-
{t('api_keys.item_title')}
-
{maskApiKey(String(key || ''))}
-
-
- - -
-
- ))} -
- )} - - - - - - } - > - setInputValue(e.target.value)} - disabled={saving} - /> - -
-
- ); -} diff --git a/src/pages/ConfigPage.module.scss b/src/pages/ConfigPage.module.scss index bf4498f..3c4841a 100644 --- a/src/pages/ConfigPage.module.scss +++ b/src/pages/ConfigPage.module.scss @@ -24,28 +24,50 @@ .tabBar { display: flex; - gap: $spacing-xs; + align-items: center; + gap: 4px; + padding: 4px; margin-bottom: $spacing-lg; - border-bottom: 1px solid var(--border-color); + border: 1px solid var(--border-color); + background: var(--bg-secondary); + border-radius: $radius-full; + width: fit-content; + max-width: 100%; overflow-x: auto; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + + @include mobile { + width: 100%; + + .tabItem { + flex: 1; + } + } } .tabItem { @include button-reset; - padding: 12px 20px; + padding: 10px 16px; font-size: 14px; - font-weight: 500; + font-weight: 600; color: var(--text-secondary); background: transparent; - border-bottom: 2px solid transparent; - margin-bottom: -1px; + border: 1px solid transparent; + border-radius: $radius-full; cursor: pointer; transition: + background-color 0.15s ease, color 0.15s ease, - border-color 0.15s ease; + border-color 0.15s ease, + box-shadow 0.15s ease; &:hover:not(:disabled) { color: var(--text-primary); + background: var(--bg-tertiary); } &:disabled { @@ -53,16 +75,21 @@ cursor: not-allowed; } - &:focus, - &:focus-visible { + &:focus { outline: none; - box-shadow: none; + } + + &:focus-visible { + outline: 2px solid var(--primary-color); + outline-offset: 2px; } } .tabActive { - color: var(--primary-color); - border-bottom-color: var(--primary-color); + color: var(--text-primary); + background: var(--bg-primary); + border-color: var(--border-color); + box-shadow: var(--shadow); } .content { diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx index b941a54..8054bfc 100644 --- a/src/pages/DashboardPage.tsx +++ b/src/pages/DashboardPage.tsx @@ -172,12 +172,12 @@ export function DashboardPage() { const quickStats: QuickStat[] = [ { - label: t('nav.api_keys'), + label: t('dashboard.management_keys'), value: stats.apiKeys ?? '-', icon: , - path: '/api-keys', + path: '/config', loading: loading && stats.apiKeys === null, - sublabel: t('dashboard.management_keys') + sublabel: t('nav.config_management') }, { label: t('nav.ai_providers'), @@ -309,7 +309,7 @@ export function DashboardPage() { )} - + {t('dashboard.edit_settings')} → diff --git a/src/pages/Settings/Settings.module.scss b/src/pages/Settings/Settings.module.scss deleted file mode 100644 index 291d500..0000000 --- a/src/pages/Settings/Settings.module.scss +++ /dev/null @@ -1,164 +0,0 @@ -@use '../../styles/mixins' as *; - -.container { - width: 100%; -} - -.pageTitle { - font-size: 28px; - font-weight: 700; - color: var(--text-primary); - margin: 0 0 $spacing-xl 0; -} - -.grid { - display: grid; - gap: $spacing-lg; - grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); - - @include mobile { - grid-template-columns: 1fr; - } -} - -.settingRow { - display: flex; - align-items: center; - justify-content: space-between; - gap: $spacing-md; -} - -.settingInfo { - flex: 1; - - h4 { - font-size: 16px; - font-weight: 600; - color: var(--text-primary); - margin: 0 0 $spacing-xs 0; - } - - p { - font-size: 14px; - color: var(--text-secondary); - margin: 0; - } -} - -.switch { - position: relative; - display: inline-block; - width: 52px; - height: 28px; - flex-shrink: 0; - - input { - opacity: 0; - width: 0; - height: 0; - - &:checked + .slider { - background-color: var(--primary-color); - - &:before { - transform: translateX(24px); - } - } - - &:focus + .slider { - box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2); - } - } -} - -.slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: var(--border-color); - transition: $transition-fast; - border-radius: $radius-full; - - &:before { - position: absolute; - content: ''; - height: 20px; - width: 20px; - left: 4px; - bottom: 4px; - background-color: white; - transition: $transition-fast; - border-radius: $radius-full; - } -} - -.formGroup { - display: flex; - flex-direction: column; - gap: $spacing-md; -} - -.buttonGroup { - display: flex; - gap: $spacing-sm; -} - -.retryRow { - display: flex; - align-items: flex-end; - gap: $spacing-md; - flex-wrap: wrap; - - :global(.form-group) { - margin-bottom: 0; - } - - @include mobile { - flex-direction: column; - align-items: stretch; - } -} - -.retryRowAligned { - align-items: flex-start; - - .retryButton { - margin-top: calc(1.5em + #{$spacing-xs}); - } - - @include mobile { - align-items: stretch; - - .retryButton { - margin-top: 0; - } - } -} - -.retryRowInputGrow { - :global(.form-group) { - flex: 1 1 0; - min-width: 0; - } - - .retryInput { - width: 100%; - } -} - -.retryInput { - width: 140px; - - @include mobile { - width: 100%; - } -} - -.retryButton { - @include mobile { - width: 100%; - } -} diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx deleted file mode 100644 index 85ccc89..0000000 --- a/src/pages/SettingsPage.tsx +++ /dev/null @@ -1,477 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Card } from '@/components/ui/Card'; -import { ToggleSwitch } from '@/components/ui/ToggleSwitch'; -import { Button } from '@/components/ui/Button'; -import { Input } from '@/components/ui/Input'; -import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores'; -import { configApi } from '@/services/api'; -import type { Config } from '@/types'; -import styles from './Settings/Settings.module.scss'; - -type PendingKey = - | 'debug' - | 'proxy' - | 'retry' - | 'logsMaxSize' - | 'forceModelPrefix' - | 'routingStrategy' - | 'switchProject' - | 'switchPreview' - | 'usage' - | 'loggingToFile' - | 'wsAuth'; - -export function SettingsPage() { - const { t } = useTranslation(); - const { showNotification } = useNotificationStore(); - const connectionStatus = useAuthStore((state) => state.connectionStatus); - const config = useConfigStore((state) => state.config); - const fetchConfig = useConfigStore((state) => state.fetchConfig); - const updateConfigValue = useConfigStore((state) => state.updateConfigValue); - const clearCache = useConfigStore((state) => state.clearCache); - - const [loading, setLoading] = useState(true); - const [proxyValue, setProxyValue] = useState(''); - const [retryValue, setRetryValue] = useState(0); - const [logsMaxTotalSizeMb, setLogsMaxTotalSizeMb] = useState(0); - const [routingStrategy, setRoutingStrategy] = useState('round-robin'); - const [pending, setPending] = useState>({} as Record); - const [error, setError] = useState(''); - - const disableControls = connectionStatus !== 'connected'; - - useEffect(() => { - const load = async () => { - setLoading(true); - setError(''); - try { - const [configResult, logsResult, prefixResult, routingResult] = await Promise.allSettled([ - fetchConfig(), - configApi.getLogsMaxTotalSizeMb(), - configApi.getForceModelPrefix(), - configApi.getRoutingStrategy(), - ]); - - if (configResult.status !== 'fulfilled') { - throw configResult.reason; - } - - const data = configResult.value as Config; - setProxyValue(data?.proxyUrl ?? ''); - setRetryValue(typeof data?.requestRetry === 'number' ? data.requestRetry : 0); - - if (logsResult.status === 'fulfilled' && Number.isFinite(logsResult.value)) { - setLogsMaxTotalSizeMb(Math.max(0, Number(logsResult.value))); - updateConfigValue('logs-max-total-size-mb', Math.max(0, Number(logsResult.value))); - } - - if (prefixResult.status === 'fulfilled') { - updateConfigValue('force-model-prefix', Boolean(prefixResult.value)); - } - - if (routingResult.status === 'fulfilled' && routingResult.value) { - setRoutingStrategy(String(routingResult.value)); - updateConfigValue('routing/strategy', String(routingResult.value)); - } - } catch (err: any) { - setError(err?.message || t('notification.refresh_failed')); - } finally { - setLoading(false); - } - }; - - load(); - }, [fetchConfig, t, updateConfigValue]); - - useEffect(() => { - if (config) { - setProxyValue(config.proxyUrl ?? ''); - if (typeof config.requestRetry === 'number') { - setRetryValue(config.requestRetry); - } - if (typeof config.logsMaxTotalSizeMb === 'number') { - setLogsMaxTotalSizeMb(config.logsMaxTotalSizeMb); - } - if (config.routingStrategy) { - setRoutingStrategy(config.routingStrategy); - } - } - }, [config]); - - const setPendingFlag = (key: PendingKey, value: boolean) => { - setPending((prev) => ({ ...prev, [key]: value })); - }; - - const toggleSetting = async ( - section: PendingKey, - rawKey: 'debug' | 'usage-statistics-enabled' | 'logging-to-file' | 'ws-auth' | 'force-model-prefix', - value: boolean, - updater: (val: boolean) => Promise, - successMessage: string - ) => { - const previous = (() => { - switch (rawKey) { - case 'debug': - return config?.debug ?? false; - case 'usage-statistics-enabled': - return config?.usageStatisticsEnabled ?? false; - case 'logging-to-file': - return config?.loggingToFile ?? false; - case 'ws-auth': - return config?.wsAuth ?? false; - case 'force-model-prefix': - return config?.forceModelPrefix ?? false; - default: - return false; - } - })(); - - setPendingFlag(section, true); - updateConfigValue(rawKey, value); - - try { - await updater(value); - clearCache(rawKey); - showNotification(successMessage, 'success'); - } catch (err: any) { - updateConfigValue(rawKey, previous); - showNotification(`${t('notification.update_failed')}: ${err?.message || ''}`, 'error'); - } finally { - setPendingFlag(section, false); - } - }; - - const handleProxyUpdate = async () => { - const previous = config?.proxyUrl ?? ''; - setPendingFlag('proxy', true); - updateConfigValue('proxy-url', proxyValue); - try { - await configApi.updateProxyUrl(proxyValue.trim()); - clearCache('proxy-url'); - showNotification(t('notification.proxy_updated'), 'success'); - } catch (err: any) { - setProxyValue(previous); - updateConfigValue('proxy-url', previous); - showNotification(`${t('notification.update_failed')}: ${err?.message || ''}`, 'error'); - } finally { - setPendingFlag('proxy', false); - } - }; - - const handleProxyClear = async () => { - const previous = config?.proxyUrl ?? ''; - setPendingFlag('proxy', true); - updateConfigValue('proxy-url', ''); - try { - await configApi.clearProxyUrl(); - clearCache('proxy-url'); - setProxyValue(''); - showNotification(t('notification.proxy_cleared'), 'success'); - } catch (err: any) { - setProxyValue(previous); - updateConfigValue('proxy-url', previous); - showNotification(`${t('notification.update_failed')}: ${err?.message || ''}`, 'error'); - } finally { - setPendingFlag('proxy', false); - } - }; - - const handleRetryUpdate = async () => { - const previous = config?.requestRetry ?? 0; - const parsed = Number(retryValue); - if (!Number.isFinite(parsed) || parsed < 0) { - showNotification(t('login.error_invalid'), 'error'); - setRetryValue(previous); - return; - } - setPendingFlag('retry', true); - updateConfigValue('request-retry', parsed); - try { - await configApi.updateRequestRetry(parsed); - clearCache('request-retry'); - showNotification(t('notification.retry_updated'), 'success'); - } catch (err: any) { - setRetryValue(previous); - updateConfigValue('request-retry', previous); - showNotification(`${t('notification.update_failed')}: ${err?.message || ''}`, 'error'); - } finally { - setPendingFlag('retry', false); - } - }; - - const handleLogsMaxTotalSizeUpdate = async () => { - const previous = config?.logsMaxTotalSizeMb ?? 0; - const parsed = Number(logsMaxTotalSizeMb); - if (!Number.isFinite(parsed) || parsed < 0) { - showNotification(t('login.error_invalid'), 'error'); - setLogsMaxTotalSizeMb(previous); - return; - } - const normalized = Math.max(0, parsed); - setPendingFlag('logsMaxSize', true); - updateConfigValue('logs-max-total-size-mb', normalized); - try { - await configApi.updateLogsMaxTotalSizeMb(normalized); - clearCache('logs-max-total-size-mb'); - showNotification(t('notification.logs_max_total_size_updated'), 'success'); - } catch (err: any) { - setLogsMaxTotalSizeMb(previous); - updateConfigValue('logs-max-total-size-mb', previous); - showNotification(`${t('notification.update_failed')}: ${err?.message || ''}`, 'error'); - } finally { - setPendingFlag('logsMaxSize', false); - } - }; - - const handleRoutingStrategyUpdate = async () => { - const strategy = routingStrategy.trim(); - if (!strategy) { - showNotification(t('login.error_invalid'), 'error'); - return; - } - const previous = config?.routingStrategy ?? 'round-robin'; - setPendingFlag('routingStrategy', true); - updateConfigValue('routing/strategy', strategy); - try { - await configApi.updateRoutingStrategy(strategy); - clearCache('routing/strategy'); - showNotification(t('notification.routing_strategy_updated'), 'success'); - } catch (err: any) { - setRoutingStrategy(previous); - updateConfigValue('routing/strategy', previous); - showNotification(`${t('notification.update_failed')}: ${err?.message || ''}`, 'error'); - } finally { - setPendingFlag('routingStrategy', false); - } - }; - - const quotaSwitchProject = config?.quotaExceeded?.switchProject ?? false; - const quotaSwitchPreview = config?.quotaExceeded?.switchPreviewModel ?? false; - - return ( -
-

{t('basic_settings.title')}

- -
- - {error &&
{error}
} -
- - toggleSetting('debug', 'debug', value, configApi.updateDebug, t('notification.debug_updated')) - } - /> - - - toggleSetting( - 'usage', - 'usage-statistics-enabled', - value, - configApi.updateUsageStatistics, - t('notification.usage_statistics_updated') - ) - } - /> - - - toggleSetting( - 'loggingToFile', - 'logging-to-file', - value, - configApi.updateLoggingToFile, - t('notification.logging_to_file_updated') - ) - } - /> - - - toggleSetting( - 'wsAuth', - 'ws-auth', - value, - configApi.updateWsAuth, - t('notification.ws_auth_updated') - ) - } - /> - - - toggleSetting( - 'forceModelPrefix', - 'force-model-prefix', - value, - configApi.updateForceModelPrefix, - t('notification.force_model_prefix_updated') - ) - } - /> -
-
- - - setProxyValue(e.target.value)} - disabled={disableControls || loading} - /> -
- - -
-
- - -
- setRetryValue(Number(e.target.value))} - disabled={disableControls || loading} - className={styles.retryInput} - /> - -
-
- - -
- setLogsMaxTotalSizeMb(Number(e.target.value))} - disabled={disableControls || loading} - className={styles.retryInput} - /> - -
-
- - -
-
- - -
{t('basic_settings.routing_strategy_hint')}
-
- -
-
- - -
- - (async () => { - const previous = config?.quotaExceeded?.switchProject ?? false; - const nextQuota = { ...(config?.quotaExceeded || {}), switchProject: value }; - setPendingFlag('switchProject', true); - updateConfigValue('quota-exceeded', nextQuota); - try { - await configApi.updateSwitchProject(value); - clearCache('quota-exceeded'); - showNotification(t('notification.quota_switch_project_updated'), 'success'); - } catch (err: any) { - updateConfigValue('quota-exceeded', { ...(config?.quotaExceeded || {}), switchProject: previous }); - showNotification(`${t('notification.update_failed')}: ${err?.message || ''}`, 'error'); - } finally { - setPendingFlag('switchProject', false); - } - })() - } - /> - - (async () => { - const previous = config?.quotaExceeded?.switchPreviewModel ?? false; - const nextQuota = { ...(config?.quotaExceeded || {}), switchPreviewModel: value }; - setPendingFlag('switchPreview', true); - updateConfigValue('quota-exceeded', nextQuota); - try { - await configApi.updateSwitchPreviewModel(value); - clearCache('quota-exceeded'); - showNotification(t('notification.quota_switch_preview_updated'), 'success'); - } catch (err: any) { - updateConfigValue('quota-exceeded', { ...(config?.quotaExceeded || {}), switchPreviewModel: previous }); - showNotification(`${t('notification.update_failed')}: ${err?.message || ''}`, 'error'); - } finally { - setPendingFlag('switchPreview', false); - } - })() - } - /> -
-
-
-
- ); -} diff --git a/src/router/MainRoutes.tsx b/src/router/MainRoutes.tsx index 41a7d3b..06aa2e0 100644 --- a/src/router/MainRoutes.tsx +++ b/src/router/MainRoutes.tsx @@ -1,7 +1,5 @@ import { Navigate, useRoutes, type Location } from 'react-router-dom'; import { DashboardPage } from '@/pages/DashboardPage'; -import { SettingsPage } from '@/pages/SettingsPage'; -import { ApiKeysPage } from '@/pages/ApiKeysPage'; import { AiProvidersPage } from '@/pages/AiProvidersPage'; import { AiProvidersAmpcodeEditPage } from '@/pages/AiProvidersAmpcodeEditPage'; import { AiProvidersClaudeEditPage } from '@/pages/AiProvidersClaudeEditPage'; @@ -24,8 +22,8 @@ import { SystemPage } from '@/pages/SystemPage'; const mainRoutes = [ { path: '/', element: }, { path: '/dashboard', element: }, - { path: '/settings', element: }, - { path: '/api-keys', element: }, + { path: '/settings', element: }, + { path: '/api-keys', element: }, { path: '/ai-providers/gemini/new', element: }, { path: '/ai-providers/gemini/:index', element: }, { path: '/ai-providers/codex/new', element: },