From d55cf7b67f433d21535a1cf75382988298295643 Mon Sep 17 00:00:00 2001 From: AzurCrystal Date: Wed, 3 Jun 2026 02:05:14 +0800 Subject: [PATCH 1/4] Remove deprecated Gemini image models and tidy related code --- backend/internal/domain/constants.go | 17 +++------- backend/internal/pkg/gemini/models.go | 2 -- backend/internal/pkg/geminicli/models.go | 2 -- .../repository/account_share_policy_repo.go | 4 +-- backend/internal/repository/subsite_repo.go | 6 ++-- .../service/account_service_delete_test.go | 31 ++++++++++++++++--- .../internal/service/account_test_service.go | 30 ++---------------- .../internal/service/auth_oauth_email_flow.go | 2 +- backend/internal/service/billing_service.go | 8 +++++ backend/internal/subsite/agent/proxy.go | 10 +++--- frontend/package.json | 5 +++ frontend/pnpm-lock.yaml | 14 +++++---- .../components/store/DeliveredFilesList.vue | 8 ++--- 13 files changed, 68 insertions(+), 71 deletions(-) diff --git a/backend/internal/domain/constants.go b/backend/internal/domain/constants.go index 7ee3353dc..27431f2fb 100644 --- a/backend/internal/domain/constants.go +++ b/backend/internal/domain/constants.go @@ -103,12 +103,10 @@ var DefaultAntigravityModelMapping = map[string]string{ "claude-haiku-4-5": "claude-sonnet-4-6", "claude-haiku-4-5-20251001": "claude-sonnet-4-6", // Gemini 2.5 白名单 - "gemini-2.5-flash": "gemini-2.5-flash", - "gemini-2.5-flash-image": "gemini-2.5-flash-image", - "gemini-2.5-flash-image-preview": "gemini-2.5-flash-image", - "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", + "gemini-2.5-flash": "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", // Gemini 3 白名单 "gemini-3-flash": "gemini-3-flash", "gemini-3-pro-high": "gemini-3-pro-high", @@ -121,13 +119,6 @@ var DefaultAntigravityModelMapping = map[string]string{ "gemini-3.1-pro-low": "gemini-3.1-pro-low", // Gemini 3.1 preview 映射 "gemini-3.1-pro-preview": "gemini-3.1-pro-high", - // Gemini 3.1 image 白名单 - "gemini-3.1-flash-image": "gemini-3.1-flash-image", - // Gemini 3.1 image preview 映射 - "gemini-3.1-flash-image-preview": "gemini-3.1-flash-image", - // Gemini 3 image 兼容映射(向 3.1 image 迁移) - "gemini-3-pro-image": "gemini-3.1-flash-image", - "gemini-3-pro-image-preview": "gemini-3.1-flash-image", // 其他官方模型 "gpt-oss-120b-medium": "gpt-oss-120b-medium", "tab_flash_lite_preview": "tab_flash_lite_preview", diff --git a/backend/internal/pkg/gemini/models.go b/backend/internal/pkg/gemini/models.go index fac79d187..f2c464166 100644 --- a/backend/internal/pkg/gemini/models.go +++ b/backend/internal/pkg/gemini/models.go @@ -20,13 +20,11 @@ func DefaultModels() []Model { return []Model{ {Name: "models/gemini-2.0-flash", SupportedGenerationMethods: methods}, {Name: "models/gemini-2.5-flash", SupportedGenerationMethods: methods}, - {Name: "models/gemini-2.5-flash-image", SupportedGenerationMethods: methods}, {Name: "models/gemini-2.5-pro", SupportedGenerationMethods: methods}, {Name: "models/gemini-3-flash-preview", SupportedGenerationMethods: methods}, {Name: "models/gemini-3-pro-preview", SupportedGenerationMethods: methods}, {Name: "models/gemini-3.1-pro-preview", SupportedGenerationMethods: methods}, {Name: "models/gemini-3.1-pro-preview-customtools", SupportedGenerationMethods: methods}, - {Name: "models/gemini-3.1-flash-image", SupportedGenerationMethods: methods}, } } diff --git a/backend/internal/pkg/geminicli/models.go b/backend/internal/pkg/geminicli/models.go index 195fb06f8..1fc4d983c 100644 --- a/backend/internal/pkg/geminicli/models.go +++ b/backend/internal/pkg/geminicli/models.go @@ -13,12 +13,10 @@ type Model struct { var DefaultModels = []Model{ {ID: "gemini-2.0-flash", Type: "model", DisplayName: "Gemini 2.0 Flash", CreatedAt: ""}, {ID: "gemini-2.5-flash", Type: "model", DisplayName: "Gemini 2.5 Flash", CreatedAt: ""}, - {ID: "gemini-2.5-flash-image", Type: "model", DisplayName: "Gemini 2.5 Flash Image", CreatedAt: ""}, {ID: "gemini-2.5-pro", Type: "model", DisplayName: "Gemini 2.5 Pro", CreatedAt: ""}, {ID: "gemini-3-flash-preview", Type: "model", DisplayName: "Gemini 3 Flash Preview", CreatedAt: ""}, {ID: "gemini-3-pro-preview", Type: "model", DisplayName: "Gemini 3 Pro Preview", CreatedAt: ""}, {ID: "gemini-3.1-pro-preview", Type: "model", DisplayName: "Gemini 3.1 Pro Preview", CreatedAt: ""}, - {ID: "gemini-3.1-flash-image", Type: "model", DisplayName: "Gemini 3.1 Flash Image", CreatedAt: ""}, } // DefaultTestModel is the default model to preselect in test flows. diff --git a/backend/internal/repository/account_share_policy_repo.go b/backend/internal/repository/account_share_policy_repo.go index fa264da8f..10a01efbe 100644 --- a/backend/internal/repository/account_share_policy_repo.go +++ b/backend/internal/repository/account_share_policy_repo.go @@ -218,8 +218,8 @@ func accountSharePolicyWhere(filters service.AccountSharePolicyFilters) (string, args := make([]any, 0, 3) add := func(condition string, arg any) { args = append(args, arg) - where.WriteString(" AND ") - where.WriteString(fmt.Sprintf(condition, len(args))) + _, _ = where.WriteString(" AND ") + _, _ = where.WriteString(fmt.Sprintf(condition, len(args))) } if scopeType := strings.TrimSpace(filters.ScopeType); scopeType != "" { add("scope_type = $%d", scopeType) diff --git a/backend/internal/repository/subsite_repo.go b/backend/internal/repository/subsite_repo.go index 782fff070..e24427dca 100644 --- a/backend/internal/repository/subsite_repo.go +++ b/backend/internal/repository/subsite_repo.go @@ -124,7 +124,7 @@ func (r *subsiteRepository) List(ctx context.Context, params pagination.Paginati if err != nil { return nil, nil, fmt.Errorf("list subsites: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() items := make([]service.Subsite, 0) for rows.Next() { subsite, err := scanSubsite(rows) @@ -459,7 +459,7 @@ func (r *accountLeaseRepository) ListActiveAccountIDsBySubsite(ctx context.Conte if err != nil { return nil, fmt.Errorf("list active lease account ids: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() accountIDs := make([]int64, 0) for rows.Next() { @@ -644,7 +644,7 @@ func (r *accountLeaseRepository) list(ctx context.Context, query string, args .. if err != nil { return nil, fmt.Errorf("list account leases: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() leases := make([]service.AccountLease, 0) for rows.Next() { lease, err := scanAccountLease(rows) diff --git a/backend/internal/service/account_service_delete_test.go b/backend/internal/service/account_service_delete_test.go index 2892bdff2..6d9c17617 100644 --- a/backend/internal/service/account_service_delete_test.go +++ b/backend/internal/service/account_service_delete_test.go @@ -19,14 +19,20 @@ import ( // 用于隔离测试 AccountService.Delete 方法,避免依赖真实数据库。 // // 设计说明: +// - account/getErr: 模拟 GetByID 返回的账号和错误 // - exists: 模拟 ExistsByID 返回的存在性结果 // - existsErr: 模拟 ExistsByID 返回的错误 // - deleteErr: 模拟 Delete 返回的错误 +// - getIDs/existsIDs: 记录查询调用的账号 ID,用于断言验证 // - deletedIDs: 记录被调用删除的账号 ID,用于断言验证 type accountRepoStub struct { + account *Account + getErr error exists bool // ExistsByID 的返回值 existsErr error // ExistsByID 的错误返回值 deleteErr error // Delete 的错误返回值 + getIDs []int64 // 记录已查询的账号 ID 列表 + existsIDs []int64 // 记录已检查存在性的账号 ID 列表 deletedIDs []int64 // 记录已删除的账号 ID 列表 } @@ -37,7 +43,8 @@ func (s *accountRepoStub) Create(ctx context.Context, account *Account) error { } func (s *accountRepoStub) GetByID(ctx context.Context, id int64) (*Account, error) { - panic("unexpected GetByID call") + s.getIDs = append(s.getIDs, id) + return s.account, s.getErr } func (s *accountRepoStub) GetByIDs(ctx context.Context, ids []int64) ([]*Account, error) { @@ -45,8 +52,9 @@ func (s *accountRepoStub) GetByIDs(ctx context.Context, ids []int64) ([]*Account } // ExistsByID 返回预设的存在性检查结果。 -// 这是 Delete 方法调用的第一个仓储方法,用于验证账号是否存在。 +// Delete 方法会在 GetByID 失败时使用它作为兼容性兜底。 func (s *accountRepoStub) ExistsByID(ctx context.Context, id int64) (bool, error) { + s.existsIDs = append(s.existsIDs, id) return s.exists, s.existsErr } @@ -209,7 +217,7 @@ func (s *accountRepoStub) ResetQuotaUsed(ctx context.Context, id int64) error { // TestAccountService_Delete_NotFound 测试删除不存在的账号时返回正确的错误。 // 预期行为: -// - ExistsByID 返回 false(账号不存在) +// - GetByID 返回 nil(账号不存在) // - 返回 ErrAccountNotFound 错误 // - Delete 方法不被调用(deletedIDs 为空) func TestAccountService_Delete_NotFound(t *testing.T) { @@ -218,21 +226,29 @@ func TestAccountService_Delete_NotFound(t *testing.T) { err := svc.Delete(context.Background(), 55) require.ErrorIs(t, err, ErrAccountNotFound) + require.Equal(t, []int64{55}, repo.getIDs) + require.Empty(t, repo.existsIDs) require.Empty(t, repo.deletedIDs) // 验证删除操作未被调用 } // TestAccountService_Delete_CheckError 测试存在性检查失败时的错误处理。 // 预期行为: +// - GetByID 返回错误 // - ExistsByID 返回数据库错误 // - 返回包含 "check account" 的错误信息 // - Delete 方法不被调用 func TestAccountService_Delete_CheckError(t *testing.T) { - repo := &accountRepoStub{existsErr: errors.New("db down")} + repo := &accountRepoStub{ + getErr: errors.New("get failed"), + existsErr: errors.New("db down"), + } svc := &AccountService{accountRepo: repo} err := svc.Delete(context.Background(), 55) require.Error(t, err) require.ErrorContains(t, err, "check account") // 验证错误信息包含上下文 + require.Equal(t, []int64{55}, repo.getIDs) + require.Equal(t, []int64{55}, repo.existsIDs) require.Empty(t, repo.deletedIDs) } @@ -244,6 +260,7 @@ func TestAccountService_Delete_CheckError(t *testing.T) { // - deletedIDs 记录了尝试删除的 ID func TestAccountService_Delete_DeleteError(t *testing.T) { repo := &accountRepoStub{ + account: &Account{ID: 55}, exists: true, deleteErr: errors.New("delete failed"), } @@ -252,6 +269,8 @@ func TestAccountService_Delete_DeleteError(t *testing.T) { err := svc.Delete(context.Background(), 55) require.Error(t, err) require.ErrorContains(t, err, "delete account") + require.Equal(t, []int64{55}, repo.getIDs) + require.Empty(t, repo.existsIDs) require.Equal(t, []int64{55}, repo.deletedIDs) // 验证删除操作被调用 } @@ -262,10 +281,12 @@ func TestAccountService_Delete_DeleteError(t *testing.T) { // - 返回 nil 错误 // - deletedIDs 记录了被删除的 ID func TestAccountService_Delete_Success(t *testing.T) { - repo := &accountRepoStub{exists: true} + repo := &accountRepoStub{account: &Account{ID: 55}} svc := &AccountService{accountRepo: repo} err := svc.Delete(context.Background(), 55) require.NoError(t, err) + require.Equal(t, []int64{55}, repo.getIDs) + require.Empty(t, repo.existsIDs) require.Equal(t, []int64{55}, repo.deletedIDs) // 验证正确的 ID 被删除 } diff --git a/backend/internal/service/account_test_service.go b/backend/internal/service/account_test_service.go index d459c25f8..509b3ceb2 100644 --- a/backend/internal/service/account_test_service.go +++ b/backend/internal/service/account_test_service.go @@ -51,7 +51,6 @@ type TestEvent struct { const ( defaultGeminiTextTestPrompt = "hi" - defaultGeminiImageTestPrompt = "Generate a cute orange cat astronaut sticker on a clean pastel background." defaultOpenAIImageTestPrompt = "Generate a cute orange cat astronaut sticker on a clean pastel background." openAITestMaxOutputTokens = 16 ) @@ -1030,34 +1029,9 @@ func (s *AccountTestService) buildCodeAssistRequest(ctx context.Context, accessT return req, nil } -// createGeminiTestPayload creates a minimal test payload for Gemini API. -// Image models use the image-generation path so the frontend can preview the returned image. +// createGeminiTestPayload creates a minimal text-only test payload for Gemini API. func createGeminiTestPayload(modelID string, prompt string) []byte { - if isImageGenerationModel(modelID) { - imagePrompt := strings.TrimSpace(prompt) - if imagePrompt == "" { - imagePrompt = defaultGeminiImageTestPrompt - } - - payload := map[string]any{ - "contents": []map[string]any{ - { - "role": "user", - "parts": []map[string]any{ - {"text": imagePrompt}, - }, - }, - }, - "generationConfig": map[string]any{ - "responseModalities": []string{"TEXT", "IMAGE"}, - "imageConfig": map[string]any{ - "aspectRatio": "1:1", - }, - }, - } - bytes, _ := json.Marshal(payload) - return bytes - } + _ = modelID textPrompt := strings.TrimSpace(prompt) if textPrompt == "" { diff --git a/backend/internal/service/auth_oauth_email_flow.go b/backend/internal/service/auth_oauth_email_flow.go index e3c8298c2..33371c821 100644 --- a/backend/internal/service/auth_oauth_email_flow.go +++ b/backend/internal/service/auth_oauth_email_flow.go @@ -18,7 +18,7 @@ func normalizeOAuthSignupSource(signupSource string) string { switch signupSource { case "", "email": return "email" - case "linuxdo", "wechat", "oidc", "github", "google": + case "linuxdo", "wechat", "oidc": return signupSource default: return "email" diff --git a/backend/internal/service/billing_service.go b/backend/internal/service/billing_service.go index 2ef531eb4..65f4a1d1f 100644 --- a/backend/internal/service/billing_service.go +++ b/backend/internal/service/billing_service.go @@ -247,6 +247,12 @@ func (s *BillingService) initFallbackPricing() { CacheReadPricePerToken: 7.5e-8, SupportsCacheBreakdown: false, } + s.fallbackPrices["gpt-5.4-nano"] = &ModelPricing{ + InputPricePerToken: 2e-7, + OutputPricePerToken: 1.25e-6, + CacheReadPricePerToken: 2e-8, + SupportsCacheBreakdown: false, + } // OpenAI GPT-5.2(本地兜底) s.fallbackPrices["gpt-5.2"] = &ModelPricing{ InputPricePerToken: 1.75e-6, @@ -314,6 +320,8 @@ func (s *BillingService) getFallbackPricing(model string) *ModelPricing { switch normalized { case "gpt-5.5": return s.fallbackPrices["gpt-5.5"] + case "gpt-5.4-nano": + return s.fallbackPrices["gpt-5.4-nano"] case "gpt-5.4-mini": return s.fallbackPrices["gpt-5.4-mini"] case "gpt-5.4": diff --git a/backend/internal/subsite/agent/proxy.go b/backend/internal/subsite/agent/proxy.go index aba49dab1..42c9dbe7d 100644 --- a/backend/internal/subsite/agent/proxy.go +++ b/backend/internal/subsite/agent/proxy.go @@ -76,7 +76,7 @@ func (s *Server) proxyAuthorizedRequest(c *gin.Context, authorization *service.A if err != nil { return nil, fmt.Errorf("call upstream: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() responseBody, wroteResponse, metrics, err := streamOrBufferResponse(c, resp) if err != nil { @@ -148,7 +148,7 @@ func (s *Server) proxyOpenAIChatCompletionsViaResponses(c *gin.Context, authoriz if err != nil { return nil, fmt.Errorf("call upstream: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() responseBody, wroteResponse, metrics, err := streamOrBufferResponse(c, resp) if err != nil { @@ -1240,7 +1240,7 @@ func convertResponsesSSEToChatCompletionsSSE(body []byte, originalModel string) if err != nil { return nil, fmt.Errorf("marshal chat chunk: %w", err) } - out.WriteString(sse) + _, _ = out.WriteString(sse) } } if err := scanner.Err(); err != nil { @@ -1251,8 +1251,8 @@ func convertResponsesSSEToChatCompletionsSSE(body []byte, originalModel string) if err != nil { return nil, fmt.Errorf("finalize chat chunk: %w", err) } - out.WriteString(sse) + _, _ = out.WriteString(sse) } - out.WriteString("data: [DONE]\n\n") + _, _ = out.WriteString("data: [DONE]\n\n") return out.Bytes(), nil } diff --git a/frontend/package.json b/frontend/package.json index 61d58c10f..507d20cbe 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -57,5 +57,10 @@ "vite-plugin-checker": "^0.9.1", "vitest": "^2.1.9", "vue-tsc": "^2.2.0" + }, + "pnpm": { + "overrides": { + "js-cookie": "^3.0.8" + } } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 12fd84b55..06cd44bb2 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + js-cookie: ^3.0.8 + importers: .: @@ -2879,9 +2882,8 @@ packages: engines: {node: '>=14'} hasBin: true - js-cookie@3.0.5: - resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} - engines: {node: '>=14'} + js-cookie@3.0.8: + resolution: {integrity: sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -6362,7 +6364,7 @@ snapshots: '@types/js-cookie': 3.0.6 dayjs: 1.11.20 intersection-observer: 0.12.2 - js-cookie: 3.0.5 + js-cookie: 3.0.8 lodash: 4.18.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -7649,10 +7651,10 @@ snapshots: config-chain: 1.1.13 editorconfig: 1.0.4 glob: 10.5.0 - js-cookie: 3.0.5 + js-cookie: 3.0.8 nopt: 7.2.1 - js-cookie@3.0.5: {} + js-cookie@3.0.8: {} js-tokens@4.0.0: {} diff --git a/frontend/src/components/store/DeliveredFilesList.vue b/frontend/src/components/store/DeliveredFilesList.vue index b6ad51617..df8b3eb50 100644 --- a/frontend/src/components/store/DeliveredFilesList.vue +++ b/frontend/src/components/store/DeliveredFilesList.vue @@ -6,7 +6,7 @@ v-if="normalizedFiles.length > 1" type="button" class="btn btn-secondary btn-sm min-h-[2.5rem]" - @click="downloadAllFiles" + @click="handleDownloadAllFiles" > {{ t('store.downloadAllFiles') }} @@ -25,7 +25,7 @@