mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-03 06:52:13 +08:00
feat: prioritize new gemini image models in frontend
This commit is contained in:
@@ -260,6 +260,7 @@ const loadingModels = ref(false)
|
|||||||
let eventSource: EventSource | null = null
|
let eventSource: EventSource | null = null
|
||||||
const isSoraAccount = computed(() => props.account?.platform === 'sora')
|
const isSoraAccount = computed(() => props.account?.platform === 'sora')
|
||||||
const generatedImages = ref<PreviewImage[]>([])
|
const generatedImages = ref<PreviewImage[]>([])
|
||||||
|
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(() => {
|
const supportsGeminiImageTest = computed(() => {
|
||||||
if (isSoraAccount.value) return false
|
if (isSoraAccount.value) return false
|
||||||
const modelID = selectedModelId.value.toLowerCase()
|
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')
|
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
|
// Load available models when modal opens
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
@@ -300,17 +312,14 @@ const loadAvailableModels = async () => {
|
|||||||
loadingModels.value = true
|
loadingModels.value = true
|
||||||
selectedModelId.value = '' // Reset selection before loading
|
selectedModelId.value = '' // Reset selection before loading
|
||||||
try {
|
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
|
// Default selection by platform
|
||||||
if (availableModels.value.length > 0) {
|
if (availableModels.value.length > 0) {
|
||||||
if (props.account.platform === 'gemini') {
|
if (props.account.platform === 'gemini') {
|
||||||
const preferred =
|
selectedModelId.value = availableModels.value[0].id
|
||||||
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
|
|
||||||
} else {
|
} else {
|
||||||
// Try to select Sonnet as default, otherwise use first model
|
// Try to select Sonnet as default, otherwise use first model
|
||||||
const sonnetModel = availableModels.value.find((m) => m.id.includes('sonnet'))
|
const sonnetModel = availableModels.value.find((m) => m.id.includes('sonnet'))
|
||||||
|
|||||||
@@ -959,11 +959,11 @@ const allModels = [
|
|||||||
{ value: 'gpt-5.1-2025-11-13', label: 'GPT-5.1' },
|
{ 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.1-codex-mini', label: 'GPT-5.1 Codex Mini' },
|
||||||
{ value: 'gpt-5-2025-08-07', label: 'GPT-5' },
|
{ 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.0-flash', label: 'Gemini 2.0 Flash' },
|
||||||
{ value: 'gemini-2.5-flash', label: 'Gemini 2.5 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-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-pro-image', label: 'Gemini 3 Pro Image (Legacy)' },
|
||||||
{ value: 'gemini-3-flash-preview', label: 'Gemini 3 Flash Preview' },
|
{ value: 'gemini-3-flash-preview', label: 'Gemini 3 Flash Preview' },
|
||||||
{ value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' }
|
{ value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' }
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ vi.mock('@/api/admin', () => ({
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/api/admin/accounts', () => ({
|
||||||
|
getAntigravityDefaultModelMapping: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
vi.mock('vue-i18n', async () => {
|
vi.mock('vue-i18n', async () => {
|
||||||
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
|
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -260,6 +260,7 @@ const loadingModels = ref(false)
|
|||||||
let eventSource: EventSource | null = null
|
let eventSource: EventSource | null = null
|
||||||
const isSoraAccount = computed(() => props.account?.platform === 'sora')
|
const isSoraAccount = computed(() => props.account?.platform === 'sora')
|
||||||
const generatedImages = ref<PreviewImage[]>([])
|
const generatedImages = ref<PreviewImage[]>([])
|
||||||
|
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(() => {
|
const supportsGeminiImageTest = computed(() => {
|
||||||
if (isSoraAccount.value) return false
|
if (isSoraAccount.value) return false
|
||||||
const modelID = selectedModelId.value.toLowerCase()
|
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')
|
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
|
// Load available models when modal opens
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
@@ -300,17 +312,14 @@ const loadAvailableModels = async () => {
|
|||||||
loadingModels.value = true
|
loadingModels.value = true
|
||||||
selectedModelId.value = '' // Reset selection before loading
|
selectedModelId.value = '' // Reset selection before loading
|
||||||
try {
|
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
|
// Default selection by platform
|
||||||
if (availableModels.value.length > 0) {
|
if (availableModels.value.length > 0) {
|
||||||
if (props.account.platform === 'gemini') {
|
if (props.account.platform === 'gemini') {
|
||||||
const preferred =
|
selectedModelId.value = availableModels.value[0].id
|
||||||
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
|
|
||||||
} else {
|
} else {
|
||||||
// Try to select Sonnet as default, otherwise use first model
|
// Try to select Sonnet as default, otherwise use first model
|
||||||
const sonnetModel = availableModels.value.find((m) => m.id.includes('sonnet'))
|
const sonnetModel = availableModels.value.find((m) => m.id.includes('sonnet'))
|
||||||
|
|||||||
@@ -89,7 +89,9 @@ function mountModal() {
|
|||||||
describe('AccountTestModal', () => {
|
describe('AccountTestModal', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
getAvailableModels.mockResolvedValue([
|
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()
|
copyToClipboard.mockReset()
|
||||||
Object.defineProperty(globalThis, 'localStorage', {
|
Object.defineProperty(globalThis, 'localStorage', {
|
||||||
@@ -134,7 +136,7 @@ describe('AccountTestModal', () => {
|
|||||||
expect(global.fetch).toHaveBeenCalledTimes(1)
|
expect(global.fetch).toHaveBeenCalledTimes(1)
|
||||||
const [, request] = (global.fetch as any).mock.calls[0]
|
const [, request] = (global.fetch as any).mock.calls[0]
|
||||||
expect(JSON.parse(request.body)).toEqual({
|
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'
|
prompt: 'draw a tiny orange cat astronaut'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ describe('useModelWhitelist', () => {
|
|||||||
|
|
||||||
expect(models).toContain('gemini-2.5-flash-image')
|
expect(models).toContain('gemini-2.5-flash-image')
|
||||||
expect(models).toContain('gemini-3.1-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 模式会忽略通配符条目', () => {
|
it('whitelist 模式会忽略通配符条目', () => {
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ export const claudeModels = [
|
|||||||
const geminiModels = [
|
const geminiModels = [
|
||||||
// Keep in sync with backend curated Gemini lists.
|
// Keep in sync with backend curated Gemini lists.
|
||||||
// This list is intentionally conservative (models commonly available across OAuth/API key).
|
// 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.0-flash',
|
||||||
'gemini-2.5-flash',
|
'gemini-2.5-flash',
|
||||||
'gemini-2.5-flash-image',
|
|
||||||
'gemini-2.5-pro',
|
'gemini-2.5-pro',
|
||||||
'gemini-3-flash-preview',
|
'gemini-3-flash-preview',
|
||||||
'gemini-3-pro-preview',
|
'gemini-3-pro-preview'
|
||||||
'gemini-3.1-flash-image'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// Sora
|
// Sora
|
||||||
@@ -87,8 +87,9 @@ const antigravityModels = [
|
|||||||
'claude-sonnet-4-5',
|
'claude-sonnet-4-5',
|
||||||
'claude-sonnet-4-5-thinking',
|
'claude-sonnet-4-5-thinking',
|
||||||
// Gemini 2.5 系列
|
// Gemini 2.5 系列
|
||||||
'gemini-2.5-flash',
|
'gemini-3.1-flash-image',
|
||||||
'gemini-2.5-flash-image',
|
'gemini-2.5-flash-image',
|
||||||
|
'gemini-2.5-flash',
|
||||||
'gemini-2.5-flash-lite',
|
'gemini-2.5-flash-lite',
|
||||||
'gemini-2.5-flash-thinking',
|
'gemini-2.5-flash-thinking',
|
||||||
'gemini-2.5-pro',
|
'gemini-2.5-pro',
|
||||||
@@ -99,7 +100,6 @@ const antigravityModels = [
|
|||||||
// Gemini 3.1 系列
|
// Gemini 3.1 系列
|
||||||
'gemini-3.1-pro-high',
|
'gemini-3.1-pro-high',
|
||||||
'gemini-3.1-pro-low',
|
'gemini-3.1-pro-low',
|
||||||
'gemini-3.1-flash-image',
|
|
||||||
'gemini-3-pro-image',
|
'gemini-3-pro-image',
|
||||||
// 其他
|
// 其他
|
||||||
'gpt-oss-120b-medium',
|
'gpt-oss-120b-medium',
|
||||||
|
|||||||
Reference in New Issue
Block a user