feat(antigravity): 添加混合调度可选功能
- 后端:账户模型添加 IsMixedSchedulingEnabled() 方法,读取 extra.mixed_scheduling - 后端:gateway_service 和 gemini_messages_compat_service 支持混合调度逻辑 - 后端:分组创建支持指定 platform 参数 - 前端:账户创建/编辑弹窗添加混合调度开关(仅 antigravity 账户显示) - 前端:混合调度开关添加问号图标和 tooltip 说明 - 前端:GroupSelector 支持根据 mixedScheduling 属性过滤分组 - 前端:分组创建支持选择 platform - 测试:e2e 测试添加 ENDPOINT_PREFIX 环境变量支持混合/隔离模式测试 - 测试:删除过时的 Claude signature 测试用例
This commit is contained in:
@@ -1025,8 +1025,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mixed Scheduling (only for antigravity accounts) -->
|
||||
<div v-if="form.platform === 'antigravity'" class="flex items-center gap-2">
|
||||
<label class="flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="mixedScheduling"
|
||||
class="h-4 w-4 rounded border-gray-300 text-primary-500 focus:ring-primary-500 dark:border-dark-500"
|
||||
/>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.accounts.mixedScheduling') }}
|
||||
</span>
|
||||
</label>
|
||||
<div class="group relative">
|
||||
<span
|
||||
class="inline-flex h-4 w-4 cursor-help items-center justify-center rounded-full bg-gray-200 text-xs text-gray-500 hover:bg-gray-300 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500"
|
||||
>
|
||||
?
|
||||
</span>
|
||||
<!-- Tooltip(向下显示避免被弹窗裁剪) -->
|
||||
<div
|
||||
class="pointer-events-none absolute left-0 top-full z-[100] mt-1.5 w-72 rounded bg-gray-900 px-3 py-2 text-xs text-white opacity-0 transition-opacity group-hover:opacity-100 dark:bg-gray-700"
|
||||
>
|
||||
{{ t('admin.accounts.mixedSchedulingTooltip') }}
|
||||
<div
|
||||
class="absolute bottom-full left-3 border-4 border-transparent border-b-gray-900 dark:border-b-gray-700"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Group Selection -->
|
||||
<GroupSelector v-model="form.group_ids" :groups="groups" :platform="form.platform" />
|
||||
<GroupSelector
|
||||
v-model="form.group_ids"
|
||||
:groups="groups"
|
||||
:platform="form.platform"
|
||||
:mixed-scheduling="mixedScheduling"
|
||||
/>
|
||||
|
||||
</form>
|
||||
|
||||
@@ -1244,6 +1279,7 @@ const customErrorCodesEnabled = ref(false)
|
||||
const selectedErrorCodes = ref<number[]>([])
|
||||
const customErrorCodeInput = ref<number | null>(null)
|
||||
const interceptWarmupRequests = ref(false)
|
||||
const mixedScheduling = ref(false) // For antigravity accounts: enable mixed scheduling
|
||||
const geminiOAuthType = ref<'code_assist' | 'ai_studio'>('code_assist')
|
||||
const geminiAIStudioOAuthEnabled = ref(false)
|
||||
|
||||
@@ -1730,7 +1766,7 @@ const createAccountAndFinish = async (
|
||||
platform: AccountPlatform,
|
||||
type: AccountType,
|
||||
credentials: Record<string, unknown>,
|
||||
extra?: Record<string, string>
|
||||
extra?: Record<string, unknown>
|
||||
) => {
|
||||
await adminAPI.accounts.create({
|
||||
name: form.name,
|
||||
@@ -1834,7 +1870,8 @@ const handleAntigravityExchange = async (authCode: string) => {
|
||||
if (!tokenInfo) return
|
||||
|
||||
const credentials = antigravityOAuth.buildCredentials(tokenInfo)
|
||||
await createAccountAndFinish('antigravity', 'oauth', credentials)
|
||||
const extra = mixedScheduling.value ? { mixed_scheduling: true } : undefined
|
||||
await createAccountAndFinish('antigravity', 'oauth', credentials, extra)
|
||||
} catch (error: any) {
|
||||
antigravityOAuth.error.value = error.response?.data?.detail || t('admin.accounts.oauth.authFailed')
|
||||
appStore.showError(antigravityOAuth.error.value)
|
||||
|
||||
@@ -466,8 +466,44 @@
|
||||
<Select v-model="form.status" :options="statusOptions" />
|
||||
</div>
|
||||
|
||||
<!-- Mixed Scheduling (only for antigravity accounts, read-only in edit mode) -->
|
||||
<div v-if="account?.platform === 'antigravity'" class="flex items-center gap-2">
|
||||
<label class="flex cursor-not-allowed items-center gap-2 opacity-60">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="mixedScheduling"
|
||||
disabled
|
||||
class="h-4 w-4 cursor-not-allowed rounded border-gray-300 text-primary-500 focus:ring-primary-500 dark:border-dark-500"
|
||||
/>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.accounts.mixedScheduling') }}
|
||||
</span>
|
||||
</label>
|
||||
<div class="group relative">
|
||||
<span
|
||||
class="inline-flex h-4 w-4 cursor-help items-center justify-center rounded-full bg-gray-200 text-xs text-gray-500 hover:bg-gray-300 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500"
|
||||
>
|
||||
?
|
||||
</span>
|
||||
<!-- Tooltip(向下显示避免被弹窗裁剪) -->
|
||||
<div
|
||||
class="pointer-events-none absolute left-0 top-full z-[100] mt-1.5 w-72 rounded bg-gray-900 px-3 py-2 text-xs text-white opacity-0 transition-opacity group-hover:opacity-100 dark:bg-gray-700"
|
||||
>
|
||||
{{ t('admin.accounts.mixedSchedulingTooltip') }}
|
||||
<div
|
||||
class="absolute bottom-full left-3 border-4 border-transparent border-b-gray-900 dark:border-b-gray-700"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Group Selection -->
|
||||
<GroupSelector v-model="form.group_ids" :groups="groups" :platform="account?.platform" />
|
||||
<GroupSelector
|
||||
v-model="form.group_ids"
|
||||
:groups="groups"
|
||||
:platform="account?.platform"
|
||||
:mixed-scheduling="mixedScheduling"
|
||||
/>
|
||||
|
||||
</form>
|
||||
|
||||
@@ -553,6 +589,7 @@ const customErrorCodesEnabled = ref(false)
|
||||
const selectedErrorCodes = ref<number[]>([])
|
||||
const customErrorCodeInput = ref<number | null>(null)
|
||||
const interceptWarmupRequests = ref(false)
|
||||
const mixedScheduling = ref(false) // For antigravity accounts: enable mixed scheduling
|
||||
|
||||
// Common models for whitelist - Anthropic
|
||||
const anthropicModels = [
|
||||
@@ -764,6 +801,10 @@ watch(
|
||||
const credentials = newAccount.credentials as Record<string, unknown> | undefined
|
||||
interceptWarmupRequests.value = credentials?.intercept_warmup_requests === true
|
||||
|
||||
// Load mixed scheduling setting (only for antigravity accounts)
|
||||
const extra = newAccount.extra as Record<string, unknown> | undefined
|
||||
mixedScheduling.value = extra?.mixed_scheduling === true
|
||||
|
||||
// Initialize API Key fields for apikey type
|
||||
if (newAccount.type === 'apikey' && newAccount.credentials) {
|
||||
const credentials = newAccount.credentials as Record<string, unknown>
|
||||
@@ -969,6 +1010,18 @@ const handleSubmit = async () => {
|
||||
updatePayload.credentials = newCredentials
|
||||
}
|
||||
|
||||
// For antigravity accounts, handle mixed_scheduling in extra
|
||||
if (props.account.platform === 'antigravity') {
|
||||
const currentExtra = (props.account.extra as Record<string, unknown>) || {}
|
||||
const newExtra: Record<string, unknown> = { ...currentExtra }
|
||||
if (mixedScheduling.value) {
|
||||
newExtra.mixed_scheduling = true
|
||||
} else {
|
||||
delete newExtra.mixed_scheduling
|
||||
}
|
||||
updatePayload.extra = newExtra
|
||||
}
|
||||
|
||||
await adminAPI.accounts.update(props.account.id, updatePayload)
|
||||
appStore.showSuccess(t('admin.accounts.accountUpdated'))
|
||||
emit('updated')
|
||||
|
||||
@@ -50,6 +50,7 @@ interface Props {
|
||||
modelValue: number[]
|
||||
groups: Group[]
|
||||
platform?: GroupPlatform // Optional platform filter
|
||||
mixedScheduling?: boolean // For antigravity accounts: allow anthropic/gemini groups
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -62,10 +63,13 @@ const filteredGroups = computed(() => {
|
||||
if (!props.platform) {
|
||||
return props.groups
|
||||
}
|
||||
// antigravity 账户可选择 anthropic 和 gemini 平台的分组
|
||||
if (props.platform === 'antigravity') {
|
||||
return props.groups.filter((g) => g.platform === 'anthropic' || g.platform === 'gemini')
|
||||
// antigravity 账户启用混合调度后,可选择 anthropic/gemini 分组
|
||||
if (props.platform === 'antigravity' && props.mixedScheduling) {
|
||||
return props.groups.filter(
|
||||
(g) => g.platform === 'antigravity' || g.platform === 'anthropic' || g.platform === 'gemini'
|
||||
)
|
||||
}
|
||||
// 默认:只能选择同 platform 的分组
|
||||
return props.groups.filter((g) => g.platform === props.platform)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user