diff --git a/src/components/config/VisualConfigEditor.module.scss b/src/components/config/VisualConfigEditor.module.scss index a9671297..2c998e16 100644 --- a/src/components/config/VisualConfigEditor.module.scss +++ b/src/components/config/VisualConfigEditor.module.scss @@ -140,6 +140,9 @@ .overviewHeader { display: flex; align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 10px; min-width: 0; @include mobile { @@ -172,6 +175,117 @@ background: var(--warning-bg); } +.modeSwitch { + display: inline-flex; + gap: 4px; + padding: 3px; + border: 1px solid var(--border-color); + border-radius: 8px; + background: var(--bg-secondary); +} + +.modeButton { + @include button-reset; + display: inline-flex; + align-items: center; + gap: 6px; + min-height: 30px; + padding: 0 12px; + border-radius: 6px; + color: var(--text-secondary); + font-size: 13px; + font-weight: 700; + line-height: 1.2; + transition: + background-color 0.15s ease, + color 0.15s ease; + + &:hover { + color: var(--text-primary); + } +} + +.modeButtonActive { + background: var(--bg-primary); + color: var(--text-primary); + box-shadow: 0 1px 2px color-mix(in srgb, var(--text-primary) 10%, transparent); +} + +.simpleView { + display: flex; + flex-direction: column; + gap: 16px; + width: 100%; + max-width: 760px; + min-width: 0; +} + +.simpleForm { + display: flex; + flex-direction: column; + gap: 14px; + min-width: 0; +} + +.simpleBanner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; + padding: 12px 14px; + border: 1px solid var(--warning-border); + border-radius: 8px; + background: var(--warning-bg); + color: var(--warning-text); + font-size: 13px; + font-weight: 600; + line-height: 1.5; +} + +.simpleBannerAction { + @include button-reset; + flex: 0 0 auto; + padding: 6px 12px; + border: 1px solid var(--warning-border); + border-radius: 6px; + color: var(--warning-text); + font-size: 12px; + font-weight: 700; + transition: background-color 0.15s ease; + + &:hover { + background: color-mix(in srgb, var(--warning-text) 10%, transparent); + } +} + +.simpleMore { + @include button-reset; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + width: 100%; + min-height: 46px; + padding: 12px 16px; + border: 1px dashed var(--border-color); + border-radius: 8px; + color: var(--text-secondary); + font-size: 13px; + font-weight: 700; + text-align: center; + transition: + background-color 0.15s ease, + border-color 0.15s ease, + color 0.15s ease; + + &:hover { + border-color: var(--text-primary); + color: var(--text-primary); + background: color-mix(in srgb, var(--text-primary) 4%, transparent); + } +} + .workspace { display: flex; flex-direction: column; diff --git a/src/components/config/VisualConfigEditor.tsx b/src/components/config/VisualConfigEditor.tsx index 9c8b694a..ff1070d9 100644 --- a/src/components/config/VisualConfigEditor.tsx +++ b/src/components/config/VisualConfigEditor.tsx @@ -10,15 +10,18 @@ import { } from 'react'; import { useTranslation } from 'react-i18next'; import { usePageTransitionLayer } from '@/components/common/PageTransitionLayer'; +import { Collapsible } from '@/components/ui/Collapsible'; import { Input } from '@/components/ui/Input'; import { Select } from '@/components/ui/Select'; import { ToggleSwitch } from '@/components/ui/ToggleSwitch'; import { IconCode, - IconDiamond, IconKey, + IconNetwork, IconSatellite, - IconSettings, + IconScrollText, + IconShield, + IconSlidersHorizontal, IconTimer, type IconProps, } from '@/components/ui/icons'; @@ -41,7 +44,18 @@ import { } from './VisualConfigEditorBlocks'; import styles from './VisualConfigEditor.module.scss'; -type VisualSectionId = 'server' | 'auth' | 'system' | 'quota' | 'streaming' | 'payload'; +type VisualSectionId = + | 'connectivity' + | 'network' + | 'logging' + | 'quota' + | 'streaming' + | 'advanced' + | 'payload'; + +type EditorMode = 'simple' | 'full'; + +const EDITOR_MODE_STORAGE_KEY = 'config-management:editor-mode'; type VisualSection = { id: VisualSectionId; @@ -178,13 +192,22 @@ export function VisualConfigEditor({ const nonstreamKeepaliveInputId = useId(); const nonstreamKeepaliveHintId = `${nonstreamKeepaliveInputId}-hint`; const nonstreamKeepaliveErrorId = `${nonstreamKeepaliveInputId}-error`; - const [activeSectionId, setActiveSectionId] = useState('server'); + const [mode, setMode] = useState(() => { + const saved = localStorage.getItem(EDITOR_MODE_STORAGE_KEY); + return saved === 'full' ? 'full' : 'simple'; + }); + const [activeSectionId, setActiveSectionId] = useState('connectivity'); const sectionRefs = useRef>>({}); const mobileNavScrollerRef = useRef(null); const mobileNavButtonRefs = useRef>>( {} ); + const handleModeChange = useCallback((next: EditorMode) => { + setMode(next); + localStorage.setItem(EDITOR_MODE_STORAGE_KEY, next); + }, []); + const isKeepaliveDisabled = values.streaming.keepaliveSeconds === '' || values.streaming.keepaliveSeconds === '0'; const isNonstreamKeepaliveDisabled = @@ -270,31 +293,32 @@ export function VisualConfigEditor({ const sections = useMemo( () => [ { - id: 'server', - title: t('config_management.visual.sections.server.title'), - icon: IconSettings, + id: 'connectivity', + title: t('config_management.visual.sections.connectivity.title'), + icon: IconKey, errorCount: countErrors(['port']), }, { - id: 'auth', - title: t('config_management.visual.sections.auth.title'), - icon: IconKey, - errorCount: 0, - }, - { - id: 'system', - title: t('config_management.visual.sections.system.title'), - icon: IconDiamond, + id: 'network', + title: t('config_management.visual.sections.network.title'), + icon: IconNetwork, errorCount: countErrors([ - 'errorLogsMaxFiles', - 'logsMaxTotalSizeMb', - 'redisUsageQueueRetentionSeconds', 'requestRetry', 'maxRetryCredentials', 'maxRetryInterval', 'authAutoRefreshWorkers', ]), }, + { + id: 'logging', + title: t('config_management.visual.sections.logging.title'), + icon: IconScrollText, + errorCount: countErrors([ + 'errorLogsMaxFiles', + 'logsMaxTotalSizeMb', + 'redisUsageQueueRetentionSeconds', + ]), + }, { id: 'quota', title: t('config_management.visual.sections.quota.title'), @@ -311,6 +335,12 @@ export function VisualConfigEditor({ 'streaming.nonstreamKeepaliveInterval', ]), }, + { + id: 'advanced', + title: t('config_management.visual.sections.advanced.title'), + icon: IconShield, + errorCount: 0, + }, { id: 'payload', title: t('config_management.visual.sections.payload.title'), @@ -323,9 +353,15 @@ export function VisualConfigEditor({ const hasValidationIssues = sections.some((section) => section.errorCount > 0) || hasPayloadValidationErrors; + // Validation errors that live in fields not surfaced by simple mode (everything except `port`). + const hasHiddenValidationIssues = + (Object.keys(validationErrors ?? {}) as VisualConfigFieldPath[]).some( + (field) => field !== 'port' && Boolean(validationErrors?.[field]) + ) || hasPayloadValidationErrors; const activeSection = sections.find((section) => section.id === activeSectionId) ?? sections[0]; useEffect(() => { + if (mode !== 'full') return undefined; if (!isCurrentLayer) return undefined; if (typeof IntersectionObserver === 'undefined') return undefined; @@ -350,10 +386,10 @@ export function VisualConfigEditor({ } return () => observer.disconnect(); - }, [isCurrentLayer, sections]); + }, [isCurrentLayer, mode, sections]); useEffect(() => { - if (!isCurrentLayer || !isMobile) return; + if (mode !== 'full' || !isCurrentLayer || !isMobile) return; const scroller = mobileNavScrollerRef.current; const button = mobileNavButtonRefs.current[activeSectionId]; if (!scroller || !button) return; @@ -371,7 +407,7 @@ export function VisualConfigEditor({ left: targetLeft, behavior: 'smooth', }); - }, [activeSectionId, isCurrentLayer, isMobile]); + }, [activeSectionId, isCurrentLayer, isMobile, mode]); const handleSectionJump = useCallback((sectionId: VisualSectionId) => { setActiveSectionId(sectionId); @@ -382,6 +418,80 @@ export function VisualConfigEditor({ }); }, []); + // Shared high-frequency field blocks — reused verbatim by both the simple view and full mode + // so the two layouts never drift apart. + const portField = ( + onChange({ port: e.target.value })} + disabled={disabled} + error={portError} + /> + ); + + const proxyUrlField = ( + onChange({ proxyUrl: e.target.value })} + disabled={disabled} + /> + ); + + const apiKeysField = ( +
+ +
+ ); + + const debugToggle = ( + onChange({ debug })} + /> + ); + + const loggingToFileToggle = ( + onChange({ loggingToFile })} + /> + ); + + const quotaSwitchProjectToggle = ( + onChange({ quotaSwitchProject })} + /> + ); + + const quotaSwitchPreviewModelToggle = ( + onChange({ quotaSwitchPreviewModel })} + /> + ); + const navContent = (
{sections.map((section, index) => { @@ -422,11 +532,35 @@ export function VisualConfigEditor({
+
+ + +
- - {t('config_management.visual.quick_jump', { defaultValue: '快速跳转' })} - - {activeSection?.title} + {mode === 'full' && activeSection ? ( + {activeSection.title} + ) : null} {hasValidationIssues ? ( {t('config_management.visual.validation.validation_blocked')} @@ -436,836 +570,851 @@ export function VisualConfigEditor({
-
- {isMobile ? ( -
-
- {sections.map((section, index) => ( - - ))} + {mode === 'simple' ? ( +
+ {hasHiddenValidationIssues ? ( +
+ {t('config_management.visual.mode.validation_banner')} +
+ ) : null} + +
+ {portField} + {apiKeysField} + {proxyUrlField} + {debugToggle} + {loggingToFileToggle} + {quotaSwitchProjectToggle} + {quotaSwitchPreviewModelToggle}
- ) : null} - - -
- { - sectionRefs.current.server = node; - }} - indexLabel="01" - icon={} - title={t('config_management.visual.sections.server.title')} - description={t('config_management.visual.sections.server.description')} + +
+ ) : ( +
+ {isMobile ? ( +
+
+ {sections.map((section, index) => ( + + ))} +
+
+ ) : null} + + + +
+ { + sectionRefs.current.connectivity = node; + }} + indexLabel="01" + icon={} + title={t('config_management.visual.sections.connectivity.title')} + description={t('config_management.visual.sections.connectivity.description')} + > + + + onChange({ host: e.target.value })} + disabled={disabled} + /> + {portField} + + onChange({ host: e.target.value })} + label={t('config_management.visual.sections.auth.auth_dir')} + placeholder="~/.cli-proxy-api" + value={values.authDir} + onChange={(e) => onChange({ authDir: e.target.value })} disabled={disabled} + hint={t('config_management.visual.sections.auth.auth_dir_hint')} /> - onChange({ port: e.target.value })} + + {apiKeysField} + + + + onChange({ tlsEnable })} + /> + + {values.tlsEnable ? ( + <> + + + onChange({ tlsCert: e.target.value })} + disabled={disabled} + /> + onChange({ tlsKey: e.target.value })} + disabled={disabled} + /> + + + ) : null} + + + + + + + onChange({ rmAllowRemote })} + /> + onChange({ rmDisableControlPanel })} + /> + + onChange({ rmDisableAutoUpdatePanel }) + } + /> + + + onChange({ rmSecretKey: e.target.value })} + disabled={disabled} + /> + onChange({ rmPanelRepo: e.target.value })} + disabled={disabled} + /> + + + + + + + { + sectionRefs.current.network = node; + }} + indexLabel="02" + icon={} + title={t('config_management.visual.sections.network.title')} + description={t('config_management.visual.sections.network.description')} + > + + + {proxyUrlField} + onChange({ requestRetry: e.target.value })} + disabled={disabled} + error={requestRetryError} + /> + onChange({ maxRetryCredentials: e.target.value })} + disabled={disabled} + hint={t('config_management.visual.sections.network.max_retry_credentials_hint')} + error={maxRetryCredentialsError} + /> + onChange({ maxRetryInterval: e.target.value })} + disabled={disabled} + error={maxRetryIntervalError} + /> + onChange({ authAutoRefreshWorkers: e.target.value })} + disabled={disabled} + hint={t( + 'config_management.visual.sections.network.auth_auto_refresh_workers_hint' + )} + error={authAutoRefreshWorkersError} + /> + + + onChange({ + disableImageGeneration: + nextValue as VisualConfigValues['disableImageGeneration'], + }) + } + /> + + onChange({ gptImage2BaseModel: e.target.value })} + disabled={disabled} + hint={t('config_management.visual.sections.network.gpt_image_2_base_model_hint')} + /> + onChange({ routingSessionAffinityTTL: e.target.value })} + disabled={disabled} + /> + + + + onChange({ forceModelPrefix })} + /> + onChange({ passthroughHeaders })} + /> + onChange({ disableCooling })} + /> + onChange({ routingSessionAffinity })} + /> + onChange({ wsAuth })} + /> + onChange({ enableGeminiCliEndpoint })} + /> + + + + + { + sectionRefs.current.logging = node; + }} + indexLabel="03" + icon={} + title={t('config_management.visual.sections.logging.title')} + description={t('config_management.visual.sections.logging.description')} + > + + + {debugToggle} + onChange({ commercialMode })} + /> + {loggingToFileToggle} + + + + onChange({ logsMaxTotalSizeMb: e.target.value })} + disabled={disabled} + error={logsMaxSizeError} + /> + onChange({ errorLogsMaxFiles: e.target.value })} + disabled={disabled} + error={errorLogsMaxFilesError} + /> + onChange({ redisUsageQueueRetentionSeconds: e.target.value })} + disabled={disabled} + hint={t('config_management.visual.sections.system.redis_usage_retention_hint')} + error={redisUsageQueueRetentionError} + /> + + + + onChange({ usageStatisticsEnabled })} + /> + + + + + { + sectionRefs.current.quota = node; + }} + indexLabel="04" + icon={} + title={t('config_management.visual.sections.quota.title')} + description={t('config_management.visual.sections.quota.description')} + > + + {quotaSwitchProjectToggle} + {quotaSwitchPreviewModelToggle} + onChange({ quotaAntigravityCredits })} /> + - { + sectionRefs.current.streaming = node; + }} + indexLabel="05" + icon={} + title={t('config_management.visual.sections.streaming.title')} + description={t('config_management.visual.sections.streaming.description')} + > + + + +
+ + onChange({ + streaming: { + ...values.streaming, + keepaliveSeconds: e.target.value, + }, + }) + } + disabled={disabled} + /> + {isKeepaliveDisabled ? ( + + {t('config_management.visual.sections.streaming.disabled')} + + ) : null} +
+
+ + + onChange({ + streaming: { + ...values.streaming, + bootstrapRetries: e.target.value, + }, + }) + } + disabled={disabled} + hint={t('config_management.visual.sections.streaming.bootstrap_hint')} + error={bootstrapRetriesError} + /> +
+ + + +
+ + onChange({ + streaming: { + ...values.streaming, + nonstreamKeepaliveInterval: e.target.value, + }, + }) + } + disabled={disabled} + /> + {isNonstreamKeepaliveDisabled ? ( + + {t('config_management.visual.sections.streaming.disabled')} + + ) : null} +
+
+
+
+ + + { + sectionRefs.current.advanced = node; + }} + indexLabel="06" + icon={} + title={t('config_management.visual.sections.advanced.title')} + description={t('config_management.visual.sections.advanced.description')} + > + - onChange({ tlsEnable })} - /> + + onChange({ pluginsEnabled })} + /> + - {values.tlsEnable ? ( - <> - + +
+ + +
+ {t('config_management.visual.sections.system.plugin_store_sources_hint')} +
+
+
+ + + + + + onChange({ antigravitySignatureCacheEnabled }) + } + /> + + onChange({ antigravitySignatureBypassStrict }) + } + /> + + + + + + +
+

+ {t('config_management.visual.sections.headers.claude_title')} +

+
onChange({ tlsCert: e.target.value })} + label={t('config_management.visual.sections.headers.user_agent')} + placeholder="claude-cli/2.1.44 (external, sdk-cli)" + value={values.claudeHeaderUserAgent} + onChange={(e) => onChange({ claudeHeaderUserAgent: e.target.value })} disabled={disabled} /> onChange({ tlsKey: e.target.value })} + label={t('config_management.visual.sections.headers.package_version')} + placeholder="0.74.0" + value={values.claudeHeaderPackageVersion} + onChange={(e) => onChange({ claudeHeaderPackageVersion: e.target.value })} + disabled={disabled} + /> + onChange({ claudeHeaderRuntimeVersion: e.target.value })} + disabled={disabled} + /> + onChange({ claudeHeaderOs: e.target.value })} + disabled={disabled} + /> + onChange({ claudeHeaderArch: e.target.value })} + disabled={disabled} + /> + onChange({ claudeHeaderTimeout: e.target.value })} disabled={disabled} /> - - ) : null} + + + onChange({ claudeHeaderStabilizeDeviceProfile }) + } + /> + + +
+

+ {t('config_management.visual.sections.headers.codex_title')} +

+
+ + onChange({ codexHeaderUserAgent: e.target.value })} + disabled={disabled} + /> + onChange({ codexHeaderBetaFeatures: e.target.value })} + disabled={disabled} + /> + + + onChange({ codexIdentityConfuse })} + /> + +
+
-
+ + - - - - onChange({ rmAllowRemote })} - /> - onChange({ rmDisableControlPanel })} - /> - - onChange({ rmDisableAutoUpdatePanel }) - } - /> - - - onChange({ rmSecretKey: e.target.value })} - disabled={disabled} - /> - onChange({ rmPanelRepo: e.target.value })} - disabled={disabled} - /> - - - - - - - { - sectionRefs.current.auth = node; - }} - indexLabel="02" - icon={} - title={t('config_management.visual.sections.auth.title')} - description={t('config_management.visual.sections.auth.description')} - > - - onChange({ authDir: e.target.value })} - disabled={disabled} - hint={t('config_management.visual.sections.auth.auth_dir_hint')} - /> -
- -
-
-
- - { - sectionRefs.current.system = node; - }} - indexLabel="03" - icon={} - title={t('config_management.visual.sections.system.title')} - description={t('config_management.visual.sections.system.description')} - > - - - onChange({ debug })} - /> - onChange({ commercialMode })} - /> - onChange({ loggingToFile })} - /> - onChange({ pluginsEnabled })} - /> - - - -
- - { + sectionRefs.current.payload = node; + }} + indexLabel="07" + icon={} + title={t('config_management.visual.sections.payload.title')} + description={t('config_management.visual.sections.payload.description')} + > + + + -
- {t('config_management.visual.sections.system.plugin_store_sources_hint')} -
-
-
+ - - onChange({ logsMaxTotalSizeMb: e.target.value })} - disabled={disabled} - error={logsMaxSizeError} - /> - onChange({ errorLogsMaxFiles: e.target.value })} - disabled={disabled} - error={errorLogsMaxFilesError} - /> - onChange({ redisUsageQueueRetentionSeconds: e.target.value })} - disabled={disabled} - hint={t('config_management.visual.sections.system.redis_usage_retention_hint')} - error={redisUsageQueueRetentionError} - /> - - - onChange({ usageStatisticsEnabled })} - /> - - onChange({ antigravitySignatureCacheEnabled }) - } - /> - - onChange({ antigravitySignatureBypassStrict }) - } - /> - - - - -
-

- {t('config_management.visual.sections.headers.claude_title')} -

-
- - onChange({ claudeHeaderUserAgent: e.target.value })} - disabled={disabled} - /> - onChange({ claudeHeaderPackageVersion: e.target.value })} - disabled={disabled} - /> - onChange({ claudeHeaderRuntimeVersion: e.target.value })} - disabled={disabled} - /> - onChange({ claudeHeaderOs: e.target.value })} - disabled={disabled} - /> - onChange({ claudeHeaderArch: e.target.value })} - disabled={disabled} - /> - onChange({ claudeHeaderTimeout: e.target.value })} - disabled={disabled} - /> - - - - onChange({ claudeHeaderStabilizeDeviceProfile }) - } - /> - - -
-

- {t('config_management.visual.sections.headers.codex_title')} -

-
- - onChange({ codexHeaderUserAgent: e.target.value })} - disabled={disabled} - /> - onChange({ codexHeaderBetaFeatures: e.target.value })} - disabled={disabled} - /> - - - onChange({ codexIdentityConfuse })} - /> - -
-
- - - - - onChange({ proxyUrl: e.target.value })} - disabled={disabled} - /> - onChange({ requestRetry: e.target.value })} - disabled={disabled} - error={requestRetryError} - /> - onChange({ maxRetryCredentials: e.target.value })} - disabled={disabled} - hint={t( - 'config_management.visual.sections.network.max_retry_credentials_hint' - )} - error={maxRetryCredentialsError} - /> - onChange({ maxRetryInterval: e.target.value })} - disabled={disabled} - error={maxRetryIntervalError} - /> - onChange({ authAutoRefreshWorkers: e.target.value })} - disabled={disabled} - hint={t( - 'config_management.visual.sections.network.auth_auto_refresh_workers_hint' - )} - error={authAutoRefreshWorkersError} - /> - - - onChange({ - disableImageGeneration: - nextValue as VisualConfigValues['disableImageGeneration'], - }) - } - /> - - onChange({ gptImage2BaseModel: e.target.value })} - disabled={disabled} - hint={t( - 'config_management.visual.sections.network.gpt_image_2_base_model_hint' - )} - /> - onChange({ routingSessionAffinityTTL: e.target.value })} - disabled={disabled} - /> - - - - onChange({ forceModelPrefix })} - /> - onChange({ passthroughHeaders })} - /> - onChange({ disableCooling })} - /> - onChange({ routingSessionAffinity })} - /> - onChange({ wsAuth })} - /> - onChange({ enableGeminiCliEndpoint })} - /> - - - -
-
- - { - sectionRefs.current.quota = node; - }} - indexLabel="04" - icon={} - title={t('config_management.visual.sections.quota.title')} - description={t('config_management.visual.sections.quota.description')} - > - - onChange({ quotaSwitchProject })} - /> - onChange({ quotaSwitchPreviewModel })} - /> - onChange({ quotaAntigravityCredits })} - /> - - - - { - sectionRefs.current.streaming = node; - }} - indexLabel="05" - icon={} - title={t('config_management.visual.sections.streaming.title')} - description={t('config_management.visual.sections.streaming.description')} - > - - - -
- - onChange({ - streaming: { - ...values.streaming, - keepaliveSeconds: e.target.value, - }, - }) - } - disabled={disabled} - /> - {isKeepaliveDisabled ? ( - - {t('config_management.visual.sections.streaming.disabled')} - - ) : null} -
-
+ + - - onChange({ - streaming: { - ...values.streaming, - bootstrapRetries: e.target.value, - }, - }) - } - disabled={disabled} - hint={t('config_management.visual.sections.streaming.bootstrap_hint')} - error={bootstrapRetriesError} - /> -
- - - -
- - onChange({ - streaming: { - ...values.streaming, - nonstreamKeepaliveInterval: e.target.value, - }, - }) - } - disabled={disabled} - /> - {isNonstreamKeepaliveDisabled ? ( - - {t('config_management.visual.sections.streaming.disabled')} - - ) : null} -
-
-
-
-
+ + - { - sectionRefs.current.payload = node; - }} - indexLabel="06" - icon={} - title={t('config_management.visual.sections.payload.title')} - description={t('config_management.visual.sections.payload.description')} - > - - - - + + + - - - - - - - - - - - - - - - - - + + + + + +
-
+ )}
); } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 2b1c8697..13a9c54a 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -909,7 +909,28 @@ "visual": { "notice": "Visual mode covers common fields. Review or edit unsupported config.yaml entries in source mode.", "quick_jump": "Quick Jump", + "mode": { + "simple": "Simple", + "full": "Full", + "label": "Editor mode", + "more_settings": "Switch to full mode to see all {{total}} sections", + "validation_banner": "Some advanced settings have validation errors and are blocking save. Switch to full mode to fix them.", + "switch_to_full": "Switch to full mode" + }, "sections": { + "connectivity": { + "title": "Access & Authentication", + "description": "Server address, port, auth directory, and API keys" + }, + "logging": { + "title": "Logging & Diagnostics", + "description": "Debug, log output, and usage statistics" + }, + "advanced": { + "title": "Advanced & Experimental", + "description": "Header spoofing, signature cache, plugin sources, and other rarely changed settings", + "disclosure": "Show advanced & experimental settings (hidden by default)" + }, "server": { "title": "Server Configuration", "description": "Basic server settings", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index ad3f1647..41adcd84 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -896,7 +896,28 @@ "visual": { "notice": "Визуальный режим охватывает основные поля. Остальные параметры config.yaml по-прежнему нужно проверять или редактировать в режиме исходника.", "quick_jump": "Быстрый переход", + "mode": { + "simple": "Простой", + "full": "Полный", + "label": "Режим редактора", + "more_settings": "Переключитесь в полный режим, чтобы увидеть все разделы ({{total}})", + "validation_banner": "В некоторых дополнительных параметрах есть ошибки проверки, сохранение заблокировано. Переключитесь в полный режим, чтобы исправить их.", + "switch_to_full": "Перейти в полный режим" + }, "sections": { + "connectivity": { + "title": "Доступ и аутентификация", + "description": "Адрес сервера, порт, каталог аутентификации и ключи API" + }, + "logging": { + "title": "Логи и диагностика", + "description": "Отладка, вывод логов и статистика использования" + }, + "advanced": { + "title": "Дополнительно и эксперименты", + "description": "Подмена заголовков, кэш подписей, источники плагинов и другие редко изменяемые настройки", + "disclosure": "Показать дополнительные и экспериментальные настройки (скрыты по умолчанию)" + }, "server": { "title": "Настройки сервера", "description": "Базовые параметры сервера", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 34d5032e..7a6f951e 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -909,7 +909,28 @@ "visual": { "notice": "可视化模式覆盖常用字段,未覆盖的配置仍需在源文件模式中查看或编辑。", "quick_jump": "快速跳转", + "mode": { + "simple": "简单", + "full": "完整", + "label": "编辑模式", + "more_settings": "切换到完整模式,查看全部 {{total}} 个配置分区", + "validation_banner": "部分高级配置项存在校验错误,已阻止保存。请切换到完整模式进行修复。", + "switch_to_full": "切换完整模式" + }, "sections": { + "connectivity": { + "title": "接入与认证", + "description": "服务地址、端口、认证目录与 API 密钥" + }, + "logging": { + "title": "日志与诊断", + "description": "调试、日志输出与使用统计" + }, + "advanced": { + "title": "高级与实验", + "description": "请求头伪装、签名缓存、插件源等不常修改的设置", + "disclosure": "展开高级与实验配置(默认隐藏)" + }, "server": { "title": "服务器配置", "description": "基础服务器设置", diff --git a/src/i18n/locales/zh-TW.json b/src/i18n/locales/zh-TW.json index 546d6de4..45be1a53 100644 --- a/src/i18n/locales/zh-TW.json +++ b/src/i18n/locales/zh-TW.json @@ -935,7 +935,28 @@ "visual": { "notice": "視覺化模式涵蓋常用欄位,未涵蓋的設定仍需在原始檔模式中查看或編輯。", "quick_jump": "快速跳轉", + "mode": { + "simple": "簡單", + "full": "完整", + "label": "編輯模式", + "more_settings": "切換到完整模式,檢視全部 {{total}} 個設定分區", + "validation_banner": "部分進階設定項存在驗證錯誤,已阻止儲存。請切換到完整模式進行修正。", + "switch_to_full": "切換完整模式" + }, "sections": { + "connectivity": { + "title": "接入與認證", + "description": "服務位址、連接埠、認證目錄與 API 金鑰" + }, + "logging": { + "title": "日誌與診斷", + "description": "除錯、日誌輸出與使用統計" + }, + "advanced": { + "title": "進階與實驗", + "description": "請求標頭偽裝、簽章快取、外掛來源等不常修改的設定", + "disclosure": "展開進階與實驗設定(預設隱藏)" + }, "server": { "title": "伺服器設定", "description": "基本伺服器設定",