mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-10-16 14:57:05 +08:00
refactor: remove quota section and migrate antigravity credits to advanced section
- Deleted the SectionQuota component and its associated tests, fields, and constants. - Moved the quotaAntigravityCredits field to the SectionAdvanced component. - Updated related constants and translations to reflect the removal of the quota section. - Adjusted tests to ensure consistency with the new configuration structure. - Ensured that deprecated quota switches are no longer present in the visual config and YAML parsing logic.
This commit is contained in:
@@ -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 <SectionNetwork {...sectionProps} />;
|
||||
case 'logging':
|
||||
return <SectionLogging {...sectionProps} />;
|
||||
case 'quota':
|
||||
return <SectionQuota {...sectionProps} />;
|
||||
case 'streaming':
|
||||
return <SectionStreaming {...sectionProps} />;
|
||||
case 'advanced':
|
||||
|
||||
@@ -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 (
|
||||
<ToggleSetting
|
||||
fieldId="quotaSwitchProject"
|
||||
wide={wide}
|
||||
label={t('config_management.visual.sections.quota.switch_project')}
|
||||
description={t('config_management.visual.sections.quota.switch_project_desc')}
|
||||
checked={values.quotaSwitchProject}
|
||||
disabled={disabled}
|
||||
onChange={(quotaSwitchProject) => onChange({ quotaSwitchProject })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function QuotaSwitchPreviewModelToggle({
|
||||
values,
|
||||
disabled,
|
||||
onChange,
|
||||
wide,
|
||||
}: SharedFieldProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<ToggleSetting
|
||||
fieldId="quotaSwitchPreviewModel"
|
||||
wide={wide}
|
||||
label={t('config_management.visual.sections.quota.switch_preview_model')}
|
||||
description={t('config_management.visual.sections.quota.switch_preview_model_desc')}
|
||||
checked={values.quotaSwitchPreviewModel}
|
||||
disabled={disabled}
|
||||
onChange={(quotaSwitchPreviewModel) => onChange({ quotaSwitchPreviewModel })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
<FieldStack>
|
||||
<SettingList>
|
||||
<ToggleSetting
|
||||
fieldId="quotaAntigravityCredits"
|
||||
label={t('config_management.visual.sections.advanced.antigravity_credits')}
|
||||
checked={values.quotaAntigravityCredits}
|
||||
disabled={disabled}
|
||||
onChange={(quotaAntigravityCredits) => onChange({ quotaAntigravityCredits })}
|
||||
/>
|
||||
</SettingList>
|
||||
<SettingList
|
||||
title={t('config_management.visual.sections.system.antigravity_sensitive_words')}
|
||||
description={t(
|
||||
|
||||
@@ -11,14 +11,12 @@ import {
|
||||
LoggingToFileToggle,
|
||||
PortField,
|
||||
ProxyUrlField,
|
||||
QuotaSwitchPreviewModelToggle,
|
||||
QuotaSwitchProjectToggle,
|
||||
} from '../fields/sharedFields';
|
||||
|
||||
const Icon = CONFIG_TAB_ICONS.common;
|
||||
|
||||
/**
|
||||
* 「常用」tab:原简单模式的 8 个高频字段,别名视图(不占分区序号)。
|
||||
* 「常用」tab:原简单模式的 6 个高频字段,别名视图(不占分区序号)。
|
||||
* 渲染源与正典分区共享(sharedFields),数据同为 useVisualConfig 一份状态。
|
||||
*/
|
||||
export function SectionCommon({
|
||||
@@ -50,8 +48,6 @@ export function SectionCommon({
|
||||
<SettingList>
|
||||
<DebugToggle {...fieldProps} />
|
||||
<LoggingToFileToggle {...fieldProps} />
|
||||
<QuotaSwitchProjectToggle {...fieldProps} />
|
||||
<QuotaSwitchPreviewModelToggle {...fieldProps} />
|
||||
</SettingList>
|
||||
</SectionCard>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ export type SectionPayloadProps = ConfigSectionProps & {
|
||||
hasPayloadValidationErrors: boolean;
|
||||
};
|
||||
|
||||
/** 07 Payload 配置:默认值 / 原始 JSON / 覆盖 / 过滤 五个规则组。 */
|
||||
/** 06 Payload 配置:默认值 / 原始 JSON / 覆盖 / 过滤 五个规则组。 */
|
||||
export function SectionPayload({
|
||||
values,
|
||||
disabled,
|
||||
|
||||
@@ -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 (
|
||||
<SectionCard
|
||||
indexLabel={SECTION_INDEX_LABELS.quota}
|
||||
icon={<Icon size={16} />}
|
||||
title={t('config_management.visual.sections.quota.title')}
|
||||
description={t('config_management.visual.sections.quota.description')}
|
||||
animateIn={animateIn}
|
||||
>
|
||||
<SettingList>
|
||||
<QuotaSwitchProjectToggle {...fieldProps} />
|
||||
<QuotaSwitchPreviewModelToggle {...fieldProps} />
|
||||
<ToggleSetting
|
||||
fieldId="quotaAntigravityCredits"
|
||||
label={t('config_management.visual.sections.quota.antigravity_credits')}
|
||||
checked={values.quotaAntigravityCredits}
|
||||
disabled={disabled}
|
||||
onChange={(quotaAntigravityCredits) => onChange({ quotaAntigravityCredits })}
|
||||
/>
|
||||
</SettingList>
|
||||
</SectionCard>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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<VisualSectionId, string> = {
|
||||
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<ConfigTabId, ComponentType<IconProps>> = {
|
||||
@@ -47,13 +44,12 @@ export const CONFIG_TAB_ICONS: Record<ConfigTabId, ComponentType<IconProps>> = {
|
||||
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<VisualSectionId, readonly VisualC
|
||||
'videoResultAuthCacheTTL',
|
||||
],
|
||||
logging: ['errorLogsMaxFiles', 'logsMaxTotalSizeMb', 'redisUsageQueueRetentionSeconds'],
|
||||
quota: [],
|
||||
streaming: [
|
||||
'streaming.keepaliveSeconds',
|
||||
'streaming.bootstrapRetries',
|
||||
@@ -180,15 +173,12 @@ export const FIELD_VALUE_KEYS: Record<string, readonly string[]> = {
|
||||
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'],
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 및 부트스트랩 재시도 설정",
|
||||
|
||||
@@ -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 и повторов запуска",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 重试设置",
|
||||
|
||||
@@ -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 重試設定",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
import type { GeminiKeyConfig, ProviderKeyConfig, OpenAIProviderConfig } from './provider';
|
||||
|
||||
export interface QuotaExceededConfig {
|
||||
switchProject?: boolean;
|
||||
switchPreviewModel?: boolean;
|
||||
antigravityCredits?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 不再存在
|
||||
|
||||
Vendored
-3
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user