diff --git a/src/features/config/ConfigPage.tsx b/src/features/config/ConfigPage.tsx
index e21d9fce..85f35076 100644
--- a/src/features/config/ConfigPage.tsx
+++ b/src/features/config/ConfigPage.tsx
@@ -43,7 +43,6 @@ import { SectionConnectivity } from './components/sections/SectionConnectivity';
import { SectionLogging } from './components/sections/SectionLogging';
import { SectionNetwork } from './components/sections/SectionNetwork';
import { SectionPayload } from './components/sections/SectionPayload';
-import { SectionQuota } from './components/sections/SectionQuota';
import { SectionStreaming } from './components/sections/SectionStreaming';
import styles from './ConfigPage.module.scss';
@@ -283,8 +282,6 @@ export function ConfigPage() {
return ;
case 'logging':
return ;
- case 'quota':
- return ;
case 'streaming':
return ;
case 'advanced':
diff --git a/src/features/config/components/fields/sharedFields.tsx b/src/features/config/components/fields/sharedFields.tsx
index 99432952..56c754ae 100644
--- a/src/features/config/components/fields/sharedFields.tsx
+++ b/src/features/config/components/fields/sharedFields.tsx
@@ -1,4 +1,4 @@
-// 8 个高频字段的唯一渲染源:SectionCommon(常用 tab)与各正典分区共用这些组件,
+// 6 个高频字段的唯一渲染源:SectionCommon(常用 tab)与各正典分区共用这些组件,
// 两处渲染结构性不可能漂移(旧简单模式靠共享 JSX 常量达成同一目的)。
// 注意:只挂载激活 tab,所以设置行的 DOM id 不会重复。
@@ -138,38 +138,3 @@ export function LoggingToFileToggle({ values, disabled, onChange, wide }: Shared
/>
);
}
-
-export function QuotaSwitchProjectToggle({ values, disabled, onChange, wide }: SharedFieldProps) {
- const { t } = useTranslation();
- return (
- onChange({ quotaSwitchProject })}
- />
- );
-}
-
-export function QuotaSwitchPreviewModelToggle({
- values,
- disabled,
- onChange,
- wide,
-}: SharedFieldProps) {
- const { t } = useTranslation();
- return (
- onChange({ quotaSwitchPreviewModel })}
- />
- );
-}
diff --git a/src/features/config/components/sections/SectionAdvanced.tsx b/src/features/config/components/sections/SectionAdvanced.tsx
index b6a0a7ea..469940b6 100644
--- a/src/features/config/components/sections/SectionAdvanced.tsx
+++ b/src/features/config/components/sections/SectionAdvanced.tsx
@@ -21,7 +21,7 @@ import { SectionOAuthBehavior } from './SectionOAuthBehavior';
const Icon = CONFIG_TAB_ICONS.advanced;
-/** 06 高级与实验:插件源、供应商敏感词、签名缓存与请求头默认值。 */
+/** 05 高级与实验:插件源、供应商敏感词、签名缓存与请求头默认值。 */
export function SectionAdvanced({
values,
validationErrors,
@@ -127,6 +127,15 @@ export function SectionAdvanced({
defaultOpen={false}
>
+
+ onChange({ quotaAntigravityCredits })}
+ />
+
-
-
);
diff --git a/src/features/config/components/sections/SectionPayload.tsx b/src/features/config/components/sections/SectionPayload.tsx
index 7a5a0690..9f7bd977 100644
--- a/src/features/config/components/sections/SectionPayload.tsx
+++ b/src/features/config/components/sections/SectionPayload.tsx
@@ -16,7 +16,7 @@ export type SectionPayloadProps = ConfigSectionProps & {
hasPayloadValidationErrors: boolean;
};
-/** 07 Payload 配置:默认值 / 原始 JSON / 覆盖 / 过滤 五个规则组。 */
+/** 06 Payload 配置:默认值 / 原始 JSON / 覆盖 / 过滤 五个规则组。 */
export function SectionPayload({
values,
disabled,
diff --git a/src/features/config/components/sections/SectionQuota.tsx b/src/features/config/components/sections/SectionQuota.tsx
deleted file mode 100644
index 35b75d0a..00000000
--- a/src/features/config/components/sections/SectionQuota.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { useTranslation } from 'react-i18next';
-import { CONFIG_TAB_ICONS, SECTION_INDEX_LABELS } from '../../constants';
-import type { ConfigSectionProps } from '../../types';
-import { SectionCard } from '../SectionCard';
-import { SettingList, ToggleSetting } from '../fields/FieldPrimitives';
-import { QuotaSwitchPreviewModelToggle, QuotaSwitchProjectToggle } from '../fields/sharedFields';
-
-const Icon = CONFIG_TAB_ICONS.quota;
-
-/** 04 配额回退:配额耗尽时的回退策略(两个开关默认 true)。 */
-export function SectionQuota({ values, disabled, animateIn, onChange }: ConfigSectionProps) {
- const { t } = useTranslation();
- const fieldProps = { values, disabled, onChange };
-
- return (
- }
- title={t('config_management.visual.sections.quota.title')}
- description={t('config_management.visual.sections.quota.description')}
- animateIn={animateIn}
- >
-
-
-
- onChange({ quotaAntigravityCredits })}
- />
-
-
- );
-}
diff --git a/src/features/config/components/sections/SectionStreaming.tsx b/src/features/config/components/sections/SectionStreaming.tsx
index b1d82d78..f2163ca0 100644
--- a/src/features/config/components/sections/SectionStreaming.tsx
+++ b/src/features/config/components/sections/SectionStreaming.tsx
@@ -7,7 +7,7 @@ import { getValidationMessage } from '../blocks/shared';
const Icon = CONFIG_TAB_ICONS.streaming;
-/** 05 流式传输:keepalive 与 bootstrap 重试;nonstream-keepalive-interval 是顶层 YAML 键。 */
+/** 04 流式传输:keepalive 与 bootstrap 重试;nonstream-keepalive-interval 是顶层 YAML 键。 */
export function SectionStreaming({
values,
validationErrors,
diff --git a/src/features/config/constants.ts b/src/features/config/constants.ts
index 96d828a2..05fa5297 100644
--- a/src/features/config/constants.ts
+++ b/src/features/config/constants.ts
@@ -7,7 +7,6 @@ import {
IconScrollText,
IconShield,
IconSlidersHorizontal,
- IconTimer,
type IconProps,
} from '@/components/ui/icons';
import type { VisualConfigFieldPath } from '@/types/visualConfig';
@@ -16,14 +15,13 @@ import type { VisualSectionId } from './searchIndex';
/** 编辑模式:可视化表单 or YAML 源码。 */
export type ConfigEditorMode = 'visual' | 'source';
-/** 顶部 tabs:'common'(常用,原简单模式的继任者)+ 7 个正典分区。 */
+/** 顶部 tabs:'common'(常用,原简单模式的继任者)+ 6 个正典分区。 */
export type ConfigTabId = 'common' | VisualSectionId;
export const CONFIG_SECTION_IDS = [
'connectivity',
'network',
'logging',
- 'quota',
'streaming',
'advanced',
'payload',
@@ -31,15 +29,14 @@ export const CONFIG_SECTION_IDS = [
export const CONFIG_TAB_IDS: readonly ConfigTabId[] = ['common', ...CONFIG_SECTION_IDS];
-/** 分区序号(01–07)。常用 tab 是别名视图,不占序号。 */
+/** 分区序号(01–06)。常用 tab 是别名视图,不占序号。 */
export const SECTION_INDEX_LABELS: Record = {
connectivity: '01',
network: '02',
logging: '03',
- quota: '04',
- streaming: '05',
- advanced: '06',
- payload: '07',
+ streaming: '04',
+ advanced: '05',
+ payload: '06',
};
export const CONFIG_TAB_ICONS: Record> = {
@@ -47,13 +44,12 @@ export const CONFIG_TAB_ICONS: Record> = {
connectivity: IconKey,
network: IconNetwork,
logging: IconScrollText,
- quota: IconTimer,
streaming: IconSatellite,
advanced: IconShield,
payload: IconCode,
};
-/** 常用 tab 的 8 个字段(原简单模式),渲染源与正典分区共享(fields/sharedFields.tsx)。 */
+/** 常用 tab 的 6 个字段(原简单模式),渲染源与正典分区共享(fields/sharedFields.tsx)。 */
export const COMMON_FIELD_IDS = [
'host',
'port',
@@ -61,8 +57,6 @@ export const COMMON_FIELD_IDS = [
'proxyUrl',
'debug',
'loggingToFile',
- 'quotaSwitchProject',
- 'quotaSwitchPreviewModel',
] as const;
/**
@@ -81,7 +75,6 @@ export const SECTION_VALIDATION_FIELDS: Record = {
errorLogsMaxFiles: ['errorLogsMaxFiles'],
redisUsageQueueRetentionSeconds: ['redisUsageQueueRetentionSeconds'],
usageStatisticsEnabled: ['usageStatisticsEnabled'],
- // ── quota ─────────────────────────────────────────────────────────────────
- quotaSwitchProject: ['quotaSwitchProject'],
- quotaSwitchPreviewModel: ['quotaSwitchPreviewModel'],
- quotaAntigravityCredits: ['quotaAntigravityCredits'],
// ── streaming ─────────────────────────────────────────────────────────────
streamingKeepaliveSeconds: ['streaming.keepaliveSeconds'],
streamingBootstrapRetries: ['streaming.bootstrapRetries'],
streamingNonstreamKeepalive: ['streaming.nonstreamKeepaliveInterval'],
// ── advanced ──────────────────────────────────────────────────────────────
+ quotaAntigravityCredits: ['quotaAntigravityCredits'],
pluginsEnabled: ['pluginsEnabled'],
pluginStoreSources: ['pluginStoreSources'],
pluginStoreAuth: ['pluginStoreAuth'],
diff --git a/src/features/config/searchIndex.ts b/src/features/config/searchIndex.ts
index 32828cef..b6fce592 100644
--- a/src/features/config/searchIndex.ts
+++ b/src/features/config/searchIndex.ts
@@ -9,7 +9,7 @@
// extra entry anywhere fails CI.
export type VisualSectionId =
- 'connectivity' | 'network' | 'logging' | 'quota' | 'streaming' | 'advanced' | 'payload';
+ 'connectivity' | 'network' | 'logging' | 'streaming' | 'advanced' | 'payload';
export interface ConfigFieldSearchEntry {
/** Stable anchor id; matches FieldAnchor's `fieldId` and the rendered DOM id. */
@@ -518,27 +518,6 @@ export const CONFIG_FIELD_SEARCH_INDEX: ConfigFieldSearchEntry[] = [
hintKey: L('sections.system.usage_statistics_enabled_desc'),
yamlKeys: ['observability', 'usage', 'usage-statistics-enabled'],
},
- // ── quota ─────────────────────────────────────────────────────────────────
- {
- fieldId: 'quotaSwitchProject',
- sectionId: 'quota',
- labelKey: L('sections.quota.switch_project'),
- hintKey: L('sections.quota.switch_project_desc'),
- yamlKeys: ['quota-exceeded', 'switch-project'],
- },
- {
- fieldId: 'quotaSwitchPreviewModel',
- sectionId: 'quota',
- labelKey: L('sections.quota.switch_preview_model'),
- hintKey: L('sections.quota.switch_preview_model_desc'),
- yamlKeys: ['quota-exceeded', 'switch-preview-model'],
- },
- {
- fieldId: 'quotaAntigravityCredits',
- sectionId: 'quota',
- labelKey: L('sections.quota.antigravity_credits'),
- yamlKeys: ['oauth', 'providers', 'antigravity', 'antigravity-credits'],
- },
// ── streaming ─────────────────────────────────────────────────────────────
{
fieldId: 'streamingKeepaliveSeconds',
@@ -562,6 +541,12 @@ export const CONFIG_FIELD_SEARCH_INDEX: ConfigFieldSearchEntry[] = [
yamlKeys: ['requests', 'nonstream-keepalive-interval'],
},
// ── advanced ──────────────────────────────────────────────────────────────
+ {
+ fieldId: 'quotaAntigravityCredits',
+ sectionId: 'advanced',
+ labelKey: L('sections.advanced.antigravity_credits'),
+ yamlKeys: ['oauth', 'providers', 'antigravity', 'antigravity-credits'],
+ },
{
fieldId: 'pluginsEnabled',
sectionId: 'advanced',
diff --git a/src/hooks/useVisualConfig.ts b/src/hooks/useVisualConfig.ts
index 88c3a73c..9ef3cf72 100644
--- a/src/hooks/useVisualConfig.ts
+++ b/src/hooks/useVisualConfig.ts
@@ -1250,8 +1250,6 @@ function getNextDirtyFields(
'maxRetryCredentials',
'maxRetryInterval',
'wsAuth',
- 'quotaSwitchProject',
- 'quotaSwitchPreviewModel',
'quotaAntigravityCredits',
'routingStrategy',
'routingSessionAffinity',
@@ -1426,7 +1424,6 @@ function parseVisualValuesFromYaml(yamlContent: string): VisualConfigValues {
const v8ObservabilityUsage = asRecord(v8Observability?.['usage']);
const tls = asRecord(v8Server?.['tls']);
const remoteManagement = asRecord(parsed['management']);
- const quotaExceeded = asRecord(parsed['quota-exceeded']);
const routing = asRecord(parsed.routing);
const payload = asRecord(v8Requests?.['payload']);
const streaming = asRecord(v8Requests?.['streaming']);
@@ -1524,12 +1521,6 @@ function parseVisualValuesFromYaml(yamlContent: string): VisualConfigValues {
? codexHeaderDefaults['beta-features']
: '',
- quotaSwitchProject: Boolean(
- quotaExceeded?.['switch-project'] ?? DEFAULT_VISUAL_VALUES.quotaSwitchProject
- ),
- quotaSwitchPreviewModel: Boolean(
- quotaExceeded?.['switch-preview-model'] ?? DEFAULT_VISUAL_VALUES.quotaSwitchPreviewModel
- ),
quotaAntigravityCredits: Boolean(antigravity?.['antigravity-credits'] ?? false),
routingStrategy: parseRoutingStrategy(routing?.strategy),
@@ -1979,25 +1970,12 @@ export function useVisualConfig() {
deleteIfMapEmpty(doc, ['oauth', 'providers', 'codex', 'header-defaults']);
}
- const quotaDirty =
- dirtyFields.has('quotaSwitchProject') ||
- dirtyFields.has('quotaSwitchPreviewModel') ||
- dirtyFields.has('quotaAntigravityCredits');
- if (quotaDirty) {
- ensureMapInDoc(doc, ['quota-exceeded']);
- if (dirtyFields.has('quotaSwitchProject')) {
- doc.setIn(['quota-exceeded', 'switch-project'], values.quotaSwitchProject);
- }
- if (dirtyFields.has('quotaSwitchPreviewModel')) {
- doc.setIn(['quota-exceeded', 'switch-preview-model'], values.quotaSwitchPreviewModel);
- }
- if (dirtyFields.has('quotaAntigravityCredits')) {
- doc.setIn(
- ['oauth', 'providers', 'antigravity', 'antigravity-credits'],
- values.quotaAntigravityCredits
- );
- }
- deleteIfMapEmpty(doc, ['quota-exceeded']);
+ if (dirtyFields.has('quotaAntigravityCredits')) {
+ ensureMapInDoc(doc, ['oauth', 'providers', 'antigravity']);
+ doc.setIn(
+ ['oauth', 'providers', 'antigravity', 'antigravity-credits'],
+ values.quotaAntigravityCredits
+ );
}
const routingDirty =
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 03ba7148..5b8e34fc 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -1279,6 +1279,7 @@
"description": "Debug, log output, and usage statistics"
},
"advanced": {
+ "antigravity_credits": "Use Antigravity Credits",
"title": "Advanced & Experimental",
"description": "Header spoofing, signature cache, plugin sources, and other rarely changed settings",
"plugins_title": "Plugins",
@@ -1427,15 +1428,6 @@
"ws_auth": "WebSocket Authentication",
"ws_auth_desc": "Require authentication for AI Studio WebSocket connections (oauth.providers.aistudio.ws-auth)"
},
- "quota": {
- "title": "Quota Fallback",
- "description": "Fallback strategy when quota is exceeded",
- "switch_project": "Switch Project",
- "switch_project_desc": "Automatically switch to another project when quota is exceeded",
- "switch_preview_model": "Switch to Preview Model",
- "switch_preview_model_desc": "Switch to preview model version when quota is exceeded",
- "antigravity_credits": "Use Antigravity Credits"
- },
"streaming": {
"title": "Streaming Configuration",
"description": "Keepalive and bootstrap retry settings",
diff --git a/src/i18n/locales/ko.json b/src/i18n/locales/ko.json
index f895856e..7d4b2747 100644
--- a/src/i18n/locales/ko.json
+++ b/src/i18n/locales/ko.json
@@ -1279,6 +1279,7 @@
"description": "디버그, 로그 출력 및 사용량 통계"
},
"advanced": {
+ "antigravity_credits": "Antigravity 크레딧 사용",
"title": "고급 및 실험",
"description": "헤더 위장, 서명 캐시, 플러그인 소스 및 거의 변경되지 않는 기타 설정",
"plugins_title": "플러그인",
@@ -1427,15 +1428,6 @@
"ws_auth": "WebSocket 인증",
"ws_auth_desc": "AI Studio WebSocket 연결에 인증을 요구합니다 (oauth.providers.aistudio.ws-auth)"
},
- "quota": {
- "title": "할당량 폴백",
- "description": "할당량이 초과되었을 때의 폴백 전략",
- "switch_project": "프로젝트 전환",
- "switch_project_desc": "할당량이 초과되면 다른 프로젝트로 자동 전환합니다.",
- "switch_preview_model": "프리뷰 모델로 전환",
- "switch_preview_model_desc": "할당량이 초과되면 프리뷰 모델 버전으로 전환합니다.",
- "antigravity_credits": "Antigravity 크레딧 사용"
- },
"streaming": {
"title": "스트리밍 설정",
"description": "Keepalive 및 부트스트랩 재시도 설정",
diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json
index 97676d73..2c5b5492 100644
--- a/src/i18n/locales/ru.json
+++ b/src/i18n/locales/ru.json
@@ -1282,6 +1282,7 @@
"description": "Отладка, вывод логов и статистика использования"
},
"advanced": {
+ "antigravity_credits": "Использовать Antigravity Credits",
"title": "Дополнительно и эксперименты",
"description": "Подмена заголовков, кэш подписей, источники плагинов и другие редко изменяемые настройки",
"plugins_title": "Плагины",
@@ -1430,15 +1431,6 @@
"ws_auth": "Аутентификация WebSocket",
"ws_auth_desc": "Аутентификация WebSocket AI Studio (oauth.providers.aistudio.ws-auth)"
},
- "quota": {
- "title": "Резерв по квоте",
- "description": "Стратегия при превышении квоты",
- "switch_project": "Переключить проект",
- "switch_project_desc": "Автоматически переходить на другой проект при превышении квоты",
- "switch_preview_model": "Переключить на preview-модель",
- "switch_preview_model_desc": "Переключаться на preview-версию модели при превышении квоты",
- "antigravity_credits": "Использовать Antigravity Credits"
- },
"streaming": {
"title": "Настройки стриминга",
"description": "Параметры keepalive и повторов запуска",
diff --git a/src/i18n/locales/vi.json b/src/i18n/locales/vi.json
index 244ebe69..05678b74 100644
--- a/src/i18n/locales/vi.json
+++ b/src/i18n/locales/vi.json
@@ -1072,6 +1072,7 @@
"description": "Debug, xuất log và thống kê sử dụng"
},
"advanced": {
+ "antigravity_credits": "Dùng credit Antigravity",
"title": "Nâng cao & Thử nghiệm",
"description": "Giả mạo header, cache chữ ký, nguồn plugin và các cài đặt ít thay đổi khác",
"plugins_title": "Plugin",
@@ -1220,15 +1221,6 @@
"proxy_url_sponsor_hint": "Chưa có proxy phù hợp?",
"max_retry_interval_hint": "Đặt thành 0 hoặc số nguyên âm để bỏ qua thời gian chờ cooldown giữa các lượt thử lại."
},
- "quota": {
- "title": "Quota dự phòng",
- "description": "Chiến lược dự phòng khi vượt quota",
- "switch_project": "Chuyển Project",
- "switch_project_desc": "Tự động chuyển sang project khác khi vượt quota",
- "switch_preview_model": "Chuyển sang model Preview",
- "switch_preview_model_desc": "Chuyển sang phiên bản model preview khi vượt quota",
- "antigravity_credits": "Dùng credit Antigravity"
- },
"streaming": {
"title": "Cấu hình Streaming",
"description": "Cài đặt keepalive và thử lại bootstrap",
diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json
index de93bac2..6ab2c835 100644
--- a/src/i18n/locales/zh-CN.json
+++ b/src/i18n/locales/zh-CN.json
@@ -1278,6 +1278,7 @@
"description": "调试、日志输出与使用统计"
},
"advanced": {
+ "antigravity_credits": "使用Antigravity Credits",
"title": "高级与实验",
"description": "请求头伪装、签名缓存、插件源等不常修改的设置",
"plugins_title": "插件",
@@ -1426,15 +1427,6 @@
"ws_auth": "WebSocket 认证",
"ws_auth_desc": "启用 AI Studio WebSocket 连接认证(oauth.providers.aistudio.ws-auth)"
},
- "quota": {
- "title": "配额回退",
- "description": "配额耗尽时的回退策略",
- "switch_project": "切换项目",
- "switch_project_desc": "配额耗尽时自动切换到其他项目",
- "switch_preview_model": "切换预览模型",
- "switch_preview_model_desc": "配额耗尽时切换到预览版本模型",
- "antigravity_credits": "使用Antigravity Credits"
- },
"streaming": {
"title": "流式传输配置",
"description": "Keepalive 与 bootstrap 重试设置",
diff --git a/src/i18n/locales/zh-TW.json b/src/i18n/locales/zh-TW.json
index 2aadb72b..7400c668 100644
--- a/src/i18n/locales/zh-TW.json
+++ b/src/i18n/locales/zh-TW.json
@@ -1306,6 +1306,7 @@
"description": "除錯、日誌輸出與使用統計"
},
"advanced": {
+ "antigravity_credits": "使用Antigravity Credits",
"title": "進階與實驗",
"description": "請求標頭偽裝、簽章快取、外掛來源等不常修改的設定",
"plugins_title": "外掛",
@@ -1454,15 +1455,6 @@
"ws_auth": "WebSocket 驗證",
"ws_auth_desc": "啟用 AI Studio WebSocket 連線驗證(oauth.providers.aistudio.ws-auth)"
},
- "quota": {
- "title": "配額回退",
- "description": "配額耗盡時的回退策略",
- "switch_project": "切換專案",
- "switch_project_desc": "配額耗盡時自動切換到其他專案",
- "switch_preview_model": "切換預覽模型",
- "switch_preview_model_desc": "配額耗盡時切換到預覽版本模型",
- "antigravity_credits": "使用Antigravity Credits"
- },
"streaming": {
"title": "串流傳輸設定",
"description": "Keepalive 與 bootstrap 重試設定",
diff --git a/src/services/api/transformers.ts b/src/services/api/transformers.ts
index 34579545..9b24ef15 100644
--- a/src/services/api/transformers.ts
+++ b/src/services/api/transformers.ts
@@ -360,10 +360,7 @@ export const normalizeConfigResponse = (raw: unknown): Config => {
config.apiKeys = Array.isArray(keys)
? keys.filter((key): key is string => typeof key === 'string')
: [];
- const quota = at('quota-exceeded');
config.quotaExceeded = {
- switchProject: isRecord(quota) ? normalizeBoolean(quota['switch-project']) : false,
- switchPreviewModel: isRecord(quota) ? normalizeBoolean(quota['switch-preview-model']) : false,
antigravityCredits:
normalizeBoolean(at('oauth.providers.antigravity.antigravity-credits')) ?? false,
};
diff --git a/src/types/config.ts b/src/types/config.ts
index 2dd89298..de8a38c5 100644
--- a/src/types/config.ts
+++ b/src/types/config.ts
@@ -6,8 +6,6 @@
import type { GeminiKeyConfig, ProviderKeyConfig, OpenAIProviderConfig } from './provider';
export interface QuotaExceededConfig {
- switchProject?: boolean;
- switchPreviewModel?: boolean;
antigravityCredits?: boolean;
}
diff --git a/src/types/visualConfig.ts b/src/types/visualConfig.ts
index c95d3c64..28203ecb 100644
--- a/src/types/visualConfig.ts
+++ b/src/types/visualConfig.ts
@@ -187,8 +187,6 @@ export type VisualConfigValues = {
disableImageGeneration: DisableImageGenerationMode;
gptImage2BaseModel: string;
authAutoRefreshWorkers: string;
- quotaSwitchProject: boolean;
- quotaSwitchPreviewModel: boolean;
/** OAuth-only: oauth.providers.antigravity.antigravity-credits. */
quotaAntigravityCredits: boolean;
routingStrategy: RoutingStrategy;
@@ -291,8 +289,6 @@ export const DEFAULT_VISUAL_VALUES: VisualConfigValues = {
disableImageGeneration: 'false',
gptImage2BaseModel: '',
authAutoRefreshWorkers: '',
- quotaSwitchProject: false,
- quotaSwitchPreviewModel: false,
quotaAntigravityCredits: false,
routingStrategy: 'round-robin',
routingSessionAffinity: false,
diff --git a/tests/configFieldParity.test.ts b/tests/configFieldParity.test.ts
index b9d438ef..6fa576c6 100644
--- a/tests/configFieldParity.test.ts
+++ b/tests/configFieldParity.test.ts
@@ -113,7 +113,7 @@ describe('JSX anchor parity (source scan)', () => {
});
describe('registry consistency', () => {
- test('tab id registry is common + the seven canonical sections', () => {
+ test('tab id registry is common + the canonical sections', () => {
expect([...CONFIG_TAB_IDS]).toEqual(['common', ...CONFIG_SECTION_IDS]);
});
@@ -138,13 +138,19 @@ describe('registry consistency', () => {
});
describe('v8 YAML search paths', () => {
+ test('deprecated quota switches are absent from values and search metadata', () => {
+ for (const fieldId of ['quotaSwitchProject', 'quotaSwitchPreviewModel']) {
+ expect(LEAF_VALUE_KEYS.has(fieldId)).toBe(false);
+ expect(findConfigFieldById(fieldId)).toBeUndefined();
+ expect(FIELD_VALUE_KEYS).not.toHaveProperty(fieldId);
+ }
+ });
+
for (const [fieldId, path] of [
['apiKeys', 'access.api-keys'],
['commercialMode', 'server.commercial-mode'],
['requestRetry', 'routing.retry.request-retry'],
['routingStrategy', 'routing.strategy'],
- ['quotaSwitchProject', 'quota-exceeded.switch-project'],
- ['quotaSwitchPreviewModel', 'quota-exceeded.switch-preview-model'],
['quotaAntigravityCredits', 'oauth.providers.antigravity.antigravity-credits'],
['wsAuth', 'oauth.providers.aistudio.ws-auth'],
['codexHeaderUserAgent', 'oauth.providers.codex.header-defaults.user-agent'],
diff --git a/tests/configNormalizationV8.test.ts b/tests/configNormalizationV8.test.ts
index 00abedf7..8747654f 100644
--- a/tests/configNormalizationV8.test.ts
+++ b/tests/configNormalizationV8.test.ts
@@ -9,12 +9,28 @@ describe('v8 persisted configuration normalization', () => {
).toBe(false);
});
- test('reads Antigravity credits without requiring the unrelated quota-exceeded block', () => {
+ test('reads only Antigravity credits from the OAuth provider path', () => {
+ expect(normalizeConfigResponse({}).quotaExceeded).toEqual({ antigravityCredits: false });
+ for (const enabled of [true, false]) {
+ const oauth = { providers: { antigravity: { 'antigravity-credits': enabled } } };
+ expect(normalizeConfigResponse({ oauth }).quotaExceeded).toEqual({
+ antigravityCredits: enabled,
+ });
+ const config = normalizeConfigResponse({
+ oauth,
+ 'quota-exceeded': {
+ 'switch-project': true,
+ 'switch-preview-model': true,
+ 'antigravity-credits': !enabled,
+ },
+ });
+ expect(config.quotaExceeded).toEqual({ antigravityCredits: enabled });
+ expect(config.quotaExceeded).not.toHaveProperty('switchProject');
+ expect(config.quotaExceeded).not.toHaveProperty('switchPreviewModel');
+ }
expect(
- normalizeConfigResponse({
- oauth: { providers: { antigravity: { 'antigravity-credits': true } } },
- }).quotaExceeded?.antigravityCredits
- ).toBe(true);
+ normalizeConfigResponse({ 'quota-exceeded': { 'antigravity-credits': true } }).quotaExceeded
+ ).toEqual({ antigravityCredits: false });
});
test('does not interpret legacy root settings as v8 values', () => {
diff --git a/tests/configOAuthBehaviorUi.test.ts b/tests/configOAuthBehaviorUi.test.ts
index 2b84e4b1..6ebdf722 100644
--- a/tests/configOAuthBehaviorUi.test.ts
+++ b/tests/configOAuthBehaviorUi.test.ts
@@ -28,8 +28,8 @@ const server = {
};
describe('OAuth behavior configuration UI', () => {
- test('keeps seven canonical tabs and places every addition in its designated section', () => {
- expect(CONFIG_SECTION_IDS).toHaveLength(7);
+ test('keeps six canonical tabs and places every addition in its designated section', () => {
+ expect(CONFIG_SECTION_IDS).toHaveLength(6);
const network = render(createElement(SectionNetwork, props));
const advanced = render(createElement(SectionAdvanced, props));
const additions = CONFIG_FIELD_SEARCH_INDEX.filter((entry) =>
diff --git a/tests/configServerUi.test.ts b/tests/configServerUi.test.ts
index e792ee6c..dd5bfe61 100644
--- a/tests/configServerUi.test.ts
+++ b/tests/configServerUi.test.ts
@@ -32,7 +32,7 @@ const populated = {
describe('server configuration UI', () => {
test('keeps nine searchable anchors in connectivity without adding a tab', () => {
- expect(CONFIG_SECTION_IDS).toHaveLength(7);
+ expect(CONFIG_SECTION_IDS).toHaveLength(6);
expect(extras).toHaveLength(9);
const markup = render(createElement(SectionConnectivity, props));
for (const entry of extras) {
diff --git a/tests/configSettingLayout.test.ts b/tests/configSettingLayout.test.ts
index 2030a690..632a9a4d 100644
--- a/tests/configSettingLayout.test.ts
+++ b/tests/configSettingLayout.test.ts
@@ -25,7 +25,6 @@ const sections = {
.SectionConnectivity,
network: (await import('@/features/config/components/sections/SectionNetwork')).SectionNetwork,
logging: (await import('@/features/config/components/sections/SectionLogging')).SectionLogging,
- quota: (await import('@/features/config/components/sections/SectionQuota')).SectionQuota,
streaming: (await import('@/features/config/components/sections/SectionStreaming'))
.SectionStreaming,
advanced: (await import('@/features/config/components/sections/SectionAdvanced')).SectionAdvanced,
diff --git a/tests/configUiState.test.ts b/tests/configUiState.test.ts
index 1b3d3a2c..e0cbfb5e 100644
--- a/tests/configUiState.test.ts
+++ b/tests/configUiState.test.ts
@@ -1,4 +1,16 @@
import { describe, expect, test } from 'bun:test';
+import {
+ COMMON_FIELD_IDS,
+ CONFIG_SECTION_IDS,
+ CONFIG_TAB_IDS,
+ FIELD_VALUE_KEYS,
+ SECTION_INDEX_LABELS,
+} from '@/features/config/constants';
+import {
+ CONFIG_FIELD_SEARCH_INDEX,
+ findConfigFieldById,
+ searchConfigFields,
+} from '@/features/config/searchIndex';
import {
buildHeaderMeta,
countSectionErrors,
@@ -94,7 +106,7 @@ describe('countSectionErrors', () => {
expect(counts.network).toBe(1);
expect(counts.streaming).toBe(1);
expect(counts.logging).toBe(0);
- expect(counts.quota).toBe(0);
+ expect(counts).not.toHaveProperty('quota');
expect(counts.advanced).toBe(0);
expect(counts.payload).toBe(0);
// port 由常用 tab 渲染,同一错误在两个 tab 都要可见
@@ -144,9 +156,10 @@ describe('resolveDirtyTabs', () => {
expect(tabs.has('connectivity')).toBe(true);
expect(tabs.size).toBe(2);
- const quotaTabs = resolveDirtyTabs(new Set(['quotaSwitchProject']));
- expect(quotaTabs.has('common')).toBe(true);
- expect(quotaTabs.has('quota')).toBe(true);
+ expect([...resolveDirtyTabs(new Set(['quotaAntigravityCredits']))]).toEqual(['advanced']);
+ expect(resolveDirtyTabs(new Set(['quotaSwitchProject', 'quotaSwitchPreviewModel'])).size).toBe(
+ 0
+ );
});
test('unknown keys are ignored instead of crashing', () => {
@@ -155,6 +168,72 @@ describe('resolveDirtyTabs', () => {
});
});
+describe('Antigravity credits UI and search', () => {
+ test('removes the quota tab and keeps section numbering contiguous', () => {
+ expect(CONFIG_TAB_IDS).not.toContain('quota');
+ expect(CONFIG_SECTION_IDS.map((id) => SECTION_INDEX_LABELS[id])).toEqual([
+ '01',
+ '02',
+ '03',
+ '04',
+ '05',
+ '06',
+ ]);
+ expect(CONFIG_FIELD_SEARCH_INDEX.some((entry) => String(entry.sectionId) === 'quota')).toBe(
+ false
+ );
+ for (const field of ['quotaSwitchProject', 'quotaSwitchPreviewModel']) {
+ expect(findConfigFieldById(field)).toBeUndefined();
+ expect(FIELD_VALUE_KEYS).not.toHaveProperty(field);
+ expect(COMMON_FIELD_IDS).not.toContain(field);
+ }
+ for (const query of ['switch-project', 'switch-preview-model', 'quota-exceeded']) {
+ expect(searchConfigFields(query, (key) => key)).toEqual([]);
+ }
+ });
+
+ test('credits search targets Advanced in every locale', async () => {
+ const entry = findConfigFieldById('quotaAntigravityCredits');
+ expect(entry?.sectionId).toBe('advanced');
+ expect(entry?.labelKey).toBe('config_management.visual.sections.advanced.antigravity_credits');
+ for (const locale of LANGUAGE_ORDER) {
+ const json = await Bun.file(`src/i18n/locales/${locale}.json`).json();
+ const sections = json.config_management.visual.sections;
+ expect(sections).not.toHaveProperty('quota');
+ expect(typeof sections.advanced.antigravity_credits).toBe('string');
+ const translate = (key: string): string =>
+ key === entry?.labelKey ? sections.advanced.antigravity_credits : key;
+ for (const query of [sections.advanced.antigravity_credits, 'antigravity-credits']) {
+ expect(searchConfigFields(query, translate)).toContainEqual(entry);
+ }
+ }
+ });
+
+ test('renders credits inside the Advanced Antigravity group, not common', async () => {
+ const root = 'src/features/config';
+ const advanced = await Bun.file(`${root}/components/sections/SectionAdvanced.tsx`).text();
+ const group = advanced.slice(
+ advanced.indexOf("label={t('config_management.visual.sections.advanced.antigravity_title')}"),
+ advanced.indexOf("label={t('config_management.visual.sections.advanced.devin_title')}")
+ );
+ expect(group).toContain('fieldId="quotaAntigravityCredits"');
+ expect(group).toContain('checked={values.quotaAntigravityCredits}');
+ expect(group).toContain(
+ 'onChange={(quotaAntigravityCredits) => onChange({ quotaAntigravityCredits })}'
+ );
+ expect(group).toContain('config_management.visual.sections.advanced.antigravity_credits');
+ for (const file of [
+ 'components/sections/SectionCommon.tsx',
+ 'components/fields/sharedFields.tsx',
+ 'ConfigPage.tsx',
+ ]) {
+ const source = await Bun.file(`${root}/${file}`).text();
+ expect(source).not.toMatch(/QuotaSwitch|quotaSwitch|SectionQuota|case 'quota'/);
+ }
+ expect(await Bun.file(`${root}/components/sections/SectionQuota.tsx`).exists()).toBe(false);
+ });
+});
+
describe('buildHeaderMeta', () => {
const base = {
fieldCount: 58,
@@ -216,6 +295,7 @@ describe('localStorage readers', () => {
});
test('readSavedSection falls back to common on stale values', () => {
+ expect(readSavedSection('quota')).toBe('common');
expect(readSavedSection('payload')).toBe('payload');
expect(readSavedSection('common')).toBe('common');
expect(readSavedSection('server')).toBe('common'); // 历史分区 id 不再存在
diff --git a/tests/fixtures/visualConfig-v8.yaml b/tests/fixtures/visualConfig-v8.yaml
index 6107bc8f..4a2909c2 100644
--- a/tests/fixtures/visualConfig-v8.yaml
+++ b/tests/fixtures/visualConfig-v8.yaml
@@ -61,9 +61,6 @@ oauth:
sensitive-words: [fixture-word]
devin:
sensitive-words: [fixture-devin]
-quota-exceeded:
- switch-project: false
- switch-preview-model: false
multimedia:
disable-image-generation: false
gpt-image-2-base-model: fixture-base
diff --git a/tests/visualConfigDefaults.test.ts b/tests/visualConfigDefaults.test.ts
index 8f23f16e..60410ad0 100644
--- a/tests/visualConfigDefaults.test.ts
+++ b/tests/visualConfigDefaults.test.ts
@@ -2,36 +2,54 @@ import { describe, expect, test } from 'bun:test';
import { parse as parseYaml } from 'yaml';
import { runVisualConfig } from './helpers/visualConfig';
-// Backend contract: config_load.go defaults WebsocketAuth to true; QuotaExceeded
-// uses Go's false zero values. config.example.yaml values are not missing-key defaults.
+// Backend contract: WebsocketAuth defaults to true and Antigravity credits to false.
+// config.example.yaml values are not missing-key defaults.
describe('visual config boolean defaults', () => {
test('initial and missing-key values agree with backend defaults', () => {
for (const config of [runVisualConfig(), runVisualConfig('server:\n port: 8317\n')]) {
expect(config.visualValues.wsAuth).toBe(true);
- expect(config.visualValues.quotaSwitchProject).toBe(false);
- expect(config.visualValues.quotaSwitchPreviewModel).toBe(false);
+ expect(config.visualValues).not.toHaveProperty('quotaSwitchProject');
+ expect(config.visualValues).not.toHaveProperty('quotaSwitchPreviewModel');
+ expect(config.visualValues.quotaAntigravityCredits).toBe(false);
expect(config.visualDirty).toBe(false);
}
});
for (const enabled of [true, false]) {
test(`preserves explicit boolean values: ${enabled}`, () => {
- const yaml = `quota-exceeded:
- switch-project: ${enabled}
- switch-preview-model: ${enabled}
-oauth:
+ const yaml = `oauth:
providers:
aistudio:
ws-auth: ${enabled}
+ antigravity:
+ antigravity-credits: ${enabled}
`;
const config = runVisualConfig(yaml);
expect(config.visualValues.wsAuth).toBe(enabled);
- expect(config.visualValues.quotaSwitchProject).toBe(enabled);
- expect(config.visualValues.quotaSwitchPreviewModel).toBe(enabled);
+ expect(config.visualValues.quotaAntigravityCredits).toBe(enabled);
expect(parseYaml(config.applyVisualChangesToYaml(yaml))).toEqual(parseYaml(yaml));
});
}
+ test('does not expose deprecated quota switches from existing YAML', () => {
+ const yaml = 'quota-exceeded: {switch-project: true, switch-preview-model: true}\n';
+ const config = runVisualConfig(yaml, [{ quotaAntigravityCredits: true }]);
+ expect(config.visualValues).not.toHaveProperty('quotaSwitchProject');
+ expect(config.visualValues).not.toHaveProperty('quotaSwitchPreviewModel');
+ expect(parseYaml(config.applyVisualChangesToYaml(yaml))).toEqual({
+ ...parseYaml(yaml),
+ oauth: { providers: { antigravity: { 'antigravity-credits': true } } },
+ });
+ });
+
+ test('writes credits when the OAuth provider map is null', () => {
+ const yaml = 'oauth: {providers: {antigravity: null}}\n';
+ const config = runVisualConfig(yaml, [{ quotaAntigravityCredits: true }]);
+ expect(parseYaml(config.applyVisualChangesToYaml(yaml))).toEqual({
+ oauth: { providers: { antigravity: { 'antigravity-credits': true } } },
+ });
+ });
+
test('writes explicit false when disabling omitted ws-auth', () => {
const yaml = 'server:\n port: 8317\n';
const config = runVisualConfig(yaml, [{ wsAuth: false }]);
@@ -42,43 +60,36 @@ oauth:
});
});
- test('writes enabled quota options when their block was omitted', () => {
+ test('writes enabled credits without creating quota-exceeded', () => {
const yaml = 'server:\n port: 8317\n';
- const config = runVisualConfig(yaml, [
- { quotaSwitchProject: true, quotaSwitchPreviewModel: true },
- ]);
+ const config = runVisualConfig(yaml, [{ quotaAntigravityCredits: true }]);
+ expect([...config.visualDirtyFields]).toEqual(['quotaAntigravityCredits']);
expect(parseYaml(config.applyVisualChangesToYaml(yaml))).toEqual({
server: { port: 8317 },
- 'quota-exceeded': { 'switch-project': true, 'switch-preview-model': true },
+ oauth: { providers: { antigravity: { 'antigravity-credits': true } } },
});
});
test('writes toggles in both directions without changing unrelated settings', () => {
for (const enabled of [true, false]) {
- const yaml = `quota-exceeded:
- switch-project: ${!enabled}
- switch-preview-model: ${!enabled}
+ const yaml = `server:
+ port: 8317
oauth:
providers:
aistudio:
ws-auth: ${!enabled}
antigravity:
- antigravity-credits: true
+ antigravity-credits: ${!enabled}
`;
- const config = runVisualConfig(yaml, [
- { wsAuth: enabled, quotaSwitchProject: enabled, quotaSwitchPreviewModel: enabled },
- ]);
+ const config = runVisualConfig(yaml, [{ wsAuth: enabled, quotaAntigravityCredits: enabled }]);
expect(parseYaml(config.applyVisualChangesToYaml(yaml))).toEqual({
oauth: {
providers: {
aistudio: { 'ws-auth': enabled },
- antigravity: { 'antigravity-credits': true },
+ antigravity: { 'antigravity-credits': enabled },
},
},
- 'quota-exceeded': {
- 'switch-project': enabled,
- 'switch-preview-model': enabled,
- },
+ server: { port: 8317 },
});
}
});
@@ -86,8 +97,8 @@ oauth:
test('reverting toggles clears dirty state without inserting missing keys', () => {
const yaml = 'server:\n port: 8317\n';
const config = runVisualConfig(yaml, [
- { wsAuth: false, quotaSwitchProject: true, quotaSwitchPreviewModel: true },
- { wsAuth: true, quotaSwitchProject: false, quotaSwitchPreviewModel: false },
+ { wsAuth: false, quotaAntigravityCredits: true },
+ { wsAuth: true, quotaAntigravityCredits: false },
]);
expect(config.visualDirty).toBe(false);
expect(config.visualDirtyFields.size).toBe(0);
diff --git a/tests/visualConfigV8.test.ts b/tests/visualConfigV8.test.ts
index ec6a9607..b30f3400 100644
--- a/tests/visualConfigV8.test.ts
+++ b/tests/visualConfigV8.test.ts
@@ -29,6 +29,9 @@ describe('v8 visual config contract', () => {
codexHeaderUserAgent: 'fixture-oauth-agent',
streaming: { keepaliveSeconds: '0', bootstrapRetries: '0', nonstreamKeepaliveInterval: '0' },
});
+ expect(config.visualValues).not.toHaveProperty('quotaSwitchProject');
+ expect(config.visualValues).not.toHaveProperty('quotaSwitchPreviewModel');
+ expect(parseYaml(fixture)).not.toHaveProperty('quota-exceeded');
expect(config.visualDirty).toBe(false);
expect(parseYaml(config.applyVisualChangesToYaml(fixture))).toEqual(parseYaml(fixture));
});
@@ -43,13 +46,12 @@ describe('v8 visual config contract', () => {
expect(runVisualConfig('api-keys:\n codex: []\n').visualValues.apiKeysText).toBe('');
});
- test('OAuth edits stay OAuth-only and preserve unmoved routing, quota, and plugin fields', () => {
+ test('OAuth edits stay OAuth-only and preserve unmoved routing and plugin fields', () => {
const config = runVisualConfig(fixture, [
{
codexHeaderUserAgent: 'new-oauth-agent',
antigravitySensitiveWords: [],
quotaAntigravityCredits: true,
- quotaSwitchProject: true,
},
]);
const original = parseYaml(fixture);
@@ -57,10 +59,7 @@ describe('v8 visual config contract', () => {
expect(output['api-keys']).toEqual(original['api-keys']);
expect(output.routing).toEqual(original.routing);
expect(output.plugins).toEqual(original.plugins);
- expect(output['quota-exceeded']).toEqual({
- 'switch-project': true,
- 'switch-preview-model': false,
- });
+ expect(output).not.toHaveProperty('quota-exceeded');
expect(output.oauth.providers.codex['header-defaults']['user-agent']).toBe('new-oauth-agent');
expect(output.oauth.providers.antigravity['sensitive-words']).toBeUndefined();
expect(output.oauth.providers.antigravity['antigravity-credits']).toBe(true);