From 3fcefe6c324286bcebc5747cbcdb5b4c6993fbf4 Mon Sep 17 00:00:00 2001 From: Rose Ding Date: Wed, 11 Mar 2026 17:34:44 +0800 Subject: [PATCH] feat: prioritize new gemini image models in frontend --- .../components/account/AccountTestModal.vue | 25 +++++++++++++------ .../account/BulkEditAccountModal.vue | 4 +-- .../__tests__/BulkEditAccountModal.spec.ts | 4 +++ .../admin/account/AccountTestModal.vue | 25 +++++++++++++------ .../__tests__/AccountTestModal.spec.ts | 6 +++-- .../__tests__/useModelWhitelist.spec.ts | 9 +++++++ frontend/src/composables/useModelWhitelist.ts | 10 ++++---- 7 files changed, 58 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/account/AccountTestModal.vue b/frontend/src/components/account/AccountTestModal.vue index 04ab032f..e731a7b1 100644 --- a/frontend/src/components/account/AccountTestModal.vue +++ b/frontend/src/components/account/AccountTestModal.vue @@ -260,6 +260,7 @@ const loadingModels = ref(false) let eventSource: EventSource | null = null const isSoraAccount = computed(() => props.account?.platform === 'sora') const generatedImages = ref([]) +const prioritizedGeminiModels = ['gemini-3.1-flash-image', 'gemini-2.5-flash-image', 'gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-3-flash-preview', 'gemini-3-pro-preview', 'gemini-2.0-flash'] const supportsGeminiImageTest = computed(() => { if (isSoraAccount.value) return false const modelID = selectedModelId.value.toLowerCase() @@ -268,6 +269,17 @@ const supportsGeminiImageTest = computed(() => { return props.account?.platform === 'gemini' || (props.account?.platform === 'antigravity' && props.account?.type === 'apikey') }) +const sortTestModels = (models: ClaudeModel[]) => { + const priorityMap = new Map(prioritizedGeminiModels.map((id, index) => [id, index])) + + return [...models].sort((a, b) => { + const aPriority = priorityMap.get(a.id) ?? Number.MAX_SAFE_INTEGER + const bPriority = priorityMap.get(b.id) ?? Number.MAX_SAFE_INTEGER + if (aPriority !== bPriority) return aPriority - bPriority + return 0 + }) +} + // Load available models when modal opens watch( () => props.show, @@ -300,17 +312,14 @@ const loadAvailableModels = async () => { loadingModels.value = true selectedModelId.value = '' // Reset selection before loading try { - availableModels.value = await adminAPI.accounts.getAvailableModels(props.account.id) + const models = await adminAPI.accounts.getAvailableModels(props.account.id) + availableModels.value = props.account.platform === 'gemini' || props.account.platform === 'antigravity' + ? sortTestModels(models) + : models // Default selection by platform if (availableModels.value.length > 0) { if (props.account.platform === 'gemini') { - const preferred = - availableModels.value.find((m) => m.id === 'gemini-2.0-flash') || - availableModels.value.find((m) => m.id === 'gemini-2.5-flash') || - availableModels.value.find((m) => m.id === 'gemini-2.5-pro') || - availableModels.value.find((m) => m.id === 'gemini-3-flash-preview') || - availableModels.value.find((m) => m.id === 'gemini-3-pro-preview') - selectedModelId.value = preferred?.id || availableModels.value[0].id + selectedModelId.value = availableModels.value[0].id } else { // Try to select Sonnet as default, otherwise use first model const sonnetModel = availableModels.value.find((m) => m.id.includes('sonnet')) diff --git a/frontend/src/components/account/BulkEditAccountModal.vue b/frontend/src/components/account/BulkEditAccountModal.vue index 17caef8b..c6e08684 100644 --- a/frontend/src/components/account/BulkEditAccountModal.vue +++ b/frontend/src/components/account/BulkEditAccountModal.vue @@ -959,11 +959,11 @@ const allModels = [ { value: 'gpt-5.1-2025-11-13', label: 'GPT-5.1' }, { value: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini' }, { value: 'gpt-5-2025-08-07', label: 'GPT-5' }, + { value: 'gemini-3.1-flash-image', label: 'Gemini 3.1 Flash Image' }, + { value: 'gemini-2.5-flash-image', label: 'Gemini 2.5 Flash Image' }, { value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash' }, { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' }, - { value: 'gemini-2.5-flash-image', label: 'Gemini 2.5 Flash Image' }, { value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' }, - { value: 'gemini-3.1-flash-image', label: 'Gemini 3.1 Flash Image' }, { value: 'gemini-3-pro-image', label: 'Gemini 3 Pro Image (Legacy)' }, { value: 'gemini-3-flash-preview', label: 'Gemini 3 Flash Preview' }, { value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' } diff --git a/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts b/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts index 28ac61ec..ba3422ca 100644 --- a/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts +++ b/frontend/src/components/account/__tests__/BulkEditAccountModal.spec.ts @@ -18,6 +18,10 @@ vi.mock('@/api/admin', () => ({ } })) +vi.mock('@/api/admin/accounts', () => ({ + getAntigravityDefaultModelMapping: vi.fn() +})) + vi.mock('vue-i18n', async () => { const actual = await vi.importActual('vue-i18n') return { diff --git a/frontend/src/components/admin/account/AccountTestModal.vue b/frontend/src/components/admin/account/AccountTestModal.vue index 04ab032f..e731a7b1 100644 --- a/frontend/src/components/admin/account/AccountTestModal.vue +++ b/frontend/src/components/admin/account/AccountTestModal.vue @@ -260,6 +260,7 @@ const loadingModels = ref(false) let eventSource: EventSource | null = null const isSoraAccount = computed(() => props.account?.platform === 'sora') const generatedImages = ref([]) +const prioritizedGeminiModels = ['gemini-3.1-flash-image', 'gemini-2.5-flash-image', 'gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-3-flash-preview', 'gemini-3-pro-preview', 'gemini-2.0-flash'] const supportsGeminiImageTest = computed(() => { if (isSoraAccount.value) return false const modelID = selectedModelId.value.toLowerCase() @@ -268,6 +269,17 @@ const supportsGeminiImageTest = computed(() => { return props.account?.platform === 'gemini' || (props.account?.platform === 'antigravity' && props.account?.type === 'apikey') }) +const sortTestModels = (models: ClaudeModel[]) => { + const priorityMap = new Map(prioritizedGeminiModels.map((id, index) => [id, index])) + + return [...models].sort((a, b) => { + const aPriority = priorityMap.get(a.id) ?? Number.MAX_SAFE_INTEGER + const bPriority = priorityMap.get(b.id) ?? Number.MAX_SAFE_INTEGER + if (aPriority !== bPriority) return aPriority - bPriority + return 0 + }) +} + // Load available models when modal opens watch( () => props.show, @@ -300,17 +312,14 @@ const loadAvailableModels = async () => { loadingModels.value = true selectedModelId.value = '' // Reset selection before loading try { - availableModels.value = await adminAPI.accounts.getAvailableModels(props.account.id) + const models = await adminAPI.accounts.getAvailableModels(props.account.id) + availableModels.value = props.account.platform === 'gemini' || props.account.platform === 'antigravity' + ? sortTestModels(models) + : models // Default selection by platform if (availableModels.value.length > 0) { if (props.account.platform === 'gemini') { - const preferred = - availableModels.value.find((m) => m.id === 'gemini-2.0-flash') || - availableModels.value.find((m) => m.id === 'gemini-2.5-flash') || - availableModels.value.find((m) => m.id === 'gemini-2.5-pro') || - availableModels.value.find((m) => m.id === 'gemini-3-flash-preview') || - availableModels.value.find((m) => m.id === 'gemini-3-pro-preview') - selectedModelId.value = preferred?.id || availableModels.value[0].id + selectedModelId.value = availableModels.value[0].id } else { // Try to select Sonnet as default, otherwise use first model const sonnetModel = availableModels.value.find((m) => m.id.includes('sonnet')) diff --git a/frontend/src/components/admin/account/__tests__/AccountTestModal.spec.ts b/frontend/src/components/admin/account/__tests__/AccountTestModal.spec.ts index e38746b0..429a905c 100644 --- a/frontend/src/components/admin/account/__tests__/AccountTestModal.spec.ts +++ b/frontend/src/components/admin/account/__tests__/AccountTestModal.spec.ts @@ -89,7 +89,9 @@ function mountModal() { describe('AccountTestModal', () => { beforeEach(() => { getAvailableModels.mockResolvedValue([ - { id: 'gemini-2.5-flash-image', display_name: 'Gemini 2.5 Flash Image' } + { id: 'gemini-2.0-flash', display_name: 'Gemini 2.0 Flash' }, + { id: 'gemini-2.5-flash-image', display_name: 'Gemini 2.5 Flash Image' }, + { id: 'gemini-3.1-flash-image', display_name: 'Gemini 3.1 Flash Image' } ]) copyToClipboard.mockReset() Object.defineProperty(globalThis, 'localStorage', { @@ -134,7 +136,7 @@ describe('AccountTestModal', () => { expect(global.fetch).toHaveBeenCalledTimes(1) const [, request] = (global.fetch as any).mock.calls[0] expect(JSON.parse(request.body)).toEqual({ - model_id: 'gemini-2.5-flash-image', + model_id: 'gemini-3.1-flash-image', prompt: 'draw a tiny orange cat astronaut' }) diff --git a/frontend/src/composables/__tests__/useModelWhitelist.spec.ts b/frontend/src/composables/__tests__/useModelWhitelist.spec.ts index 1af7b929..b4308a63 100644 --- a/frontend/src/composables/__tests__/useModelWhitelist.spec.ts +++ b/frontend/src/composables/__tests__/useModelWhitelist.spec.ts @@ -27,6 +27,15 @@ describe('useModelWhitelist', () => { expect(models).toContain('gemini-2.5-flash-image') expect(models).toContain('gemini-3.1-flash-image') + expect(models.indexOf('gemini-3.1-flash-image')).toBeLessThan(models.indexOf('gemini-2.0-flash')) + expect(models.indexOf('gemini-2.5-flash-image')).toBeLessThan(models.indexOf('gemini-2.5-flash')) + }) + + it('antigravity 模型列表会把新的 Gemini 图片模型排在前面', () => { + const models = getModelsByPlatform('antigravity') + + expect(models.indexOf('gemini-3.1-flash-image')).toBeLessThan(models.indexOf('gemini-2.5-flash')) + expect(models.indexOf('gemini-2.5-flash-image')).toBeLessThan(models.indexOf('gemini-2.5-flash-lite')) }) it('whitelist 模式会忽略通配符条目', () => { diff --git a/frontend/src/composables/useModelWhitelist.ts b/frontend/src/composables/useModelWhitelist.ts index 81ca458e..09a150cb 100644 --- a/frontend/src/composables/useModelWhitelist.ts +++ b/frontend/src/composables/useModelWhitelist.ts @@ -51,13 +51,13 @@ export const claudeModels = [ const geminiModels = [ // Keep in sync with backend curated Gemini lists. // This list is intentionally conservative (models commonly available across OAuth/API key). + 'gemini-3.1-flash-image', + 'gemini-2.5-flash-image', 'gemini-2.0-flash', 'gemini-2.5-flash', - 'gemini-2.5-flash-image', 'gemini-2.5-pro', 'gemini-3-flash-preview', - 'gemini-3-pro-preview', - 'gemini-3.1-flash-image' + 'gemini-3-pro-preview' ] // Sora @@ -87,8 +87,9 @@ const antigravityModels = [ 'claude-sonnet-4-5', 'claude-sonnet-4-5-thinking', // Gemini 2.5 系列 - 'gemini-2.5-flash', + 'gemini-3.1-flash-image', 'gemini-2.5-flash-image', + 'gemini-2.5-flash', 'gemini-2.5-flash-lite', 'gemini-2.5-flash-thinking', 'gemini-2.5-pro', @@ -99,7 +100,6 @@ const antigravityModels = [ // Gemini 3.1 系列 'gemini-3.1-pro-high', 'gemini-3.1-pro-low', - 'gemini-3.1-flash-image', 'gemini-3-pro-image', // 其他 'gpt-oss-120b-medium',