diff --git a/apps/aether-gateway/src/tests/control/admin/providers.rs b/apps/aether-gateway/src/tests/control/admin/providers.rs index e65167bd8..da687e1cf 100644 --- a/apps/aether-gateway/src/tests/control/admin/providers.rs +++ b/apps/aether-gateway/src/tests/control/admin/providers.rs @@ -780,6 +780,14 @@ async fn gateway_updates_admin_provider_locally_with_trusted_admin_principal() { let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed( vec![ sample_provider("provider-openai", "openai", 10) + .with_billing_fields( + Some("free_tier".to_string()), + None, + None, + Some(30), + None, + None, + ) .with_transport_fields( true, false, @@ -974,6 +982,7 @@ async fn gateway_updates_admin_provider_locally_with_trusted_admin_principal() { .iter() .find(|provider| provider.id == "provider-openai") .expect("provider should exist"); + assert_eq!(updated_provider.billing_type.as_deref(), Some("free_tier")); assert_eq!( updated_provider.request_timeout_secs, Some(aether_contracts::MAX_EXECUTION_REQUEST_TIMEOUT_SECS as f64) @@ -1106,6 +1115,7 @@ async fn gateway_creates_admin_provider_locally_with_trusted_admin_principal() { .find(|provider| provider.id == "provider-existing") .expect("existing provider should remain"); assert_eq!(created.provider_type, "codex"); + assert_eq!(created.billing_type.as_deref(), Some("pay_as_you_go")); assert_eq!(created.provider_priority, 0); assert_eq!(existing.provider_priority, 1); assert_eq!(created.website.as_deref(), Some("https://codex.example")); diff --git a/frontend/src/features/providers/components/ProviderFormDialog.vue b/frontend/src/features/providers/components/ProviderFormDialog.vue index b1f90f2a5..791628cd5 100644 --- a/frontend/src/features/providers/components/ProviderFormDialog.vue +++ b/frontend/src/features/providers/components/ProviderFormDialog.vue @@ -123,39 +123,11 @@ - +
-
-

- {{ legacyT('计费与限流') }} -

-

- {{ legacyT('请求配置') }} -

-
-
-
- - -
-
+

+ {{ legacyT('请求配置') }} +

@@ -229,49 +201,6 @@ />
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
@@ -426,7 +355,6 @@ import { } from '@/api/endpoints' import { parseApiError } from '@/utils/errorParser' import { parseNumberInput } from '@/utils/form' -import { dateTimeLocalToRfc3339, formatDateTimeLocalInput } from '@/utils/date' const props = defineProps<{ modelValue: boolean @@ -468,12 +396,6 @@ const form = ref({ provider_type: 'custom' as ProviderType, description: '', website: '', - // 计费配置 - billing_type: 'pay_as_you_go' as 'monthly_quota' | 'pay_as_you_go' | 'free_tier', - monthly_quota_usd: undefined as number | undefined, - quota_reset_day: 30, - quota_last_reset_at: '', // 周期开始时间 - quota_expires_at: '', provider_priority: 100, keep_priority_on_conversion: false, // 格式转换时是否保持优先级 // 状态配置 @@ -504,11 +426,6 @@ function resetForm() { provider_type: 'custom', description: '', website: '', - billing_type: 'pay_as_you_go', - monthly_quota_usd: undefined, - quota_reset_day: 30, - quota_last_reset_at: '', - quota_expires_at: '', provider_priority: defaultPriority.value, keep_priority_on_conversion: false, is_active: true, @@ -542,11 +459,6 @@ function loadProviderData() { provider_type: props.provider.provider_type || 'custom', description: props.provider.description || '', website: props.provider.website || '', - billing_type: (props.provider.billing_type as 'monthly_quota' | 'pay_as_you_go' | 'free_tier') || 'pay_as_you_go', - monthly_quota_usd: props.provider.monthly_quota_usd || undefined, - quota_reset_day: props.provider.quota_reset_day || 30, - quota_last_reset_at: formatDateTimeLocalInput(props.provider.quota_last_reset_at), - quota_expires_at: formatDateTimeLocalInput(props.provider.quota_expires_at), provider_priority: props.provider.provider_priority || 999, keep_priority_on_conversion: props.provider.keep_priority_on_conversion ?? false, is_active: props.provider.is_active, @@ -595,23 +507,6 @@ watch(() => form.value.provider_type, () => { // 提交表单 const handleSubmit = async () => { - // 月卡类型必须设置周期开始时间 - if (form.value.billing_type === 'monthly_quota' && !form.value.quota_last_reset_at) { - showError(legacyT('月卡类型必须设置周期开始时间'), legacyT('验证失败')) - return - } - - const quotaLastResetAt = dateTimeLocalToRfc3339(form.value.quota_last_reset_at) - if (form.value.billing_type === 'monthly_quota' && !quotaLastResetAt) { - showError(legacyT('周期开始时间必须是合法时间'), legacyT('验证失败')) - return - } - const quotaExpiresAt = dateTimeLocalToRfc3339(form.value.quota_expires_at) - if (form.value.quota_expires_at && !quotaExpiresAt) { - showError(legacyT('过期时间必须是合法时间'), legacyT('验证失败')) - return - } - loading.value = true try { const currentPoolAdvanced = normalizePoolAdvancedConfig(props.provider?.pool_advanced) @@ -620,11 +515,6 @@ const handleSubmit = async () => { provider_type: form.value.provider_type, description: form.value.description || undefined, website: form.value.website || undefined, - billing_type: form.value.billing_type, - monthly_quota_usd: form.value.monthly_quota_usd, - quota_reset_day: form.value.quota_reset_day, - quota_last_reset_at: quotaLastResetAt, - quota_expires_at: quotaExpiresAt, keep_priority_on_conversion: form.value.keep_priority_on_conversion, responses_websocket_enabled: form.value.responses_websocket_enabled, is_active: form.value.is_active, diff --git a/frontend/src/features/providers/components/__tests__/ProviderFormDialog.transfer-limits.spec.ts b/frontend/src/features/providers/components/__tests__/ProviderFormDialog.transfer-limits.spec.ts index 3144dce81..f0d8f7d69 100644 --- a/frontend/src/features/providers/components/__tests__/ProviderFormDialog.transfer-limits.spec.ts +++ b/frontend/src/features/providers/components/__tests__/ProviderFormDialog.transfer-limits.spec.ts @@ -137,6 +137,36 @@ function clickButton(text: string) { button.click() } +const billingFieldNames = [ + 'billing_type', + 'monthly_quota_usd', + 'quota_reset_day', + 'quota_last_reset_at', + 'quota_expires_at', +] as const + +function expectBillingConfigurationHidden() { + for (const text of [ + '计费类型', + '月卡额度', + '按量付费', + '免费套餐', + '周期额度 (USD)', + '重置周期 (天)', + '周期开始时间', + '过期时间', + ]) { + expect(document.body.textContent).not.toContain(text) + } +} + +function expectBillingFieldsOmitted(payload: unknown) { + expect(payload).toEqual(expect.any(Object)) + for (const field of billingFieldNames) { + expect(payload).not.toHaveProperty(field) + } +} + beforeEach(() => { endpointMocks.createProvider.mockReset() endpointMocks.createProvider.mockResolvedValue({ id: 'provider-new', name: 'New Provider' }) @@ -226,6 +256,41 @@ describe('ProviderFormDialog transfer limits', () => { }) }) +describe('ProviderFormDialog billing configuration', () => { + it('hides billing configuration and omits billing fields when creating', async () => { + mountDialog(null) + await settle() + + expectBillingConfigurationHidden() + + await setInput('#name', 'New Provider') + clickButton('创建') + await settle() + + expect(endpointMocks.createProvider).toHaveBeenCalledTimes(1) + expectBillingFieldsOmitted(endpointMocks.createProvider.mock.calls[0]?.[0]) + }) + + it('hides existing monthly quota configuration and preserves it when editing', async () => { + mountDialog(makeProvider({ + billing_type: 'monthly_quota', + monthly_quota_usd: 200, + quota_reset_day: 30, + quota_last_reset_at: '2026-08-01T00:00:00Z', + quota_expires_at: '2026-09-01T00:00:00Z', + })) + await settle() + + expectBillingConfigurationHidden() + + clickButton('保存') + await settle() + + expect(endpointMocks.updateProvider).toHaveBeenCalledTimes(1) + expectBillingFieldsOmitted(endpointMocks.updateProvider.mock.calls[0]?.[1]) + }) +}) + describe('ProviderFormDialog provider types', () => { it('creates an experimental Claude Code provider from the add dialog', async () => { mountDialog(null) diff --git a/frontend/src/views/public/guide/ConceptsGuide.vue b/frontend/src/views/public/guide/ConceptsGuide.vue index 0f7ecdc99..dbdfc7700 100644 --- a/frontend/src/views/public/guide/ConceptsGuide.vue +++ b/frontend/src/views/public/guide/ConceptsGuide.vue @@ -68,14 +68,6 @@ import { BookOpen } from 'lucide-vue-next'