From d6f5c45aa51e249664d9ccf2ac5930b067603aef Mon Sep 17 00:00:00 2001 From: LTbinglingfeng Date: Mon, 18 May 2026 03:08:05 +0800 Subject: [PATCH] feat(i18n): add Home control plane settings and various enhancements - Introduced Home control plane configuration options in English, Russian, Simplified Chinese, and Traditional Chinese locales. - Added new fields for error log retention, usage statistics, and image generation settings in visual configuration. - Enhanced payload model entries to include header management and condition matching capabilities. - Updated default visual configuration values to accommodate new settings. --- .../config/VisualConfigEditor.module.scss | 33 +- src/components/config/VisualConfigEditor.tsx | 363 +++++++++++- .../config/VisualConfigEditorBlocks.tsx | 528 +++++++++++++++-- src/hooks/useVisualConfig.ts | 556 +++++++++++++++--- src/i18n/locales/en.json | 77 ++- src/i18n/locales/ru.json | 77 ++- src/i18n/locales/zh-CN.json | 77 ++- src/i18n/locales/zh-TW.json | 77 ++- src/types/visualConfig.ts | 75 +++ 9 files changed, 1695 insertions(+), 168 deletions(-) diff --git a/src/components/config/VisualConfigEditor.module.scss b/src/components/config/VisualConfigEditor.module.scss index ee03622..c176f9a 100644 --- a/src/components/config/VisualConfigEditor.module.scss +++ b/src/components/config/VisualConfigEditor.module.scss @@ -602,13 +602,41 @@ .payloadRuleModelRow { display: grid; - grid-template-columns: 1fr 160px auto; + grid-template-columns: 1fr 160px auto auto; gap: 8px; align-items: center; } .payloadRuleModelRowProtocolFirst { - grid-template-columns: 160px 1fr auto; + grid-template-columns: 160px 1fr auto auto; +} + +.payloadModelGroup { + display: flex; + flex-direction: column; + gap: 8px; +} + +.payloadModelAdvanced { + display: flex; + flex-direction: column; + gap: 12px; + margin-left: 10px; + padding-left: 12px; + border-left: 2px solid var(--border-color); +} + +.payloadAdvancedGrid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 10px; +} + +.payloadHeaderRow { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; + gap: 8px; + align-items: center; } .payloadRuleParamRow { @@ -660,6 +688,7 @@ @media (max-width: 900px) { .payloadRuleModelRow, .payloadRuleModelRowProtocolFirst, + .payloadHeaderRow, .payloadRuleParamRow, .payloadFilterModelRow { grid-template-columns: minmax(0, 1fr); diff --git a/src/components/config/VisualConfigEditor.tsx b/src/components/config/VisualConfigEditor.tsx index e45b904..afc466e 100644 --- a/src/components/config/VisualConfigEditor.tsx +++ b/src/components/config/VisualConfigEditor.tsx @@ -40,13 +40,7 @@ import { } from './VisualConfigEditorBlocks'; import styles from './VisualConfigEditor.module.scss'; -type VisualSectionId = - | 'server' - | 'auth' - | 'system' - | 'quota' - | 'streaming' - | 'payload'; +type VisualSectionId = 'server' | 'auth' | 'system' | 'quota' | 'streaming' | 'payload'; type VisualSection = { id: VisualSectionId; @@ -175,6 +169,8 @@ export function VisualConfigEditor({ const isMobile = useMediaQuery('(max-width: 768px)'); const routingStrategyLabelId = useId(); const routingStrategyHintId = `${routingStrategyLabelId}-hint`; + const disableImageGenerationLabelId = useId(); + const disableImageGenerationHintId = `${disableImageGenerationLabelId}-hint`; const keepaliveInputId = useId(); const keepaliveHintId = `${keepaliveInputId}-hint`; const keepaliveErrorId = `${keepaliveInputId}-error`; @@ -195,10 +191,20 @@ export function VisualConfigEditor({ values.streaming.nonstreamKeepaliveInterval === '0'; const portError = getValidationMessage(t, validationErrors?.port); + const homePortError = getValidationMessage(t, validationErrors?.homePort); const logsMaxSizeError = getValidationMessage(t, validationErrors?.logsMaxTotalSizeMb); + const errorLogsMaxFilesError = getValidationMessage(t, validationErrors?.errorLogsMaxFiles); + const redisUsageQueueRetentionError = getValidationMessage( + t, + validationErrors?.redisUsageQueueRetentionSeconds + ); const requestRetryError = getValidationMessage(t, validationErrors?.requestRetry); const maxRetryCredentialsError = getValidationMessage(t, validationErrors?.maxRetryCredentials); const maxRetryIntervalError = getValidationMessage(t, validationErrors?.maxRetryInterval); + const authAutoRefreshWorkersError = getValidationMessage( + t, + validationErrors?.authAutoRefreshWorkers + ); const keepaliveError = getValidationMessage(t, validationErrors?.['streaming.keepaliveSeconds']); const bootstrapRetriesError = getValidationMessage( t, @@ -233,6 +239,23 @@ export function VisualConfigEditor({ (payloadFilterRules: PayloadFilterRule[]) => onChange({ payloadFilterRules }), [onChange] ); + const disableImageGenerationOptions = useMemo( + () => [ + { + value: 'false', + label: t('config_management.visual.sections.network.disable_image_generation_false'), + }, + { + value: 'true', + label: t('config_management.visual.sections.network.disable_image_generation_true'), + }, + { + value: 'chat', + label: t('config_management.visual.sections.network.disable_image_generation_chat'), + }, + ], + [t] + ); const countErrors = useCallback( (fields: VisualConfigFieldPath[]) => @@ -246,7 +269,7 @@ export function VisualConfigEditor({ id: 'server', title: t('config_management.visual.sections.server.title'), icon: IconSettings, - errorCount: countErrors(['port']), + errorCount: countErrors(['port', 'homePort']), }, { id: 'auth', @@ -259,10 +282,13 @@ export function VisualConfigEditor({ title: t('config_management.visual.sections.system.title'), icon: IconDiamond, errorCount: countErrors([ + 'errorLogsMaxFiles', 'logsMaxTotalSizeMb', + 'redisUsageQueueRetentionSeconds', 'requestRetry', 'maxRetryCredentials', 'maxRetryInterval', + 'authAutoRefreshWorkers', ]), }, { @@ -513,6 +539,102 @@ export function VisualConfigEditor({ + + + + onChange({ homeEnabled })} + /> + + onChange({ homeDisableClusterDiscovery }) + } + /> + + + onChange({ homeHost: e.target.value })} + disabled={disabled} + /> + onChange({ homePort: e.target.value })} + disabled={disabled} + error={homePortError} + /> + onChange({ homePassword: e.target.value })} + disabled={disabled} + /> + + +
+

+ {t('config_management.visual.sections.home.tls_title')} +

+

+ {t('config_management.visual.sections.home.tls_description')} +

+
+ + onChange({ homeTlsEnable })} + /> + + onChange({ homeTlsInsecureSkipVerify }) + } + /> + + + onChange({ homeTlsServerName: e.target.value })} + disabled={disabled} + /> + onChange({ homeTlsCaCert: e.target.value })} + disabled={disabled} + /> + +
+
+ onChange({ rmDisableControlPanel })} /> + + onChange({ rmDisableAutoUpdatePanel }) + } + /> + 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({ authAutoRefreshWorkers: e.target.value })} + disabled={disabled} + hint={t( + 'config_management.visual.sections.network.auth_auto_refresh_workers_hint' + )} + error={authAutoRefreshWorkersError} + /> + + onChange({ forceModelPrefix })} /> + onChange({ passthroughHeaders })} + /> + onChange({ disableCooling })} + /> onChange({ wsAuth })} /> + onChange({ enableGeminiCliEndpoint })} + /> diff --git a/src/components/config/VisualConfigEditorBlocks.tsx b/src/components/config/VisualConfigEditorBlocks.tsx index 6f24a15..3c77d06 100644 --- a/src/components/config/VisualConfigEditorBlocks.tsx +++ b/src/components/config/VisualConfigEditorBlocks.tsx @@ -8,6 +8,7 @@ import styles from './VisualConfigEditor.module.scss'; import { copyToClipboard } from '@/utils/clipboard'; import type { PayloadFilterRule, + PayloadHeaderEntry, PayloadModelEntry, PayloadParamEntry, PayloadParamValidationErrorCode, @@ -449,6 +450,17 @@ const StringListEditor = memo(function StringListEditor({ ); }); +function hasPayloadModelAdvancedSettings(model: PayloadModelEntry) { + return Boolean( + model.fromProtocol || + (model.headers?.length ?? 0) > 0 || + (model.match?.length ?? 0) > 0 || + (model.notMatch?.length ?? 0) > 0 || + (model.exist?.length ?? 0) > 0 || + (model.notExist?.length ?? 0) > 0 + ); +} + export const PayloadRulesEditor = memo(function PayloadRulesEditor({ value, disabled, @@ -465,6 +477,31 @@ export const PayloadRulesEditor = memo(function PayloadRulesEditor({ const { t } = useTranslation(); const rules = value; const protocolOptions = useMemo(() => buildProtocolOptions(t, rules), [rules, t]); + const fromProtocolOptions = useMemo( + () => [ + { + value: '', + label: t('config_management.visual.payload_rules.provider_default'), + }, + { + value: 'openai', + label: t('config_management.visual.payload_rules.provider_openai'), + }, + { + value: 'responses', + label: t('config_management.visual.payload_rules.provider_responses'), + }, + { + value: 'gemini', + label: t('config_management.visual.payload_rules.provider_gemini'), + }, + { + value: 'claude', + label: t('config_management.visual.payload_rules.provider_claude'), + }, + ], + [t] + ); const payloadValueTypeOptions = useMemo( () => VISUAL_CONFIG_PAYLOAD_VALUE_TYPE_OPTIONS.map((option) => ({ @@ -480,6 +517,7 @@ export const PayloadRulesEditor = memo(function PayloadRulesEditor({ ], [t] ); + const [modelAdvancedOverrides, setModelAdvancedOverrides] = useState>({}); const addRule = () => onChange([...rules, { id: makeClientId(), models: [], params: [] }]); const removeRule = (ruleIndex: number) => onChange(rules.filter((_, i) => i !== ruleIndex)); @@ -509,6 +547,79 @@ export const PayloadRulesEditor = memo(function PayloadRulesEditor({ }); }; + const toggleModelAdvanced = (modelId: string, defaultExpanded: boolean) => { + setModelAdvancedOverrides((current) => ({ + ...current, + [modelId]: !(current[modelId] ?? defaultExpanded), + })); + }; + + const addHeader = (ruleIndex: number, modelIndex: number) => { + const rule = rules[ruleIndex]; + const model = rule.models[modelIndex]; + updateModel(ruleIndex, modelIndex, { + headers: [...(model.headers ?? []), { id: makeClientId(), name: '', value: '' }], + }); + }; + + const updateHeader = ( + ruleIndex: number, + modelIndex: number, + headerIndex: number, + patch: Partial + ) => { + const model = rules[ruleIndex].models[modelIndex]; + updateModel(ruleIndex, modelIndex, { + headers: (model.headers ?? []).map((header, i) => + i === headerIndex ? { ...header, ...patch } : header + ), + }); + }; + + const removeHeader = (ruleIndex: number, modelIndex: number, headerIndex: number) => { + const model = rules[ruleIndex].models[modelIndex]; + updateModel(ruleIndex, modelIndex, { + headers: (model.headers ?? []).filter((_, i) => i !== headerIndex), + }); + }; + + const addCondition = (ruleIndex: number, modelIndex: number, key: 'match' | 'notMatch') => { + const model = rules[ruleIndex].models[modelIndex]; + updateModel(ruleIndex, modelIndex, { + [key]: [ + ...(model[key] ?? []), + { id: makeClientId(), path: '', valueType: 'string', value: '' }, + ], + }); + }; + + const updateCondition = ( + ruleIndex: number, + modelIndex: number, + key: 'match' | 'notMatch', + conditionIndex: number, + patch: Partial + ) => { + const model = rules[ruleIndex].models[modelIndex]; + updateModel(ruleIndex, modelIndex, { + [key]: (model[key] ?? []).map((condition, i) => + i === conditionIndex ? { ...condition, ...patch } : condition + ), + }); + }; + + const removeCondition = ( + ruleIndex: number, + modelIndex: number, + key: 'match' | 'notMatch', + conditionIndex: number + ) => { + const model = rules[ruleIndex].models[modelIndex]; + updateModel(ruleIndex, modelIndex, { + [key]: (model[key] ?? []).filter((_, i) => i !== conditionIndex), + }); + }; + const addParam = (ruleIndex: number) => { const rule = rules[ruleIndex]; const nextParam: PayloadParamEntry = { @@ -558,6 +669,62 @@ export const PayloadRulesEditor = memo(function PayloadRulesEditor({ return getValidationMessage(t, errorCode); }; + const renderConditionValueEditor = ( + ruleIndex: number, + modelIndex: number, + key: 'match' | 'notMatch', + conditionIndex: number, + condition: PayloadParamEntry + ) => { + if (condition.valueType === 'boolean') { + return ( +