From 471b1c3eeb3a0efec261dffdfe48277aab5af2c3 Mon Sep 17 00:00:00 2001 From: ianshaw Date: Sun, 4 Jan 2026 22:26:33 -0800 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=E4=BF=AE=E5=A4=8D=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E6=97=B6=E9=81=97=E6=BC=8F=E7=9A=84=20SVG=20=E5=9B=BE?= =?UTF-8?q?=E6=A0=87=EF=BC=8C=E5=88=9B=E5=BB=BA=E7=BB=9F=E4=B8=80=E5=9B=BE?= =?UTF-8?q?=E6=A0=87=E7=AE=A1=E7=90=86=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 Icon.vue 统一管理 SVG 图标(20+ 常用图标) - 修复 AccountActionMenu 中被错误替换为 emoji 的图标 - 修复 ProfileView 和 GroupsView 中的 emoji 图标 - 图标支持 size/strokeWidth 属性,便于复用 --- .../service/antigravity_gateway_service.go | 61 +++++++++++++++-- .../admin/account/AccountActionMenu.vue | 34 ++++++++-- frontend/src/components/icons/Icon.vue | 66 +++++++++++++++++++ frontend/src/components/icons/index.ts | 1 + frontend/src/views/admin/GroupsView.vue | 5 +- frontend/src/views/user/ProfileView.vue | 3 +- 6 files changed, 156 insertions(+), 14 deletions(-) create mode 100644 frontend/src/components/icons/Icon.vue create mode 100644 frontend/src/components/icons/index.ts diff --git a/backend/internal/service/antigravity_gateway_service.go b/backend/internal/service/antigravity_gateway_service.go index cfbb85d2..35aa3937 100644 --- a/backend/internal/service/antigravity_gateway_service.go +++ b/backend/internal/service/antigravity_gateway_service.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "log" + mathrand "math/rand" "net/http" "strings" "sync/atomic" @@ -405,6 +406,14 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context, // 重试循环 var resp *http.Response for attempt := 1; attempt <= antigravityMaxRetries; attempt++ { + // 检查 context 是否已取消(客户端断开连接) + select { + case <-ctx.Done(): + log.Printf("%s status=context_canceled error=%v", prefix, ctx.Err()) + return nil, ctx.Err() + default: + } + upstreamReq, err := antigravity.NewAPIRequest(ctx, action, accessToken, geminiBody) if err != nil { return nil, err @@ -414,7 +423,10 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context, if err != nil { if attempt < antigravityMaxRetries { log.Printf("%s status=request_failed retry=%d/%d error=%v", prefix, attempt, antigravityMaxRetries, err) - sleepAntigravityBackoff(attempt) + if !sleepAntigravityBackoffWithContext(ctx, attempt) { + log.Printf("%s status=context_canceled_during_backoff", prefix) + return nil, ctx.Err() + } continue } log.Printf("%s status=request_failed retries_exhausted error=%v", prefix, err) @@ -427,7 +439,10 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context, if attempt < antigravityMaxRetries { log.Printf("%s status=%d retry=%d/%d", prefix, resp.StatusCode, attempt, antigravityMaxRetries) - sleepAntigravityBackoff(attempt) + if !sleepAntigravityBackoffWithContext(ctx, attempt) { + log.Printf("%s status=context_canceled_during_backoff", prefix) + return nil, ctx.Err() + } continue } // 所有重试都失败,标记限流状态 @@ -901,6 +916,14 @@ func (s *AntigravityGatewayService) ForwardGemini(ctx context.Context, c *gin.Co // 重试循环 var resp *http.Response for attempt := 1; attempt <= antigravityMaxRetries; attempt++ { + // 检查 context 是否已取消(客户端断开连接) + select { + case <-ctx.Done(): + log.Printf("%s status=context_canceled error=%v", prefix, ctx.Err()) + return nil, ctx.Err() + default: + } + upstreamReq, err := antigravity.NewAPIRequest(ctx, upstreamAction, accessToken, wrappedBody) if err != nil { return nil, err @@ -910,7 +933,10 @@ func (s *AntigravityGatewayService) ForwardGemini(ctx context.Context, c *gin.Co if err != nil { if attempt < antigravityMaxRetries { log.Printf("%s status=request_failed retry=%d/%d error=%v", prefix, attempt, antigravityMaxRetries, err) - sleepAntigravityBackoff(attempt) + if !sleepAntigravityBackoffWithContext(ctx, attempt) { + log.Printf("%s status=context_canceled_during_backoff", prefix) + return nil, ctx.Err() + } continue } log.Printf("%s status=request_failed retries_exhausted error=%v", prefix, err) @@ -923,7 +949,10 @@ func (s *AntigravityGatewayService) ForwardGemini(ctx context.Context, c *gin.Co if attempt < antigravityMaxRetries { log.Printf("%s status=%d retry=%d/%d", prefix, resp.StatusCode, attempt, antigravityMaxRetries) - sleepAntigravityBackoff(attempt) + if !sleepAntigravityBackoffWithContext(ctx, attempt) { + log.Printf("%s status=context_canceled_during_backoff", prefix) + return nil, ctx.Err() + } continue } // 所有重试都失败,标记限流状态 @@ -1062,6 +1091,30 @@ func sleepAntigravityBackoff(attempt int) { sleepGeminiBackoff(attempt) // 复用 Gemini 的退避逻辑 } +// sleepAntigravityBackoffWithContext 带 context 取消检查的退避等待 +// 返回 true 表示正常完成等待,false 表示 context 已取消 +func sleepAntigravityBackoffWithContext(ctx context.Context, attempt int) bool { + delay := geminiRetryBaseDelay * time.Duration(1< geminiRetryMaxDelay { + delay = geminiRetryMaxDelay + } + + // +/- 20% jitter + r := mathrand.New(mathrand.NewSource(time.Now().UnixNano())) + jitter := time.Duration(float64(delay) * 0.2 * (r.Float64()*2 - 1)) + sleepFor := delay + jitter + if sleepFor < 0 { + sleepFor = 0 + } + + select { + case <-ctx.Done(): + return false + case <-time.After(sleepFor): + return true + } +} + func (s *AntigravityGatewayService) handleUpstreamError(ctx context.Context, prefix string, account *Account, statusCode int, headers http.Header, body []byte) { // 429 使用 Gemini 格式解析(从 body 解析重置时间) if statusCode == 429 { diff --git a/frontend/src/components/admin/account/AccountActionMenu.vue b/frontend/src/components/admin/account/AccountActionMenu.vue index ebf574d5..980fd352 100644 --- a/frontend/src/components/admin/account/AccountActionMenu.vue +++ b/frontend/src/components/admin/account/AccountActionMenu.vue @@ -3,15 +3,33 @@
@@ -21,10 +39,12 @@ \ No newline at end of file + diff --git a/frontend/src/components/icons/Icon.vue b/frontend/src/components/icons/Icon.vue new file mode 100644 index 00000000..8632ef5f --- /dev/null +++ b/frontend/src/components/icons/Icon.vue @@ -0,0 +1,66 @@ + + + diff --git a/frontend/src/components/icons/index.ts b/frontend/src/components/icons/index.ts new file mode 100644 index 00000000..ea5ccfd4 --- /dev/null +++ b/frontend/src/components/icons/index.ts @@ -0,0 +1 @@ +export { default as Icon } from './Icon.vue' diff --git a/frontend/src/views/admin/GroupsView.vue b/frontend/src/views/admin/GroupsView.vue index ffa43edf..cb50afc6 100644 --- a/frontend/src/views/admin/GroupsView.vue +++ b/frontend/src/views/admin/GroupsView.vue @@ -336,7 +336,7 @@

- 💡 {{ t('admin.groups.exclusiveTooltip.example') }} + {{ t('admin.groups.exclusiveTooltip.example') }} {{ t('admin.groups.exclusiveTooltip.exampleContent') }}

@@ -531,7 +531,7 @@

- 💡 {{ t('admin.groups.exclusiveTooltip.example') }} + {{ t('admin.groups.exclusiveTooltip.example') }} {{ t('admin.groups.exclusiveTooltip.exampleContent') }}

@@ -691,6 +691,7 @@ import ConfirmDialog from '@/components/common/ConfirmDialog.vue' import EmptyState from '@/components/common/EmptyState.vue' import Select from '@/components/common/Select.vue' import PlatformIcon from '@/components/common/PlatformIcon.vue' +import { Icon } from '@/components/icons' const { t } = useI18n() const appStore = useAppStore() diff --git a/frontend/src/views/user/ProfileView.vue b/frontend/src/views/user/ProfileView.vue index eaf98b77..a85ea5ca 100644 --- a/frontend/src/views/user/ProfileView.vue +++ b/frontend/src/views/user/ProfileView.vue @@ -9,7 +9,7 @@
-
💬
+

{{ t('common.contactSupport') }}

{{ contactInfo }}

@@ -27,6 +27,7 @@ import StatCard from '@/components/common/StatCard.vue' import ProfileInfoCard from '@/components/user/profile/ProfileInfoCard.vue' import ProfileEditForm from '@/components/user/profile/ProfileEditForm.vue' import ProfilePasswordForm from '@/components/user/profile/ProfilePasswordForm.vue' +import { Icon } from '@/components/icons' const { t } = useI18n(); const authStore = useAuthStore(); const user = computed(() => authStore.user) const contactInfo = ref('')