Просмотр исходного кода

🔔 feat: Add subscription-aware quota notifications and update UI copy

Routes quota alerts through a subscription-specific check when billing from subscriptions, preventing wallet-based thresholds from triggering false warnings.
Updates the notification settings description and localization keys to clarify that both wallet and subscription balances are monitored.
t0ng7u 6 дней назад
Родитель
Сommit
82138fc0b0

+ 6 - 2
service/billing.go

@@ -58,9 +58,13 @@ func SettleBilling(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, actualQuo
 			return err
 		}
 
-		// 发送额度通知
+		// 发送额度通知(订阅计费使用订阅剩余额度)
 		if actualQuota != 0 {
-			checkAndSendQuotaNotify(relayInfo, actualQuota-preConsumed, preConsumed)
+			if relayInfo.BillingSource == BillingSourceSubscription {
+				checkAndSendSubscriptionQuotaNotify(relayInfo)
+			} else {
+				checkAndSendQuotaNotify(relayInfo, actualQuota-preConsumed, preConsumed)
+			}
 		}
 		return nil
 	}

+ 48 - 0
service/quota.go

@@ -556,3 +556,51 @@ func checkAndSendQuotaNotify(relayInfo *relaycommon.RelayInfo, quota int, preCon
 		}
 	})
 }
+
+func checkAndSendSubscriptionQuotaNotify(relayInfo *relaycommon.RelayInfo) {
+	gopool.Go(func() {
+		if relayInfo == nil {
+			return
+		}
+		if relayInfo.SubscriptionId == 0 || relayInfo.SubscriptionAmountTotal <= 0 {
+			return
+		}
+
+		userSetting := relayInfo.UserSetting
+		threshold := common.QuotaRemindThreshold
+		if userSetting.QuotaWarningThreshold != 0 {
+			threshold = int(userSetting.QuotaWarningThreshold)
+		}
+
+		usedAfter := relayInfo.SubscriptionAmountUsedAfterPreConsume + relayInfo.SubscriptionPostDelta
+		remaining := relayInfo.SubscriptionAmountTotal - usedAfter
+		if remaining >= int64(threshold) {
+			return
+		}
+
+		prompt := "您的订阅额度即将用尽"
+		topUpLink := fmt.Sprintf("%s/console/topup", system_setting.ServerAddress)
+
+		var content string
+		var values []interface{}
+		notifyType := userSetting.NotifyType
+		if notifyType == "" {
+			notifyType = dto.NotifyTypeEmail
+		}
+
+		if notifyType == dto.NotifyTypeBark {
+			content = "{{value}},剩余额度:{{value}},请及时充值"
+			values = []interface{}{prompt, logger.FormatQuota(int(remaining))}
+		} else if notifyType == dto.NotifyTypeGotify {
+			content = "{{value}},当前剩余额度为 {{value}},请及时充值。"
+			values = []interface{}{prompt, logger.FormatQuota(int(remaining))}
+		} else {
+			content = "{{value}},当前剩余额度为 {{value}},为了不影响您的使用,请及时充值。<br/>充值链接:<a href='{{value}}'>{{value}}</a>"
+			values = []interface{}{prompt, logger.FormatQuota(int(remaining)), topUpLink, topUpLink}
+		}
+
+		if err := NotifyUser(relayInfo.UserId, relayInfo.UserEmail, relayInfo.UserSetting, dto.NewNotify(dto.NotifyTypeQuotaExceed, prompt, content, values)); err != nil {
+			common.SysError(fmt.Sprintf("failed to send subscription quota notify to user %d: %s", relayInfo.UserId, err.Error()))
+		}
+	})
+}

+ 1 - 1
web/src/components/settings/personal/cards/NotificationSettings.jsx

@@ -446,7 +446,7 @@ const NotificationSettings = ({
                   onChange={(val) => handleFormChange('warningThreshold', val)}
                   prefix={<IconBell />}
                   extraText={t(
-                    '当剩余额度低于此数值时,系统将通过选择的方式发送通知',
+                    '当钱包或订阅剩余额度低于此数值时,系统将通过选择的方式发送通知',
                   )}
                   style={{ width: '100%', maxWidth: '300px' }}
                   rules={[

+ 1 - 1
web/src/i18n/locales/en.json

@@ -1032,7 +1032,7 @@
     "当前设置类型: ": "Current setting type: ",
     "当前跟随系统": "Currently following system",
     "当前配置无法连接到 io.net。": "Unable to connect to io.net with current configuration.",
-    "当剩余额度低于此数值时,系统将通过选择的方式发送通知": "When the remaining quota is lower than this value, the system will send a notification through the selected method",
+    "当钱包或订阅剩余额度低于此数值时,系统将通过选择的方式发送通知": "When wallet or subscription remaining quota falls below this value, the system will send a notification through the selected method",
     "当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用": "Accept calls even if the model has no price settings, use only when you trust the website, which may incur high costs",
     "当运行通道全部测试时,超过此时间将自动禁用通道": "When running all channel tests, the channel will be automatically disabled when this time is exceeded",
     "待使用收益": "Proceeds to be used",

+ 1 - 1
web/src/i18n/locales/fr.json

@@ -1042,7 +1042,7 @@
     "当前设置类型: ": "Type de paramètre actuel : ",
     "当前跟随系统": "Suit actuellement le système",
     "当前配置无法连接到 io.net。": "Unable to connect to io.net with current configuration.",
-    "当剩余额度低于此数值时,系统将通过选择的方式发送通知": "Lorsque le quota restant est inférieur à cette valeur, le système enverra une notification via la méthode sélectionnée",
+    "当钱包或订阅剩余额度低于此数值时,系统将通过选择的方式发送通知": "Lorsque le quota restant du portefeuille ou de l'abonnement est inférieur à cette valeur, le système enverra une notification via la méthode sélectionnée",
     "当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用": "Acceptez les appels même si le modèle n'a pas de prix défini, utilisez uniquement lorsque vous faites confiance au site Web, ce qui peut entraîner des coûts élevés",
     "当运行通道全部测试时,超过此时间将自动禁用通道": "Lors de l'exécution de tous les tests de canaux, le canal sera automatiquement désactivé lorsque ce temps sera dépassé",
     "待使用收益": "Produits à utiliser",

+ 1 - 1
web/src/i18n/locales/ja.json

@@ -1027,7 +1027,7 @@
     "当前设置类型: ": "現在の設定タイプ:",
     "当前跟随系统": "システム設定に準拠",
     "当前配置无法连接到 io.net。": "Unable to connect to io.net with current configuration.",
-    "当剩余额度低于此数值时,系统将通过选择的方式发送通知": "残りクォータがこの数値を下回った場合、システムが選択された方法で通知を送信します",
+    "当钱包或订阅剩余额度低于此数值时,系统将通过选择的方式发送通知": "ウォレットまたはサブスクリプションの残りクォータがこの値を下回ると、システムは選択した方法で通知します",
     "当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用": "モデルに料金が設定されていない場合でもAPIコールを受け付けます。信頼できるウェブサイトの場合にのみ使用してください。高額な料金が発生する可能性があります",
     "当运行通道全部测试时,超过此时间将自动禁用通道": "すべてのチャネルテストの実行時、この時間を超えたチャネルは自動的に無効になります",
     "待使用收益": "未使用の収益",

+ 1 - 1
web/src/i18n/locales/ru.json

@@ -1053,7 +1053,7 @@
     "当前设置类型: ": "Текущий тип настроек: ",
     "当前跟随系统": "Следовать системе",
     "当前配置无法连接到 io.net。": "Unable to connect to io.net with current configuration.",
-    "当剩余额度低于此数值时,系统将通过选择的方式发送通知": "Когда оставшаяся квота ниже этого значения, система отправит уведомление выбранным способом",
+    "当钱包或订阅剩余额度低于此数值时,系统将通过选择的方式发送通知": "Когда оставшаяся квота кошелька или подписки ниже этого значения, система отправит уведомление выбранным способом",
     "当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用": "Принимать вызовы моделей без установленной цены, использовать только если вы доверяете сайту, могут возникнуть высокие расходы",
     "当运行通道全部测试时,超过此时间将自动禁用通道": "При тестировании всех работающих каналов, превышение этого времени автоматически отключит канал",
     "待使用收益": "Ожидаемый доход",

+ 1 - 1
web/src/i18n/locales/vi.json

@@ -1028,7 +1028,7 @@
     "当前设置类型: ": "Loại cài đặt hiện tại: ",
     "当前跟随系统": "Hiện đang theo hệ thống",
     "当前配置无法连接到 io.net。": "Unable to connect to io.net with current configuration.",
-    "当剩余额度低于此数值时,系统将通过选择的方式发送通知": "Khi hạn ngạch còn lại thấp hơn giá trị này, hệ thống sẽ gửi thông báo thông qua phương thức đã chọn",
+    "当钱包或订阅剩余额度低于此数值时,系统将通过选择的方式发送通知": "Khi hạn ngạch còn lại của ví hoặc gói thuê bao thấp hơn giá trị này, hệ thống sẽ gửi thông báo theo phương thức đã chọn",
     "当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用": "Chấp nhận cuộc gọi ngay cả khi mô hình không có cài đặt giá, chỉ sử dụng khi bạn tin tưởng trang web, điều này có thể phát sinh chi phí cao",
     "当运行通道全部测试时,超过此时间将自动禁用通道": "Khi chạy tất cả các kiểm tra kênh, kênh sẽ tự động bị vô hiệu hóa khi vượt quá thời gian này",
     "待使用收益": "Tiền thu được để sử dụng",

+ 1 - 1
web/src/i18n/locales/zh.json

@@ -1022,7 +1022,7 @@
     "当前设置类型: ": "当前设置类型: ",
     "当前跟随系统": "当前跟随系统",
     "当前配置无法连接到 io.net。": "当前配置无法连接到 io.net。",
-    "当剩余额度低于此数值时,系统将通过选择的方式发送通知": "当剩余额度低于此数值时,系统将通过选择的方式发送通知",
+    "当钱包或订阅剩余额度低于此数值时,系统将通过选择的方式发送通知": "当钱包或订阅剩余额度低于此数值时,系统将通过选择的方式发送通知",
     "当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用": "当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用",
     "当运行通道全部测试时,超过此时间将自动禁用通道": "当运行通道全部测试时,超过此时间将自动禁用通道",
     "待使用收益": "待使用收益",