feat: require compliance confirmation for paid features
Gate payment, redemption, subscription, and invitation reward flows behind an audited compliance acknowledgement.
This commit is contained in:
parent
aa56667b8f
commit
0526a22643
@ -3,9 +3,11 @@ package controller
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/i18n"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
"github.com/QuantumNous/new-api/setting"
|
||||
"github.com/QuantumNous/new-api/setting/console_setting"
|
||||
@ -27,6 +29,19 @@ var completionRatioMetaOptionKeys = []string{
|
||||
"AudioCompletionRatio",
|
||||
}
|
||||
|
||||
func isPaymentComplianceOptionKey(key string) bool {
|
||||
return strings.HasPrefix(key, "payment_setting.compliance_")
|
||||
}
|
||||
|
||||
func isPositiveOptionValue(value string) bool {
|
||||
intValue, err := strconv.Atoi(strings.TrimSpace(value))
|
||||
if err == nil {
|
||||
return intValue > 0
|
||||
}
|
||||
floatValue, err := strconv.ParseFloat(strings.TrimSpace(value), 64)
|
||||
return err == nil && floatValue > 0
|
||||
}
|
||||
|
||||
func isVisiblePublicKeyOption(key string) bool {
|
||||
switch key {
|
||||
case "WaffoPancakeWebhookPublicKey", "WaffoPancakeWebhookTestKey":
|
||||
@ -104,7 +119,6 @@ func GetOptions(c *gin.Context) {
|
||||
"message": "",
|
||||
"data": options,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
type OptionUpdateRequest struct {
|
||||
@ -133,6 +147,18 @@ func UpdateOption(c *gin.Context) {
|
||||
option.Value = fmt.Sprintf("%v", option.Value)
|
||||
}
|
||||
switch option.Key {
|
||||
case "QuotaForInviter", "QuotaForInvitee":
|
||||
if isPositiveOptionValue(option.Value.(string)) && !operation_setting.IsPaymentComplianceConfirmed() {
|
||||
common.ApiErrorI18n(c, i18n.MsgPaymentComplianceRequired)
|
||||
return
|
||||
}
|
||||
default:
|
||||
if isPaymentComplianceOptionKey(option.Key) {
|
||||
common.ApiErrorMsg(c, "合规确认字段不允许通过通用设置接口修改")
|
||||
return
|
||||
}
|
||||
}
|
||||
switch option.Key {
|
||||
case "GitHubOAuthEnabled":
|
||||
if option.Value == "true" && common.GitHubClientId == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@ -324,5 +350,4 @@ func UpdateOption(c *gin.Context) {
|
||||
"success": true,
|
||||
"message": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
82
controller/payment_compliance.go
Normal file
82
controller/payment_compliance.go
Normal file
@ -0,0 +1,82 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/i18n"
|
||||
"github.com/QuantumNous/new-api/logger"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
"github.com/QuantumNous/new-api/setting/operation_setting"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PaymentComplianceRequest struct {
|
||||
Confirmed bool `json:"confirmed"`
|
||||
}
|
||||
|
||||
func requirePaymentCompliance(c *gin.Context) bool {
|
||||
if !operation_setting.IsPaymentComplianceConfirmed() {
|
||||
common.ApiErrorI18n(c, i18n.MsgPaymentComplianceRequired)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ConfirmPaymentCompliance(c *gin.Context) {
|
||||
if c.GetBool("use_access_token") {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"success": false,
|
||||
"message": "This operation requires dashboard session authentication. API access token is not allowed.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req PaymentComplianceRequest
|
||||
if err := common.DecodeJson(c.Request.Body, &req); err != nil {
|
||||
common.ApiErrorMsg(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if !req.Confirmed {
|
||||
common.ApiErrorMsg(c, "请确认合规声明")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
userId := c.GetInt("id")
|
||||
clientIP := c.ClientIP()
|
||||
|
||||
updates := map[string]string{
|
||||
"payment_setting.compliance_confirmed": "true",
|
||||
"payment_setting.compliance_terms_version": operation_setting.CurrentComplianceTermsVersion,
|
||||
"payment_setting.compliance_confirmed_at": strconv.FormatInt(now, 10),
|
||||
"payment_setting.compliance_confirmed_by": strconv.Itoa(userId),
|
||||
"payment_setting.compliance_confirmed_ip": clientIP,
|
||||
}
|
||||
|
||||
for key, value := range updates {
|
||||
if err := model.UpdateOption(key, value); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInfo(c.Request.Context(), fmt.Sprintf(
|
||||
"payment compliance confirmed user_id=%d ip=%s terms_version=%s confirmed_at=%d",
|
||||
userId,
|
||||
clientIP,
|
||||
operation_setting.CurrentComplianceTermsVersion,
|
||||
now,
|
||||
))
|
||||
|
||||
common.ApiSuccess(c, gin.H{
|
||||
"confirmed": true,
|
||||
"terms_version": operation_setting.CurrentComplianceTermsVersion,
|
||||
"confirmed_at": now,
|
||||
"confirmed_by": userId,
|
||||
})
|
||||
}
|
||||
@ -7,7 +7,14 @@ import (
|
||||
"github.com/QuantumNous/new-api/setting/operation_setting"
|
||||
)
|
||||
|
||||
func isPaymentComplianceConfirmed() bool {
|
||||
return operation_setting.IsPaymentComplianceConfirmed()
|
||||
}
|
||||
|
||||
func isStripeTopUpEnabled() bool {
|
||||
if !isPaymentComplianceConfirmed() {
|
||||
return false
|
||||
}
|
||||
return strings.TrimSpace(setting.StripeApiSecret) != "" &&
|
||||
strings.TrimSpace(setting.StripeWebhookSecret) != "" &&
|
||||
strings.TrimSpace(setting.StripePriceId) != ""
|
||||
@ -22,6 +29,9 @@ func isStripeWebhookEnabled() bool {
|
||||
}
|
||||
|
||||
func isCreemTopUpEnabled() bool {
|
||||
if !isPaymentComplianceConfirmed() {
|
||||
return false
|
||||
}
|
||||
products := strings.TrimSpace(setting.CreemProducts)
|
||||
return strings.TrimSpace(setting.CreemApiKey) != "" &&
|
||||
products != "" &&
|
||||
@ -37,6 +47,9 @@ func isCreemWebhookEnabled() bool {
|
||||
}
|
||||
|
||||
func isWaffoTopUpEnabled() bool {
|
||||
if !isPaymentComplianceConfirmed() {
|
||||
return false
|
||||
}
|
||||
if !setting.WaffoEnabled {
|
||||
return false
|
||||
}
|
||||
@ -61,6 +74,9 @@ func isWaffoWebhookEnabled() bool {
|
||||
}
|
||||
|
||||
func isWaffoPancakeTopUpEnabled() bool {
|
||||
if !isPaymentComplianceConfirmed() {
|
||||
return false
|
||||
}
|
||||
if !setting.WaffoPancakeEnabled {
|
||||
return false
|
||||
}
|
||||
@ -86,6 +102,9 @@ func isWaffoPancakeWebhookEnabled() bool {
|
||||
}
|
||||
|
||||
func isEpayTopUpEnabled() bool {
|
||||
if !isPaymentComplianceConfirmed() {
|
||||
return false
|
||||
}
|
||||
return isEpayWebhookConfigured() && len(operation_setting.PayMethods) > 0
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,21 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func confirmPaymentComplianceForTest(t *testing.T) {
|
||||
t.Helper()
|
||||
paymentSetting := operation_setting.GetPaymentSetting()
|
||||
originalConfirmed := paymentSetting.ComplianceConfirmed
|
||||
originalTermsVersion := paymentSetting.ComplianceTermsVersion
|
||||
t.Cleanup(func() {
|
||||
paymentSetting.ComplianceConfirmed = originalConfirmed
|
||||
paymentSetting.ComplianceTermsVersion = originalTermsVersion
|
||||
})
|
||||
paymentSetting.ComplianceConfirmed = true
|
||||
paymentSetting.ComplianceTermsVersion = operation_setting.CurrentComplianceTermsVersion
|
||||
}
|
||||
|
||||
func TestStripeWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
|
||||
confirmPaymentComplianceForTest(t)
|
||||
originalAPISecret := setting.StripeApiSecret
|
||||
originalWebhookSecret := setting.StripeWebhookSecret
|
||||
originalPriceID := setting.StripePriceId
|
||||
@ -31,6 +45,7 @@ func TestStripeWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreemWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
|
||||
confirmPaymentComplianceForTest(t)
|
||||
originalAPIKey := setting.CreemApiKey
|
||||
originalProducts := setting.CreemProducts
|
||||
originalWebhookSecret := setting.CreemWebhookSecret
|
||||
@ -53,6 +68,7 @@ func TestCreemWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWaffoWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
|
||||
confirmPaymentComplianceForTest(t)
|
||||
originalEnabled := setting.WaffoEnabled
|
||||
originalSandbox := setting.WaffoSandbox
|
||||
originalAPIKey := setting.WaffoApiKey
|
||||
@ -97,6 +113,7 @@ func TestWaffoWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWaffoPancakeWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
|
||||
confirmPaymentComplianceForTest(t)
|
||||
originalEnabled := setting.WaffoPancakeEnabled
|
||||
originalSandbox := setting.WaffoPancakeSandbox
|
||||
originalMerchantID := setting.WaffoPancakeMerchantID
|
||||
@ -141,6 +158,7 @@ func TestWaffoPancakeWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEpayWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
|
||||
confirmPaymentComplianceForTest(t)
|
||||
originalPayAddress := operation_setting.PayAddress
|
||||
originalEpayID := operation_setting.EpayId
|
||||
originalEpayKey := operation_setting.EpayKey
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/i18n"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
"github.com/QuantumNous/new-api/setting/operation_setting"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@ -59,6 +60,11 @@ func GetRedemption(c *gin.Context) {
|
||||
}
|
||||
|
||||
func AddRedemption(c *gin.Context) {
|
||||
if !operation_setting.IsPaymentComplianceConfirmed() {
|
||||
common.ApiErrorI18n(c, i18n.MsgPaymentComplianceRequired)
|
||||
return
|
||||
}
|
||||
|
||||
redemption := model.Redemption{}
|
||||
err := c.ShouldBindJSON(&redemption)
|
||||
if err != nil {
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
"github.com/QuantumNous/new-api/setting/operation_setting"
|
||||
"github.com/QuantumNous/new-api/setting/ratio_setting"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
@ -24,6 +25,11 @@ type BillingPreferenceRequest struct {
|
||||
// ---- User APIs ----
|
||||
|
||||
func GetSubscriptionPlans(c *gin.Context) {
|
||||
if !operation_setting.IsPaymentComplianceConfirmed() {
|
||||
common.ApiSuccess(c, []SubscriptionPlanDTO{})
|
||||
return
|
||||
}
|
||||
|
||||
var plans []model.SubscriptionPlan
|
||||
if err := model.DB.Where("enabled = ?", true).Order("sort_order desc, id desc").Find(&plans).Error; err != nil {
|
||||
common.ApiError(c, err)
|
||||
@ -108,6 +114,10 @@ type AdminUpsertSubscriptionPlanRequest struct {
|
||||
}
|
||||
|
||||
func AdminCreateSubscriptionPlan(c *gin.Context) {
|
||||
if !requirePaymentCompliance(c) {
|
||||
return
|
||||
}
|
||||
|
||||
var req AdminUpsertSubscriptionPlanRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
common.ApiErrorMsg(c, "参数错误")
|
||||
@ -166,6 +176,10 @@ func AdminCreateSubscriptionPlan(c *gin.Context) {
|
||||
}
|
||||
|
||||
func AdminUpdateSubscriptionPlan(c *gin.Context) {
|
||||
if !requirePaymentCompliance(c) {
|
||||
return
|
||||
}
|
||||
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
if id <= 0 {
|
||||
common.ApiErrorMsg(c, "无效的ID")
|
||||
@ -259,6 +273,10 @@ type AdminUpdateSubscriptionPlanStatusRequest struct {
|
||||
}
|
||||
|
||||
func AdminUpdateSubscriptionPlanStatus(c *gin.Context) {
|
||||
if !requirePaymentCompliance(c) {
|
||||
return
|
||||
}
|
||||
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
if id <= 0 {
|
||||
common.ApiErrorMsg(c, "无效的ID")
|
||||
@ -283,6 +301,10 @@ type AdminBindSubscriptionRequest struct {
|
||||
}
|
||||
|
||||
func AdminBindSubscription(c *gin.Context) {
|
||||
if !requirePaymentCompliance(c) {
|
||||
return
|
||||
}
|
||||
|
||||
var req AdminBindSubscriptionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil || req.UserId <= 0 || req.PlanId <= 0 {
|
||||
common.ApiErrorMsg(c, "参数错误")
|
||||
@ -322,6 +344,10 @@ type AdminCreateUserSubscriptionRequest struct {
|
||||
|
||||
// AdminCreateUserSubscription creates a new user subscription from a plan (no payment).
|
||||
func AdminCreateUserSubscription(c *gin.Context) {
|
||||
if !requirePaymentCompliance(c) {
|
||||
return
|
||||
}
|
||||
|
||||
userId, _ := strconv.Atoi(c.Param("id"))
|
||||
if userId <= 0 {
|
||||
common.ApiErrorMsg(c, "无效的用户ID")
|
||||
|
||||
@ -21,6 +21,10 @@ type SubscriptionCreemPayRequest struct {
|
||||
}
|
||||
|
||||
func SubscriptionRequestCreemPay(c *gin.Context) {
|
||||
if !requirePaymentCompliance(c) {
|
||||
return
|
||||
}
|
||||
|
||||
var req SubscriptionCreemPayRequest
|
||||
|
||||
// Keep body for debugging consistency (like RequestCreemPay)
|
||||
|
||||
@ -22,6 +22,10 @@ type SubscriptionEpayPayRequest struct {
|
||||
}
|
||||
|
||||
func SubscriptionRequestEpay(c *gin.Context) {
|
||||
if !requirePaymentCompliance(c) {
|
||||
return
|
||||
}
|
||||
|
||||
var req SubscriptionEpayPayRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil || req.PlanId <= 0 {
|
||||
common.ApiErrorMsg(c, "参数错误")
|
||||
|
||||
@ -21,6 +21,10 @@ type SubscriptionStripePayRequest struct {
|
||||
}
|
||||
|
||||
func SubscriptionRequestStripePay(c *gin.Context) {
|
||||
if !requirePaymentCompliance(c) {
|
||||
return
|
||||
}
|
||||
|
||||
var req SubscriptionStripePayRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil || req.PlanId <= 0 {
|
||||
common.ApiErrorMsg(c, "参数错误")
|
||||
|
||||
@ -22,8 +22,13 @@ import (
|
||||
)
|
||||
|
||||
func GetTopUpInfo(c *gin.Context) {
|
||||
complianceConfirmed := operation_setting.IsPaymentComplianceConfirmed()
|
||||
|
||||
// 获取支付方式
|
||||
payMethods := operation_setting.PayMethods
|
||||
if !complianceConfirmed {
|
||||
payMethods = []map[string]string{}
|
||||
}
|
||||
|
||||
// 如果启用了 Stripe 支付,添加到支付方法列表
|
||||
if isStripeTopUpEnabled() {
|
||||
@ -90,11 +95,14 @@ func GetTopUpInfo(c *gin.Context) {
|
||||
}
|
||||
|
||||
data := gin.H{
|
||||
"enable_online_topup": isEpayTopUpEnabled(),
|
||||
"enable_stripe_topup": isStripeTopUpEnabled(),
|
||||
"enable_creem_topup": isCreemTopUpEnabled(),
|
||||
"enable_waffo_topup": enableWaffo,
|
||||
"enable_waffo_pancake_topup": enableWaffoPancake,
|
||||
"enable_online_topup": isEpayTopUpEnabled(),
|
||||
"enable_stripe_topup": isStripeTopUpEnabled(),
|
||||
"enable_creem_topup": isCreemTopUpEnabled(),
|
||||
"enable_waffo_topup": enableWaffo,
|
||||
"enable_waffo_pancake_topup": enableWaffoPancake,
|
||||
"enable_redemption": complianceConfirmed,
|
||||
"payment_compliance_confirmed": complianceConfirmed,
|
||||
"payment_compliance_terms_version": operation_setting.CurrentComplianceTermsVersion,
|
||||
"waffo_pay_methods": func() interface{} {
|
||||
if enableWaffo {
|
||||
return setting.GetWaffoPayMethods()
|
||||
|
||||
@ -17,6 +17,7 @@ import (
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
"github.com/QuantumNous/new-api/service"
|
||||
"github.com/QuantumNous/new-api/setting"
|
||||
"github.com/QuantumNous/new-api/setting/operation_setting"
|
||||
|
||||
"github.com/QuantumNous/new-api/constant"
|
||||
|
||||
@ -327,6 +328,10 @@ type TransferAffQuotaRequest struct {
|
||||
}
|
||||
|
||||
func TransferAffQuota(c *gin.Context) {
|
||||
if !requirePaymentCompliance(c) {
|
||||
return
|
||||
}
|
||||
|
||||
id := c.GetInt("id")
|
||||
user, err := model.GetUserById(id, true)
|
||||
if err != nil {
|
||||
@ -1081,6 +1086,11 @@ func getTopUpLock(userID int) *topUpTryLock {
|
||||
}
|
||||
|
||||
func TopUp(c *gin.Context) {
|
||||
if !operation_setting.IsPaymentComplianceConfirmed() {
|
||||
common.ApiErrorI18n(c, i18n.MsgPaymentComplianceRequired)
|
||||
return
|
||||
}
|
||||
|
||||
id := c.GetInt("id")
|
||||
lock := getTopUpLock(id)
|
||||
if !lock.TryLock() {
|
||||
|
||||
21
i18n/keys.go
21
i18n/keys.go
@ -142,16 +142,17 @@ const (
|
||||
|
||||
// Payment related messages
|
||||
const (
|
||||
MsgPaymentNotConfigured = "payment.not_configured"
|
||||
MsgPaymentMethodNotExists = "payment.method_not_exists"
|
||||
MsgPaymentCallbackError = "payment.callback_error"
|
||||
MsgPaymentCreateFailed = "payment.create_failed"
|
||||
MsgPaymentStartFailed = "payment.start_failed"
|
||||
MsgPaymentAmountTooLow = "payment.amount_too_low"
|
||||
MsgPaymentStripeNotConfig = "payment.stripe_not_configured"
|
||||
MsgPaymentWebhookNotConfig = "payment.webhook_not_configured"
|
||||
MsgPaymentPriceIdNotConfig = "payment.price_id_not_configured"
|
||||
MsgPaymentCreemNotConfig = "payment.creem_not_configured"
|
||||
MsgPaymentNotConfigured = "payment.not_configured"
|
||||
MsgPaymentMethodNotExists = "payment.method_not_exists"
|
||||
MsgPaymentCallbackError = "payment.callback_error"
|
||||
MsgPaymentCreateFailed = "payment.create_failed"
|
||||
MsgPaymentStartFailed = "payment.start_failed"
|
||||
MsgPaymentAmountTooLow = "payment.amount_too_low"
|
||||
MsgPaymentStripeNotConfig = "payment.stripe_not_configured"
|
||||
MsgPaymentWebhookNotConfig = "payment.webhook_not_configured"
|
||||
MsgPaymentPriceIdNotConfig = "payment.price_id_not_configured"
|
||||
MsgPaymentCreemNotConfig = "payment.creem_not_configured"
|
||||
MsgPaymentComplianceRequired = "payment.compliance_required"
|
||||
)
|
||||
|
||||
// Topup related messages
|
||||
|
||||
@ -134,6 +134,7 @@ payment.stripe_not_configured: "Stripe is not configured or key is invalid"
|
||||
payment.webhook_not_configured: "Webhook is not configured"
|
||||
payment.price_id_not_configured: "StripePriceId is not configured for this plan"
|
||||
payment.creem_not_configured: "CreemProductId is not configured for this plan"
|
||||
payment.compliance_required: "Payment, redemption, subscription, and invitation reward features are disabled. The administrator must confirm compliance terms before enabling them."
|
||||
|
||||
# Topup messages
|
||||
topup.not_provided: "Payment order number not provided"
|
||||
|
||||
@ -135,6 +135,7 @@ payment.stripe_not_configured: "Stripe 未配置或密钥无效"
|
||||
payment.webhook_not_configured: "Webhook 未配置"
|
||||
payment.price_id_not_configured: "该套餐未配置 StripePriceId"
|
||||
payment.creem_not_configured: "该套餐未配置 CreemProductId"
|
||||
payment.compliance_required: "支付、兑换码、订阅计划和邀请返利功能已禁用。管理员需先确认合规声明后方可启用。"
|
||||
|
||||
# Topup messages
|
||||
topup.not_provided: "未提供支付单号"
|
||||
|
||||
@ -135,6 +135,7 @@ payment.stripe_not_configured: "Stripe 未設定或密鑰無效"
|
||||
payment.webhook_not_configured: "Webhook 未設定"
|
||||
payment.price_id_not_configured: "該訂閱方案未設定 StripePriceId"
|
||||
payment.creem_not_configured: "該訂閱方案未設定 CreemProductId"
|
||||
payment.compliance_required: "支付、兌換碼、訂閱方案和邀請返利功能已停用。管理員需先確認合規聲明後方可啟用。"
|
||||
|
||||
# Topup messages
|
||||
topup.not_provided: "未提供支付單號"
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/dto"
|
||||
"github.com/QuantumNous/new-api/logger"
|
||||
"github.com/QuantumNous/new-api/setting/operation_setting"
|
||||
|
||||
"github.com/bytedance/gopkg/util/gopool"
|
||||
"gorm.io/gorm"
|
||||
@ -420,7 +421,7 @@ func (user *User) Insert(inviterId int) error {
|
||||
if common.QuotaForNewUser > 0 {
|
||||
RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("新用户注册赠送 %s", logger.LogQuota(common.QuotaForNewUser)))
|
||||
}
|
||||
if inviterId != 0 {
|
||||
if inviterId != 0 && operation_setting.IsPaymentComplianceConfirmed() {
|
||||
if common.QuotaForInvitee > 0 {
|
||||
_ = IncreaseUserQuota(user.Id, common.QuotaForInvitee, true)
|
||||
RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("使用邀请码赠送 %s", logger.LogQuota(common.QuotaForInvitee)))
|
||||
@ -481,7 +482,7 @@ func (user *User) FinalizeOAuthUserCreation(inviterId int) {
|
||||
if common.QuotaForNewUser > 0 {
|
||||
RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("新用户注册赠送 %s", logger.LogQuota(common.QuotaForNewUser)))
|
||||
}
|
||||
if inviterId != 0 {
|
||||
if inviterId != 0 && operation_setting.IsPaymentComplianceConfirmed() {
|
||||
if common.QuotaForInvitee > 0 {
|
||||
_ = IncreaseUserQuota(user.Id, common.QuotaForInvitee, true)
|
||||
RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("使用邀请码赠送 %s", logger.LogQuota(common.QuotaForInvitee)))
|
||||
|
||||
@ -181,6 +181,7 @@ func SetApiRouter(router *gin.Engine) {
|
||||
{
|
||||
optionRoute.GET("/", controller.GetOptions)
|
||||
optionRoute.PUT("/", controller.UpdateOption)
|
||||
optionRoute.POST("/payment_compliance", controller.ConfirmPaymentCompliance)
|
||||
optionRoute.GET("/channel_affinity_cache", controller.GetChannelAffinityCacheStats)
|
||||
optionRoute.DELETE("/channel_affinity_cache", controller.ClearChannelAffinityCache)
|
||||
optionRoute.POST("/rest_model_ratio", controller.ResetModelRatio)
|
||||
|
||||
@ -5,8 +5,16 @@ import "github.com/QuantumNous/new-api/setting/config"
|
||||
type PaymentSetting struct {
|
||||
AmountOptions []int `json:"amount_options"`
|
||||
AmountDiscount map[int]float64 `json:"amount_discount"` // 充值金额对应的折扣,例如 100 元 0.9 表示 100 元充值享受 9 折优惠
|
||||
|
||||
ComplianceConfirmed bool `json:"compliance_confirmed"`
|
||||
ComplianceTermsVersion string `json:"compliance_terms_version"`
|
||||
ComplianceConfirmedAt int64 `json:"compliance_confirmed_at"`
|
||||
ComplianceConfirmedBy int `json:"compliance_confirmed_by"`
|
||||
ComplianceConfirmedIP string `json:"compliance_confirmed_ip"`
|
||||
}
|
||||
|
||||
const CurrentComplianceTermsVersion = "v1"
|
||||
|
||||
// 默认配置
|
||||
var paymentSetting = PaymentSetting{
|
||||
AmountOptions: []int{10, 20, 50, 100, 200, 500},
|
||||
@ -21,3 +29,8 @@ func init() {
|
||||
func GetPaymentSetting() *PaymentSetting {
|
||||
return &paymentSetting
|
||||
}
|
||||
|
||||
func IsPaymentComplianceConfirmed() bool {
|
||||
return paymentSetting.ComplianceConfirmed &&
|
||||
paymentSetting.ComplianceTermsVersion == CurrentComplianceTermsVersion
|
||||
}
|
||||
|
||||
@ -62,6 +62,7 @@ const RiskAcknowledgementModal = React.memo(function RiskAcknowledgementModal({
|
||||
checklist = [],
|
||||
inputPrompt = '',
|
||||
requiredText = '',
|
||||
requiredTextParts = [],
|
||||
inputPlaceholder = '',
|
||||
mismatchText = '',
|
||||
cancelText = '',
|
||||
@ -72,24 +73,68 @@ const RiskAcknowledgementModal = React.memo(function RiskAcknowledgementModal({
|
||||
const isMobile = useIsMobile();
|
||||
const [checkedItems, setCheckedItems] = useState([]);
|
||||
const [typedText, setTypedText] = useState('');
|
||||
const [typedTextParts, setTypedTextParts] = useState([]);
|
||||
|
||||
const normalizedRequiredTextParts = useMemo(() => {
|
||||
let inputIndex = 0;
|
||||
return requiredTextParts.map((part) => {
|
||||
if (part.type === 'input') {
|
||||
const normalizedPart = { ...part, inputIndex };
|
||||
inputIndex += 1;
|
||||
return normalizedPart;
|
||||
}
|
||||
return part;
|
||||
});
|
||||
}, [requiredTextParts]);
|
||||
|
||||
const requiredTextInputCount = useMemo(
|
||||
() =>
|
||||
normalizedRequiredTextParts.filter((part) => part.type === 'input')
|
||||
.length,
|
||||
[normalizedRequiredTextParts],
|
||||
);
|
||||
const hasSegmentedRequiredText = requiredTextInputCount > 0;
|
||||
const requiredTextToDisplay = hasSegmentedRequiredText
|
||||
? normalizedRequiredTextParts.map((part) => part.text).join('')
|
||||
: requiredText;
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) return;
|
||||
setCheckedItems(Array(checklist.length).fill(false));
|
||||
setTypedText('');
|
||||
}, [visible, checklist.length]);
|
||||
setTypedTextParts(Array(requiredTextInputCount).fill(''));
|
||||
}, [visible, checklist.length, requiredTextInputCount]);
|
||||
|
||||
const allChecked = useMemo(() => {
|
||||
if (checklist.length === 0) return true;
|
||||
return checkedItems.length === checklist.length && checkedItems.every(Boolean);
|
||||
return (
|
||||
checkedItems.length === checklist.length && checkedItems.every(Boolean)
|
||||
);
|
||||
}, [checkedItems, checklist.length]);
|
||||
|
||||
const typedMatched = useMemo(() => {
|
||||
if (hasSegmentedRequiredText) {
|
||||
return normalizedRequiredTextParts.every((part) => {
|
||||
if (part.type === 'static') return true;
|
||||
return (
|
||||
typedTextParts[part.inputIndex ?? 0]?.trim() === part.text.trim()
|
||||
);
|
||||
});
|
||||
}
|
||||
if (!requiredText) return true;
|
||||
return typedText.trim() === requiredText.trim();
|
||||
}, [typedText, requiredText]);
|
||||
}, [
|
||||
hasSegmentedRequiredText,
|
||||
normalizedRequiredTextParts,
|
||||
requiredText,
|
||||
typedText,
|
||||
typedTextParts,
|
||||
]);
|
||||
|
||||
const detailText = useMemo(() => detailItems.join(', '), [detailItems]);
|
||||
const hasTypedRequiredText = hasSegmentedRequiredText
|
||||
? typedTextParts.some((part) => part.trim() !== '')
|
||||
: typedText.length > 0;
|
||||
const canConfirm = allChecked && typedMatched;
|
||||
|
||||
const handleChecklistChange = useCallback((index, checked) => {
|
||||
@ -100,6 +145,14 @@ const RiskAcknowledgementModal = React.memo(function RiskAcknowledgementModal({
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleTextPartChange = useCallback((index, value) => {
|
||||
setTypedTextParts((previous) => {
|
||||
const next = [...previous];
|
||||
next[index] = value;
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
@ -134,7 +187,6 @@ const RiskAcknowledgementModal = React.memo(function RiskAcknowledgementModal({
|
||||
}
|
||||
>
|
||||
<div className='flex flex-col gap-4'>
|
||||
|
||||
<RiskMarkdownBlock markdownContent={markdownContent} />
|
||||
|
||||
{detailItems.length > 0 ? (
|
||||
@ -176,7 +228,7 @@ const RiskAcknowledgementModal = React.memo(function RiskAcknowledgementModal({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{requiredText ? (
|
||||
{requiredTextToDisplay ? (
|
||||
<div
|
||||
className='flex flex-col gap-2 rounded-lg'
|
||||
style={{
|
||||
@ -187,19 +239,51 @@ const RiskAcknowledgementModal = React.memo(function RiskAcknowledgementModal({
|
||||
>
|
||||
{inputPrompt ? <Text strong>{inputPrompt}</Text> : null}
|
||||
<div className='font-mono text-xs break-all rounded-md p-2 bg-gray-50 border border-gray-200'>
|
||||
{requiredText}
|
||||
{requiredTextToDisplay}
|
||||
</div>
|
||||
<Input
|
||||
value={typedText}
|
||||
onChange={setTypedText}
|
||||
placeholder={inputPlaceholder}
|
||||
autoFocus={visible}
|
||||
onCopy={(event) => event.preventDefault()}
|
||||
onCut={(event) => event.preventDefault()}
|
||||
onPaste={(event) => event.preventDefault()}
|
||||
onDrop={(event) => event.preventDefault()}
|
||||
/>
|
||||
{!typedMatched && typedText ? (
|
||||
{hasSegmentedRequiredText ? (
|
||||
<div className='flex flex-wrap items-center gap-2'>
|
||||
{normalizedRequiredTextParts.map((part, index) =>
|
||||
part.type === 'static' ? (
|
||||
<span
|
||||
key={`static-${index}`}
|
||||
className='select-none rounded-md border border-gray-200 bg-white px-2 py-1 font-mono text-sm text-gray-500'
|
||||
>
|
||||
{part.text}
|
||||
</span>
|
||||
) : (
|
||||
<Input
|
||||
key={`input-${index}`}
|
||||
value={typedTextParts[part.inputIndex ?? 0] ?? ''}
|
||||
onChange={(value) =>
|
||||
handleTextPartChange(part.inputIndex ?? 0, value)
|
||||
}
|
||||
placeholder={
|
||||
part.placeholder || part.text || inputPlaceholder
|
||||
}
|
||||
autoFocus={visible && part.inputIndex === 0}
|
||||
onCopy={(event) => event.preventDefault()}
|
||||
onCut={(event) => event.preventDefault()}
|
||||
onPaste={(event) => event.preventDefault()}
|
||||
onDrop={(event) => event.preventDefault()}
|
||||
style={{ width: isMobile ? '100%' : 260 }}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Input
|
||||
value={typedText}
|
||||
onChange={setTypedText}
|
||||
placeholder={inputPlaceholder}
|
||||
autoFocus={visible}
|
||||
onCopy={(event) => event.preventDefault()}
|
||||
onCut={(event) => event.preventDefault()}
|
||||
onPaste={(event) => event.preventDefault()}
|
||||
onDrop={(event) => event.preventDefault()}
|
||||
/>
|
||||
)}
|
||||
{!typedMatched && hasTypedRequiredText ? (
|
||||
<Text type='danger' size='small'>
|
||||
{mismatchText}
|
||||
</Text>
|
||||
|
||||
@ -18,15 +18,18 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Card, Spin, Tabs } from '@douyinfe/semi-ui';
|
||||
import { Banner, Button, Card, Spin, Tabs } from '@douyinfe/semi-ui';
|
||||
import SettingsGeneralPayment from '../../pages/Setting/Payment/SettingsGeneralPayment';
|
||||
import SettingsPaymentGateway from '../../pages/Setting/Payment/SettingsPaymentGateway';
|
||||
import SettingsPaymentGatewayStripe from '../../pages/Setting/Payment/SettingsPaymentGatewayStripe';
|
||||
import SettingsPaymentGatewayCreem from '../../pages/Setting/Payment/SettingsPaymentGatewayCreem';
|
||||
import SettingsPaymentGatewayWaffo from '../../pages/Setting/Payment/SettingsPaymentGatewayWaffo';
|
||||
import SettingsPaymentGatewayWaffoPancake from '../../pages/Setting/Payment/SettingsPaymentGatewayWaffoPancake';
|
||||
import { API, showError, toBoolean } from '../../helpers';
|
||||
import { API, showError, showSuccess, toBoolean } from '../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import RiskAcknowledgementModal from '../common/modals/RiskAcknowledgementModal';
|
||||
|
||||
const CURRENT_COMPLIANCE_TERMS_VERSION = 'v1';
|
||||
|
||||
const PaymentSetting = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -60,9 +63,59 @@ const PaymentSetting = () => {
|
||||
WaffoPancakeCurrency: 'USD',
|
||||
WaffoPancakeUnitPrice: 1.0,
|
||||
WaffoPancakeMinTopUp: 1,
|
||||
'payment_setting.compliance_confirmed': false,
|
||||
'payment_setting.compliance_terms_version': '',
|
||||
'payment_setting.compliance_confirmed_at': 0,
|
||||
'payment_setting.compliance_confirmed_by': 0,
|
||||
});
|
||||
|
||||
let [loading, setLoading] = useState(false);
|
||||
const [complianceVisible, setComplianceVisible] = useState(false);
|
||||
|
||||
const complianceStatements = [
|
||||
t('你已合法取得所接入模型 API、账号、密钥和额度的授权;'),
|
||||
t(
|
||||
'你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。',
|
||||
),
|
||||
t(
|
||||
'如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;',
|
||||
),
|
||||
t(
|
||||
'你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。',
|
||||
),
|
||||
t('你理解并自行承担部署、运营和收费行为产生的法律责任。'),
|
||||
t(
|
||||
'你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。',
|
||||
),
|
||||
];
|
||||
const requiredComplianceText = t(
|
||||
'我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任',
|
||||
);
|
||||
const requiredComplianceTextParts = [
|
||||
{
|
||||
type: 'input',
|
||||
text: t('我已阅读并理解上述合规提醒'),
|
||||
},
|
||||
{ type: 'static', text: t(',') },
|
||||
{
|
||||
type: 'input',
|
||||
text: t('知悉相关法律风险'),
|
||||
},
|
||||
{ type: 'static', text: t(',') },
|
||||
{
|
||||
type: 'input',
|
||||
text: t('并确认自行承担部署'),
|
||||
},
|
||||
{ type: 'static', text: t('、') },
|
||||
{
|
||||
type: 'input',
|
||||
text: t('运营和收费行为产生的法律责任'),
|
||||
},
|
||||
];
|
||||
const complianceConfirmed =
|
||||
inputs['payment_setting.compliance_confirmed'] &&
|
||||
inputs['payment_setting.compliance_terms_version'] ===
|
||||
CURRENT_COMPLIANCE_TERMS_VERSION;
|
||||
|
||||
const getOptions = async () => {
|
||||
const res = await API.get('/api/option/');
|
||||
@ -104,6 +157,16 @@ const PaymentSetting = () => {
|
||||
newInputs['AmountDiscount'] = item.value;
|
||||
}
|
||||
break;
|
||||
case 'payment_setting.compliance_confirmed':
|
||||
newInputs[item.key] = toBoolean(item.value);
|
||||
break;
|
||||
case 'payment_setting.compliance_confirmed_at':
|
||||
case 'payment_setting.compliance_confirmed_by':
|
||||
newInputs[item.key] = parseInt(item.value) || 0;
|
||||
break;
|
||||
case 'payment_setting.compliance_terms_version':
|
||||
newInputs[item.key] = item.value;
|
||||
break;
|
||||
case 'Price':
|
||||
case 'MinTopUp':
|
||||
case 'StripeUnitPrice':
|
||||
@ -154,59 +217,143 @@ const PaymentSetting = () => {
|
||||
onRefresh();
|
||||
}, []);
|
||||
|
||||
const confirmCompliance = async () => {
|
||||
try {
|
||||
const res = await API.post('/api/option/payment_compliance', {
|
||||
confirmed: true,
|
||||
});
|
||||
if (res.data.success) {
|
||||
showSuccess(t('合规声明确认成功'));
|
||||
setComplianceVisible(false);
|
||||
await onRefresh();
|
||||
} else {
|
||||
showError(res.data.message || t('确认失败'));
|
||||
}
|
||||
} catch (error) {
|
||||
showError(t('确认失败'));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Spin spinning={loading} size='large'>
|
||||
<Card style={{ marginTop: '10px' }}>
|
||||
<Tabs
|
||||
type='card'
|
||||
defaultActiveKey='general'
|
||||
contentStyle={{ paddingTop: 24 }}
|
||||
{!complianceConfirmed ? (
|
||||
<Banner
|
||||
type='warning'
|
||||
title={t('需要确认合规声明')}
|
||||
description={
|
||||
<div className='flex flex-col gap-2'>
|
||||
<span>
|
||||
{t(
|
||||
'确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。',
|
||||
)}
|
||||
</span>
|
||||
<Button
|
||||
type='warning'
|
||||
theme='solid'
|
||||
onClick={() => setComplianceVisible(true)}
|
||||
>
|
||||
{t('确认合规声明')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
closeIcon={null}
|
||||
style={{ marginBottom: 16 }}
|
||||
fullMode={false}
|
||||
/>
|
||||
) : (
|
||||
<Banner
|
||||
type='success'
|
||||
title={t('合规声明已确认')}
|
||||
description={t('确认时间:{{time}},确认用户:#{{userId}}', {
|
||||
time: inputs['payment_setting.compliance_confirmed_at']
|
||||
? new Date(
|
||||
inputs['payment_setting.compliance_confirmed_at'] * 1000,
|
||||
).toLocaleString()
|
||||
: '-',
|
||||
userId:
|
||||
inputs['payment_setting.compliance_confirmed_by'] || '-',
|
||||
})}
|
||||
closeIcon={null}
|
||||
style={{ marginBottom: 16 }}
|
||||
fullMode={false}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
style={
|
||||
complianceConfirmed
|
||||
? undefined
|
||||
: { opacity: 0.4, pointerEvents: 'none' }
|
||||
}
|
||||
>
|
||||
<Tabs.TabPane tab={t('通用设置')} itemKey='general'>
|
||||
<SettingsGeneralPayment
|
||||
options={inputs}
|
||||
refresh={onRefresh}
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t('易支付设置')} itemKey='epay'>
|
||||
<SettingsPaymentGateway
|
||||
options={inputs}
|
||||
refresh={onRefresh}
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t('Stripe 设置')} itemKey='stripe'>
|
||||
<SettingsPaymentGatewayStripe
|
||||
options={inputs}
|
||||
refresh={onRefresh}
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t('Creem 设置')} itemKey='creem'>
|
||||
<SettingsPaymentGatewayCreem
|
||||
options={inputs}
|
||||
refresh={onRefresh}
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t('Waffo 设置')} itemKey='waffo'>
|
||||
<SettingsPaymentGatewayWaffo
|
||||
options={inputs}
|
||||
refresh={onRefresh}
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
{/*<Tabs.TabPane tab={t('Waffo Pancake 设置')} itemKey='waffo-pancake'>*/}
|
||||
{/* <SettingsPaymentGatewayWaffoPancake*/}
|
||||
{/* options={inputs}*/}
|
||||
{/* refresh={onRefresh}*/}
|
||||
{/* hideSectionTitle*/}
|
||||
{/* />*/}
|
||||
{/*</Tabs.TabPane>*/}
|
||||
</Tabs>
|
||||
<Tabs
|
||||
type='card'
|
||||
defaultActiveKey='general'
|
||||
contentStyle={{ paddingTop: 24 }}
|
||||
>
|
||||
<Tabs.TabPane tab={t('通用设置')} itemKey='general'>
|
||||
<SettingsGeneralPayment
|
||||
options={inputs}
|
||||
refresh={onRefresh}
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t('易支付设置')} itemKey='epay'>
|
||||
<SettingsPaymentGateway
|
||||
options={inputs}
|
||||
refresh={onRefresh}
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t('Stripe 设置')} itemKey='stripe'>
|
||||
<SettingsPaymentGatewayStripe
|
||||
options={inputs}
|
||||
refresh={onRefresh}
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t('Creem 设置')} itemKey='creem'>
|
||||
<SettingsPaymentGatewayCreem
|
||||
options={inputs}
|
||||
refresh={onRefresh}
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t('Waffo 设置')} itemKey='waffo'>
|
||||
<SettingsPaymentGatewayWaffo
|
||||
options={inputs}
|
||||
refresh={onRefresh}
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
{/*<Tabs.TabPane tab={t('Waffo Pancake 设置')} itemKey='waffo-pancake'>*/}
|
||||
{/* <SettingsPaymentGatewayWaffoPancake*/}
|
||||
{/* options={inputs}*/}
|
||||
{/* refresh={onRefresh}*/}
|
||||
{/* hideSectionTitle*/}
|
||||
{/* />*/}
|
||||
{/*</Tabs.TabPane>*/}
|
||||
</Tabs>
|
||||
</div>
|
||||
</Card>
|
||||
<RiskAcknowledgementModal
|
||||
visible={complianceVisible}
|
||||
title={t('确认合规声明')}
|
||||
markdownContent={t(
|
||||
'该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。',
|
||||
)}
|
||||
checklist={complianceStatements}
|
||||
inputPrompt={t('请输入以下文字以确认:')}
|
||||
requiredText={requiredComplianceText}
|
||||
requiredTextParts={requiredComplianceTextParts}
|
||||
inputPlaceholder={t('请输入确认文案')}
|
||||
mismatchText={t('输入内容与要求文案不一致')}
|
||||
cancelText={t('取消')}
|
||||
confirmText={t('确认并启用')}
|
||||
onCancel={() => setComplianceVisible(false)}
|
||||
onConfirm={confirmCompliance}
|
||||
/>
|
||||
</Spin>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -20,7 +20,7 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import React from 'react';
|
||||
import { Button } from '@douyinfe/semi-ui';
|
||||
|
||||
const SubscriptionsActions = ({ openCreate, t }) => {
|
||||
const SubscriptionsActions = ({ openCreate, t, disabled = false }) => {
|
||||
return (
|
||||
<div className='flex gap-2 w-full md:w-auto'>
|
||||
<Button
|
||||
@ -28,6 +28,7 @@ const SubscriptionsActions = ({ openCreate, t }) => {
|
||||
className='w-full md:w-auto'
|
||||
onClick={openCreate}
|
||||
size='small'
|
||||
disabled={disabled}
|
||||
>
|
||||
{t('新建套餐')}
|
||||
</Button>
|
||||
|
||||
@ -228,7 +228,11 @@ const renderPaymentConfig = (text, record, t, enableEpay) => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderOperations = (text, record, { openEdit, setPlanEnabled, t }) => {
|
||||
const renderOperations = (
|
||||
text,
|
||||
record,
|
||||
{ openEdit, setPlanEnabled, t, complianceConfirmed },
|
||||
) => {
|
||||
const isEnabled = record?.plan?.enabled;
|
||||
|
||||
const handleToggle = () => {
|
||||
@ -256,11 +260,18 @@ const renderOperations = (text, record, { openEdit, setPlanEnabled, t }) => {
|
||||
type='tertiary'
|
||||
size='small'
|
||||
onClick={() => openEdit(record)}
|
||||
disabled={!complianceConfirmed}
|
||||
>
|
||||
{t('编辑')}
|
||||
</Button>
|
||||
{isEnabled ? (
|
||||
<Button theme='light' type='danger' size='small' onClick={handleToggle}>
|
||||
<Button
|
||||
theme='light'
|
||||
type='danger'
|
||||
size='small'
|
||||
onClick={handleToggle}
|
||||
disabled={!complianceConfirmed}
|
||||
>
|
||||
{t('禁用')}
|
||||
</Button>
|
||||
) : (
|
||||
@ -269,6 +280,7 @@ const renderOperations = (text, record, { openEdit, setPlanEnabled, t }) => {
|
||||
type='primary'
|
||||
size='small'
|
||||
onClick={handleToggle}
|
||||
disabled={!complianceConfirmed}
|
||||
>
|
||||
{t('启用')}
|
||||
</Button>
|
||||
@ -282,6 +294,7 @@ export const getSubscriptionsColumns = ({
|
||||
openEdit,
|
||||
setPlanEnabled,
|
||||
enableEpay,
|
||||
complianceConfirmed = true,
|
||||
}) => {
|
||||
return [
|
||||
{
|
||||
@ -351,7 +364,12 @@ export const getSubscriptionsColumns = ({
|
||||
fixed: 'right',
|
||||
width: 160,
|
||||
render: (text, record) =>
|
||||
renderOperations(text, record, { openEdit, setPlanEnabled, t }),
|
||||
renderOperations(text, record, {
|
||||
openEdit,
|
||||
setPlanEnabled,
|
||||
t,
|
||||
complianceConfirmed,
|
||||
}),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@ -35,6 +35,7 @@ const SubscriptionsTable = (subscriptionsData) => {
|
||||
setPlanEnabled,
|
||||
t,
|
||||
enableEpay,
|
||||
complianceConfirmed,
|
||||
} = subscriptionsData;
|
||||
|
||||
const columns = useMemo(() => {
|
||||
@ -43,8 +44,9 @@ const SubscriptionsTable = (subscriptionsData) => {
|
||||
openEdit,
|
||||
setPlanEnabled,
|
||||
enableEpay,
|
||||
complianceConfirmed,
|
||||
});
|
||||
}, [t, openEdit, setPlanEnabled, enableEpay]);
|
||||
}, [t, openEdit, setPlanEnabled, enableEpay, complianceConfirmed]);
|
||||
|
||||
const tableColumns = useMemo(() => {
|
||||
return compactMode
|
||||
|
||||
@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Banner } from '@douyinfe/semi-ui';
|
||||
import CardPro from '../../common/ui/CardPro';
|
||||
import SubscriptionsTable from './SubscriptionsTable';
|
||||
@ -28,12 +28,14 @@ import { useSubscriptionsData } from '../../../hooks/subscriptions/useSubscripti
|
||||
import { useIsMobile } from '../../../hooks/common/useIsMobile';
|
||||
import { createCardProPagination } from '../../../helpers/utils';
|
||||
import { StatusContext } from '../../../context/Status';
|
||||
import { API } from '../../../helpers';
|
||||
|
||||
const SubscriptionsPage = () => {
|
||||
const subscriptionsData = useSubscriptionsData();
|
||||
const isMobile = useIsMobile();
|
||||
const [statusState] = useContext(StatusContext);
|
||||
const enableEpay = !!statusState?.status?.enable_online_topup;
|
||||
const [complianceConfirmed, setComplianceConfirmed] = useState(true);
|
||||
|
||||
const {
|
||||
showEdit,
|
||||
@ -47,6 +49,22 @@ const SubscriptionsPage = () => {
|
||||
t,
|
||||
} = subscriptionsData;
|
||||
|
||||
useEffect(() => {
|
||||
const loadComplianceStatus = async () => {
|
||||
try {
|
||||
const res = await API.get('/api/user/topup/info');
|
||||
if (res.data?.success) {
|
||||
setComplianceConfirmed(
|
||||
res.data.data?.payment_compliance_confirmed !== false,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// Keep the page usable if status loading fails; backend still enforces.
|
||||
}
|
||||
};
|
||||
loadComplianceStatus();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AddEditSubscriptionModal
|
||||
@ -71,7 +89,11 @@ const SubscriptionsPage = () => {
|
||||
<div className='flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full'>
|
||||
{/* Mobile: actions first; Desktop: actions left */}
|
||||
<div className='order-1 md:order-0 w-full md:w-auto'>
|
||||
<SubscriptionsActions openCreate={openCreate} t={t} />
|
||||
<SubscriptionsActions
|
||||
openCreate={openCreate}
|
||||
t={t}
|
||||
disabled={!complianceConfirmed}
|
||||
/>
|
||||
</div>
|
||||
<Banner
|
||||
type='info'
|
||||
@ -94,7 +116,21 @@ const SubscriptionsPage = () => {
|
||||
})}
|
||||
t={t}
|
||||
>
|
||||
<SubscriptionsTable {...subscriptionsData} enableEpay={enableEpay} />
|
||||
{!complianceConfirmed && (
|
||||
<Banner
|
||||
type='warning'
|
||||
description={t(
|
||||
'订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。',
|
||||
)}
|
||||
closeIcon={null}
|
||||
className='!rounded-lg mb-3'
|
||||
/>
|
||||
)}
|
||||
<SubscriptionsTable
|
||||
{...subscriptionsData}
|
||||
enableEpay={enableEpay}
|
||||
complianceConfirmed={complianceConfirmed}
|
||||
/>
|
||||
</CardPro>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -38,6 +38,7 @@ const InvitationCard = ({
|
||||
setOpenTransfer,
|
||||
affLink,
|
||||
handleAffLinkClick,
|
||||
complianceConfirmed = true,
|
||||
}) => {
|
||||
return (
|
||||
<Card className='!rounded-2xl shadow-sm border-0'>
|
||||
@ -81,6 +82,7 @@ const InvitationCard = ({
|
||||
theme='solid'
|
||||
size='small'
|
||||
disabled={
|
||||
!complianceConfirmed ||
|
||||
!userState?.user?.aff_quota ||
|
||||
userState?.user?.aff_quota <= 0
|
||||
}
|
||||
@ -91,6 +93,16 @@ const InvitationCard = ({
|
||||
{t('划转到余额')}
|
||||
</Button>
|
||||
</div>
|
||||
{!complianceConfirmed && (
|
||||
<Text
|
||||
style={{
|
||||
color: 'rgba(255,255,255,0.8)',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{t('邀请奖励划转已禁用,管理员需先确认合规声明。')}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* 统计数据 */}
|
||||
<div className='grid grid-cols-3 gap-6 mt-4'>
|
||||
|
||||
106
web/classic/src/components/topup/RechargeCard.jsx
vendored
106
web/classic/src/components/topup/RechargeCard.jsx
vendored
@ -96,6 +96,7 @@ const RechargeCard = ({
|
||||
activeSubscriptions = [],
|
||||
allSubscriptions = [],
|
||||
reloadSubscriptionSelf,
|
||||
enableRedemption = true,
|
||||
}) => {
|
||||
const onlineFormApiRef = useRef(null);
|
||||
const redeemFormApiRef = useRef(null);
|
||||
@ -566,57 +567,66 @@ const RechargeCard = ({
|
||||
</Card>
|
||||
|
||||
{/* 兑换码充值 */}
|
||||
<Card
|
||||
className='!rounded-xl w-full'
|
||||
title={
|
||||
<Text type='tertiary' strong>
|
||||
{t('兑换码充值')}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<Form
|
||||
getFormApi={(api) => (redeemFormApiRef.current = api)}
|
||||
initValues={{ redemptionCode: redemptionCode }}
|
||||
{enableRedemption ? (
|
||||
<Card
|
||||
className='!rounded-xl w-full'
|
||||
title={
|
||||
<Text type='tertiary' strong>
|
||||
{t('兑换码充值')}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<Form.Input
|
||||
field='redemptionCode'
|
||||
noLabel={true}
|
||||
placeholder={t('请输入兑换码')}
|
||||
value={redemptionCode}
|
||||
onChange={(value) => setRedemptionCode(value)}
|
||||
prefix={<IconGift />}
|
||||
suffix={
|
||||
<div className='flex items-center gap-2'>
|
||||
<Button
|
||||
type='primary'
|
||||
theme='solid'
|
||||
onClick={topUp}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{t('兑换额度')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
showClear
|
||||
style={{ width: '100%' }}
|
||||
extraText={
|
||||
topUpLink && (
|
||||
<Text type='tertiary'>
|
||||
{t('在找兑换码?')}
|
||||
<Text
|
||||
type='secondary'
|
||||
underline
|
||||
className='cursor-pointer'
|
||||
onClick={openTopUpLink}
|
||||
<Form
|
||||
getFormApi={(api) => (redeemFormApiRef.current = api)}
|
||||
initValues={{ redemptionCode: redemptionCode }}
|
||||
>
|
||||
<Form.Input
|
||||
field='redemptionCode'
|
||||
noLabel={true}
|
||||
placeholder={t('请输入兑换码')}
|
||||
value={redemptionCode}
|
||||
onChange={(value) => setRedemptionCode(value)}
|
||||
prefix={<IconGift />}
|
||||
suffix={
|
||||
<div className='flex items-center gap-2'>
|
||||
<Button
|
||||
type='primary'
|
||||
theme='solid'
|
||||
onClick={topUp}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{t('购买兑换码')}
|
||||
{t('兑换额度')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
showClear
|
||||
style={{ width: '100%' }}
|
||||
extraText={
|
||||
topUpLink && (
|
||||
<Text type='tertiary'>
|
||||
{t('在找兑换码?')}
|
||||
<Text
|
||||
type='secondary'
|
||||
underline
|
||||
className='cursor-pointer'
|
||||
onClick={openTopUpLink}
|
||||
>
|
||||
{t('购买兑换码')}
|
||||
</Text>
|
||||
</Text>
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Form>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Form>
|
||||
</Card>
|
||||
) : (
|
||||
<Banner
|
||||
type='warning'
|
||||
description={t('兑换码功能已禁用,管理员需先确认合规声明。')}
|
||||
closeIcon={null}
|
||||
className='!rounded-xl'
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
|
||||
|
||||
14
web/classic/src/components/topup/index.jsx
vendored
14
web/classic/src/components/topup/index.jsx
vendored
@ -110,6 +110,8 @@ const TopUp = () => {
|
||||
const [topupInfo, setTopupInfo] = useState({
|
||||
amount_options: [],
|
||||
discount: {},
|
||||
enable_redemption: true,
|
||||
payment_compliance_confirmed: true,
|
||||
});
|
||||
|
||||
const confirmPayMethods = [
|
||||
@ -645,7 +647,7 @@ const TopUp = () => {
|
||||
? data.waffo_min_topup
|
||||
: enableWaffoPancakeTopUp
|
||||
? data.waffo_pancake_min_topup
|
||||
: 1;
|
||||
: 1;
|
||||
setEnableOnlineTopUp(enableOnlineTopUp);
|
||||
setEnableStripeTopUp(enableStripeTopUp);
|
||||
setEnableCreemTopUp(enableCreemTopUp);
|
||||
@ -657,6 +659,14 @@ const TopUp = () => {
|
||||
setMinTopUp(minTopUpValue);
|
||||
setTopUpCount(minTopUpValue);
|
||||
setTopUpLink(data.topup_link || '');
|
||||
setTopupInfo((prev) => ({
|
||||
...prev,
|
||||
enable_redemption: data.enable_redemption !== false,
|
||||
payment_compliance_confirmed:
|
||||
data.payment_compliance_confirmed !== false,
|
||||
payment_compliance_terms_version:
|
||||
data.payment_compliance_terms_version || '',
|
||||
}));
|
||||
|
||||
// 设置 Creem 产品
|
||||
try {
|
||||
@ -984,6 +994,7 @@ const TopUp = () => {
|
||||
activeSubscriptions={activeSubscriptions}
|
||||
allSubscriptions={allSubscriptions}
|
||||
reloadSubscriptionSelf={getSubscriptionSelf}
|
||||
enableRedemption={topupInfo.enable_redemption !== false}
|
||||
/>
|
||||
<InvitationCard
|
||||
t={t}
|
||||
@ -992,6 +1003,7 @@ const TopUp = () => {
|
||||
setOpenTransfer={setOpenTransfer}
|
||||
affLink={affLink}
|
||||
handleAffLinkClick={handleAffLinkClick}
|
||||
complianceConfirmed={topupInfo.payment_compliance_confirmed !== false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
32
web/classic/src/i18n/locales/en.json
vendored
32
web/classic/src/i18n/locales/en.json
vendored
@ -3794,6 +3794,36 @@
|
||||
"见上方动态计费详情": "See dynamic pricing details above",
|
||||
"含时间条件": "Time rules",
|
||||
"含请求条件": "Request rules",
|
||||
"(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)": "(Currently only supports Epay interface, the default callback address is the server address above!)"
|
||||
"(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)": "(Currently only supports Epay interface, the default callback address is the server address above!)",
|
||||
"你已合法取得所接入模型 API、账号、密钥和额度的授权;": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;": "If you provide generative AI services to the public in mainland China, you will fulfill legal compliance obligations.",
|
||||
"你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"你理解并自行承担部署、运营和收费行为产生的法律责任。": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"需要确认合规声明": "Compliance confirmation required",
|
||||
"确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。": "Payment, redemption codes, subscription plans, and invitation rewards remain locked until confirmation.",
|
||||
"确认合规声明": "Confirm compliance terms",
|
||||
"合规声明已确认": "Compliance confirmed",
|
||||
"确认时间:{{time}},确认用户:#{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read carefully.",
|
||||
"请输入以下文字以确认:": "Please type the following text to confirm:",
|
||||
"请输入确认文案": "Type the confirmation text here",
|
||||
"输入内容与要求文案不一致": "The entered text does not match the required text.",
|
||||
"确认并启用": "Confirm and enable",
|
||||
"合规声明确认成功": "Compliance confirmed successfully",
|
||||
"确认失败": "Confirmation failed",
|
||||
"兑换码功能已禁用,管理员需先确认合规声明。": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。": "Subscription plan creation and changes are locked until compliance terms are confirmed in payment settings.",
|
||||
"邀请奖励划转已禁用,管理员需先确认合规声明。": "Invitation reward transfer is disabled until compliance terms are confirmed.",
|
||||
"设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。": "Non-zero invitation rewards require compliance confirmation in payment settings.",
|
||||
"非零值需先确认合规声明": "Non-zero values require compliance confirmation first",
|
||||
"我已阅读并理解上述合规提醒": "I have read and understood the above compliance reminder",
|
||||
"知悉相关法律风险": "acknowledge the related legal risks",
|
||||
"并确认自行承担部署": "confirm that I bear legal responsibility arising from deployment",
|
||||
"运营和收费行为产生的法律责任": "operation and charging behavior",
|
||||
",": ", ",
|
||||
"、": ", "
|
||||
}
|
||||
}
|
||||
|
||||
32
web/classic/src/i18n/locales/fr.json
vendored
32
web/classic/src/i18n/locales/fr.json
vendored
@ -3649,6 +3649,36 @@
|
||||
"默认用户消息": "Bonjour",
|
||||
"默认补全倍率": "Taux de complétion par défaut",
|
||||
"阶梯计费(表达式解析失败)": "Facturation par paliers (échec de l'analyse de l'expression)",
|
||||
"阶梯计费(未匹配到对应阶梯)": "Facturation par paliers (aucun palier correspondant)"
|
||||
"阶梯计费(未匹配到对应阶梯)": "Facturation par paliers (aucun palier correspondant)",
|
||||
"你已合法取得所接入模型 API、账号、密钥和额度的授权;": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;": "If you provide generative AI services to the public in mainland China, you will fulfill legal compliance obligations.",
|
||||
"你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"你理解并自行承担部署、运营和收费行为产生的法律责任。": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"需要确认合规声明": "Compliance confirmation required",
|
||||
"确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。": "Payment, redemption codes, subscription plans, and invitation rewards remain locked until confirmation.",
|
||||
"确认合规声明": "Confirm compliance terms",
|
||||
"合规声明已确认": "Compliance confirmed",
|
||||
"确认时间:{{time}},确认用户:#{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read carefully.",
|
||||
"请输入以下文字以确认:": "Please type the following text to confirm:",
|
||||
"请输入确认文案": "Type the confirmation text here",
|
||||
"输入内容与要求文案不一致": "The entered text does not match the required text.",
|
||||
"确认并启用": "Confirm and enable",
|
||||
"合规声明确认成功": "Compliance confirmed successfully",
|
||||
"确认失败": "Confirmation failed",
|
||||
"兑换码功能已禁用,管理员需先确认合规声明。": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。": "Subscription plan creation and changes are locked until compliance terms are confirmed in payment settings.",
|
||||
"邀请奖励划转已禁用,管理员需先确认合规声明。": "Invitation reward transfer is disabled until compliance terms are confirmed.",
|
||||
"设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。": "Non-zero invitation rewards require compliance confirmation in payment settings.",
|
||||
"非零值需先确认合规声明": "Non-zero values require compliance confirmation first",
|
||||
"我已阅读并理解上述合规提醒": "I have read and understood the above compliance reminder",
|
||||
"知悉相关法律风险": "acknowledge the related legal risks",
|
||||
"并确认自行承担部署": "confirm that I bear legal responsibility arising from deployment",
|
||||
"运营和收费行为产生的法律责任": "operation and charging behavior",
|
||||
",": ", ",
|
||||
"、": ", "
|
||||
}
|
||||
}
|
||||
|
||||
32
web/classic/src/i18n/locales/ja.json
vendored
32
web/classic/src/i18n/locales/ja.json
vendored
@ -3618,6 +3618,36 @@
|
||||
"默认用户消息": "こんにちは",
|
||||
"默认补全倍率": "デフォルト補完倍率",
|
||||
"阶梯计费(表达式解析失败)": "段階課金(式の解析に失敗)",
|
||||
"阶梯计费(未匹配到对应阶梯)": "段階課金(一致する階層なし)"
|
||||
"阶梯计费(未匹配到对应阶梯)": "段階課金(一致する階層なし)",
|
||||
"你已合法取得所接入模型 API、账号、密钥和额度的授权;": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;": "If you provide generative AI services to the public in mainland China, you will fulfill legal compliance obligations.",
|
||||
"你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"你理解并自行承担部署、运营和收费行为产生的法律责任。": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"需要确认合规声明": "Compliance confirmation required",
|
||||
"确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。": "Payment, redemption codes, subscription plans, and invitation rewards remain locked until confirmation.",
|
||||
"确认合规声明": "Confirm compliance terms",
|
||||
"合规声明已确认": "Compliance confirmed",
|
||||
"确认时间:{{time}},确认用户:#{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read carefully.",
|
||||
"请输入以下文字以确认:": "Please type the following text to confirm:",
|
||||
"请输入确认文案": "Type the confirmation text here",
|
||||
"输入内容与要求文案不一致": "The entered text does not match the required text.",
|
||||
"确认并启用": "Confirm and enable",
|
||||
"合规声明确认成功": "Compliance confirmed successfully",
|
||||
"确认失败": "Confirmation failed",
|
||||
"兑换码功能已禁用,管理员需先确认合规声明。": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。": "Subscription plan creation and changes are locked until compliance terms are confirmed in payment settings.",
|
||||
"邀请奖励划转已禁用,管理员需先确认合规声明。": "Invitation reward transfer is disabled until compliance terms are confirmed.",
|
||||
"设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。": "Non-zero invitation rewards require compliance confirmation in payment settings.",
|
||||
"非零值需先确认合规声明": "Non-zero values require compliance confirmation first",
|
||||
"我已阅读并理解上述合规提醒": "I have read and understood the above compliance reminder",
|
||||
"知悉相关法律风险": "acknowledge the related legal risks",
|
||||
"并确认自行承担部署": "confirm that I bear legal responsibility arising from deployment",
|
||||
"运营和收费行为产生的法律责任": "operation and charging behavior",
|
||||
",": ", ",
|
||||
"、": ", "
|
||||
}
|
||||
}
|
||||
|
||||
32
web/classic/src/i18n/locales/ru.json
vendored
32
web/classic/src/i18n/locales/ru.json
vendored
@ -3669,6 +3669,36 @@
|
||||
"默认用户消息": "Здравствуйте",
|
||||
"默认补全倍率": "Коэффициент завершения по умолчанию",
|
||||
"阶梯计费(表达式解析失败)": "Многоуровневая тарификация (ошибка разбора выражения)",
|
||||
"阶梯计费(未匹配到对应阶梯)": "Многоуровневая тарификация (подходящий уровень не найден)"
|
||||
"阶梯计费(未匹配到对应阶梯)": "Многоуровневая тарификация (подходящий уровень не найден)",
|
||||
"你已合法取得所接入模型 API、账号、密钥和额度的授权;": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;": "If you provide generative AI services to the public in mainland China, you will fulfill legal compliance obligations.",
|
||||
"你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"你理解并自行承担部署、运营和收费行为产生的法律责任。": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"需要确认合规声明": "Compliance confirmation required",
|
||||
"确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。": "Payment, redemption codes, subscription plans, and invitation rewards remain locked until confirmation.",
|
||||
"确认合规声明": "Confirm compliance terms",
|
||||
"合规声明已确认": "Compliance confirmed",
|
||||
"确认时间:{{time}},确认用户:#{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read carefully.",
|
||||
"请输入以下文字以确认:": "Please type the following text to confirm:",
|
||||
"请输入确认文案": "Type the confirmation text here",
|
||||
"输入内容与要求文案不一致": "The entered text does not match the required text.",
|
||||
"确认并启用": "Confirm and enable",
|
||||
"合规声明确认成功": "Compliance confirmed successfully",
|
||||
"确认失败": "Confirmation failed",
|
||||
"兑换码功能已禁用,管理员需先确认合规声明。": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。": "Subscription plan creation and changes are locked until compliance terms are confirmed in payment settings.",
|
||||
"邀请奖励划转已禁用,管理员需先确认合规声明。": "Invitation reward transfer is disabled until compliance terms are confirmed.",
|
||||
"设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。": "Non-zero invitation rewards require compliance confirmation in payment settings.",
|
||||
"非零值需先确认合规声明": "Non-zero values require compliance confirmation first",
|
||||
"我已阅读并理解上述合规提醒": "I have read and understood the above compliance reminder",
|
||||
"知悉相关法律风险": "acknowledge the related legal risks",
|
||||
"并确认自行承担部署": "confirm that I bear legal responsibility arising from deployment",
|
||||
"运营和收费行为产生的法律责任": "operation and charging behavior",
|
||||
",": ", ",
|
||||
"、": ", "
|
||||
}
|
||||
}
|
||||
|
||||
32
web/classic/src/i18n/locales/vi.json
vendored
32
web/classic/src/i18n/locales/vi.json
vendored
@ -4183,6 +4183,36 @@
|
||||
"默认用户消息": "Xin chào",
|
||||
"默认补全倍率": "Tỷ lệ hoàn thành mặc định",
|
||||
"阶梯计费(表达式解析失败)": "Thanh toán theo bậc (không phân tích được biểu thức)",
|
||||
"阶梯计费(未匹配到对应阶梯)": "Thanh toán theo bậc (không tìm thấy bậc phù hợp)"
|
||||
"阶梯计费(未匹配到对应阶梯)": "Thanh toán theo bậc (không tìm thấy bậc phù hợp)",
|
||||
"你已合法取得所接入模型 API、账号、密钥和额度的授权;": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;": "If you provide generative AI services to the public in mainland China, you will fulfill legal compliance obligations.",
|
||||
"你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"你理解并自行承担部署、运营和收费行为产生的法律责任。": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"需要确认合规声明": "Compliance confirmation required",
|
||||
"确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。": "Payment, redemption codes, subscription plans, and invitation rewards remain locked until confirmation.",
|
||||
"确认合规声明": "Confirm compliance terms",
|
||||
"合规声明已确认": "Compliance confirmed",
|
||||
"确认时间:{{time}},确认用户:#{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read carefully.",
|
||||
"请输入以下文字以确认:": "Please type the following text to confirm:",
|
||||
"请输入确认文案": "Type the confirmation text here",
|
||||
"输入内容与要求文案不一致": "The entered text does not match the required text.",
|
||||
"确认并启用": "Confirm and enable",
|
||||
"合规声明确认成功": "Compliance confirmed successfully",
|
||||
"确认失败": "Confirmation failed",
|
||||
"兑换码功能已禁用,管理员需先确认合规声明。": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。": "Subscription plan creation and changes are locked until compliance terms are confirmed in payment settings.",
|
||||
"邀请奖励划转已禁用,管理员需先确认合规声明。": "Invitation reward transfer is disabled until compliance terms are confirmed.",
|
||||
"设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。": "Non-zero invitation rewards require compliance confirmation in payment settings.",
|
||||
"非零值需先确认合规声明": "Non-zero values require compliance confirmation first",
|
||||
"我已阅读并理解上述合规提醒": "I have read and understood the above compliance reminder",
|
||||
"知悉相关法律风险": "acknowledge the related legal risks",
|
||||
"并确认自行承担部署": "confirm that I bear legal responsibility arising from deployment",
|
||||
"运营和收费行为产生的法律责任": "operation and charging behavior",
|
||||
",": ", ",
|
||||
"、": ", "
|
||||
}
|
||||
}
|
||||
|
||||
32
web/classic/src/i18n/locales/zh-CN.json
vendored
32
web/classic/src/i18n/locales/zh-CN.json
vendored
@ -3778,6 +3778,36 @@
|
||||
"缓存创建-1h": "缓存创建-1h",
|
||||
"见上方动态计费详情": "见上方动态计费详情",
|
||||
"含时间条件": "含时间条件",
|
||||
"含请求条件": "含请求条件"
|
||||
"含请求条件": "含请求条件",
|
||||
"你已合法取得所接入模型 API、账号、密钥和额度的授权;": "你已合法取得所接入模型 API、账号、密钥和额度的授权;",
|
||||
"你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。": "你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。",
|
||||
"如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;": "如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;",
|
||||
"你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。": "你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。",
|
||||
"你理解并自行承担部署、运营和收费行为产生的法律责任。": "你理解并自行承担部署、运营和收费行为产生的法律责任。",
|
||||
"你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。": "你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。",
|
||||
"我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任": "我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任",
|
||||
"需要确认合规声明": "需要确认合规声明",
|
||||
"确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。": "确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。",
|
||||
"确认合规声明": "确认合规声明",
|
||||
"合规声明已确认": "合规声明已确认",
|
||||
"确认时间:{{time}},确认用户:#{{userId}}": "确认时间:{{time}},确认用户:#{{userId}}",
|
||||
"该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。": "该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。",
|
||||
"请输入以下文字以确认:": "请输入以下文字以确认:",
|
||||
"请输入确认文案": "请输入确认文案",
|
||||
"输入内容与要求文案不一致": "输入内容与要求文案不一致",
|
||||
"确认并启用": "确认并启用",
|
||||
"合规声明确认成功": "合规声明确认成功",
|
||||
"确认失败": "确认失败",
|
||||
"兑换码功能已禁用,管理员需先确认合规声明。": "兑换码功能已禁用,管理员需先确认合规声明。",
|
||||
"订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。": "订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。",
|
||||
"邀请奖励划转已禁用,管理员需先确认合规声明。": "邀请奖励划转已禁用,管理员需先确认合规声明。",
|
||||
"设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。": "设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。",
|
||||
"非零值需先确认合规声明": "非零值需先确认合规声明",
|
||||
"我已阅读并理解上述合规提醒": "我已阅读并理解上述合规提醒",
|
||||
"知悉相关法律风险": "知悉相关法律风险",
|
||||
"并确认自行承担部署": "并确认自行承担部署",
|
||||
"运营和收费行为产生的法律责任": "运营和收费行为产生的法律责任",
|
||||
",": ",",
|
||||
"、": "、"
|
||||
}
|
||||
}
|
||||
|
||||
32
web/classic/src/i18n/locales/zh-TW.json
vendored
32
web/classic/src/i18n/locales/zh-TW.json
vendored
@ -3642,6 +3642,36 @@
|
||||
"默认用户消息": "你好",
|
||||
"默认补全倍率": "預設補全倍率",
|
||||
"阶梯计费(表达式解析失败)": "階梯計費(表達式解析失敗)",
|
||||
"阶梯计费(未匹配到对应阶梯)": "階梯計費(未匹配到對應階梯)"
|
||||
"阶梯计费(未匹配到对应阶梯)": "階梯計費(未匹配到對應階梯)",
|
||||
"你已合法取得所接入模型 API、账号、密钥和额度的授权;": "你已合法取得所接入模型 API、账号、密钥和额度的授权;",
|
||||
"你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。": "你承諾僅在已取得上游服務商、模型服務提供方或相關權利方合法授權的範圍內使用其 API、帳號、金鑰、額度及服務能力,不進行未經授權的轉售、倒賣、分銷或其他違規商業化使用。",
|
||||
"如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;": "如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;",
|
||||
"你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。": "你承諾不會利用本系統實施、協助實施或變相實施違反適用法律法規、監管要求、平台規則、社會公共利益或第三方合法權益的行為。",
|
||||
"你理解并自行承担部署、运营和收费行为产生的法律责任。": "你理解并自行承担部署、运营和收费行为产生的法律责任。",
|
||||
"你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。": "你理解本合規提醒僅用於風險提示,不構成法律意見、合規審查結論或對你使用本系統行為合法性的保證;你應根據實際業務場景自行諮詢專業法律或合規顧問。",
|
||||
"我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任": "我已閱讀並理解上述合規提醒,知悉相關法律風險,並確認自行承擔部署、營運和收費行為產生的法律責任",
|
||||
"需要确认合规声明": "需要确认合规声明",
|
||||
"确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。": "确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。",
|
||||
"确认合规声明": "确认合规声明",
|
||||
"合规声明已确认": "合规声明已确认",
|
||||
"确认时间:{{time}},确认用户:#{{userId}}": "确认时间:{{time}},确认用户:#{{userId}}",
|
||||
"该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。": "该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。",
|
||||
"请输入以下文字以确认:": "请输入以下文字以确认:",
|
||||
"请输入确认文案": "请输入确认文案",
|
||||
"输入内容与要求文案不一致": "输入内容与要求文案不一致",
|
||||
"确认并启用": "确认并启用",
|
||||
"合规声明确认成功": "合规声明确认成功",
|
||||
"确认失败": "确认失败",
|
||||
"兑换码功能已禁用,管理员需先确认合规声明。": "兑换码功能已禁用,管理员需先确认合规声明。",
|
||||
"订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。": "订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。",
|
||||
"邀请奖励划转已禁用,管理员需先确认合规声明。": "邀请奖励划转已禁用,管理员需先确认合规声明。",
|
||||
"设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。": "设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。",
|
||||
"非零值需先确认合规声明": "非零值需先确认合规声明",
|
||||
"我已阅读并理解上述合规提醒": "我已閱讀並理解上述合規提醒",
|
||||
"知悉相关法律风险": "知悉相關法律風險",
|
||||
"并确认自行承担部署": "並確認自行承擔部署",
|
||||
"运营和收费行为产生的法律责任": "營運和收費行為產生的法律責任",
|
||||
",": ",",
|
||||
"、": "、"
|
||||
}
|
||||
}
|
||||
|
||||
33
web/classic/src/i18n/locales/zh.json
vendored
33
web/classic/src/i18n/locales/zh.json
vendored
@ -2561,7 +2561,6 @@
|
||||
"默认补全倍率": "默认补全倍率",
|
||||
"每日签到": "每日签到",
|
||||
"今日已签到,累计签到": "今日已签到,累计签到",
|
||||
"天": "天",
|
||||
"每日签到可获得随机额度奖励": "每日签到可获得随机额度奖励",
|
||||
"今日已签到": "今日已签到",
|
||||
"立即签到": "立即签到",
|
||||
@ -2595,6 +2594,36 @@
|
||||
"说明:本页测试为非流式请求;若渠道仅支持流式返回,可能出现测试失败,请以实际使用为准。": "说明:本页测试为非流式请求;若渠道仅支持流式返回,可能出现测试失败,请以实际使用为准。",
|
||||
"提示:端点映射仅用于模型广场展示,不会影响模型真实调用。如需配置真实调用,请前往「渠道管理」。": "提示:端点映射仅用于模型广场展示,不会影响模型真实调用。如需配置真实调用,请前往「渠道管理」。",
|
||||
"阶梯计费(表达式解析失败)": "阶梯计费(表达式解析失败)",
|
||||
"阶梯计费(未匹配到对应阶梯)": "阶梯计费(未匹配到对应阶梯)"
|
||||
"阶梯计费(未匹配到对应阶梯)": "阶梯计费(未匹配到对应阶梯)",
|
||||
"你已合法取得所接入模型 API、账号、密钥和额度的授权;": "你已合法取得所接入模型 API、账号、密钥和额度的授权;",
|
||||
"你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。": "你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。",
|
||||
"如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;": "如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;",
|
||||
"你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。": "你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。",
|
||||
"你理解并自行承担部署、运营和收费行为产生的法律责任。": "你理解并自行承担部署、运营和收费行为产生的法律责任。",
|
||||
"你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。": "你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。",
|
||||
"我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任": "我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任",
|
||||
"需要确认合规声明": "需要确认合规声明",
|
||||
"确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。": "确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。",
|
||||
"确认合规声明": "确认合规声明",
|
||||
"合规声明已确认": "合规声明已确认",
|
||||
"确认时间:{{time}},确认用户:#{{userId}}": "确认时间:{{time}},确认用户:#{{userId}}",
|
||||
"该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。": "该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。",
|
||||
"请输入以下文字以确认:": "请输入以下文字以确认:",
|
||||
"请输入确认文案": "请输入确认文案",
|
||||
"输入内容与要求文案不一致": "输入内容与要求文案不一致",
|
||||
"确认并启用": "确认并启用",
|
||||
"合规声明确认成功": "合规声明确认成功",
|
||||
"确认失败": "确认失败",
|
||||
"兑换码功能已禁用,管理员需先确认合规声明。": "兑换码功能已禁用,管理员需先确认合规声明。",
|
||||
"订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。": "订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。",
|
||||
"邀请奖励划转已禁用,管理员需先确认合规声明。": "邀请奖励划转已禁用,管理员需先确认合规声明。",
|
||||
"设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。": "设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。",
|
||||
"非零值需先确认合规声明": "非零值需先确认合规声明",
|
||||
"我已阅读并理解上述合规提醒": "我已阅读并理解上述合规提醒",
|
||||
"知悉相关法律风险": "知悉相关法律风险",
|
||||
"并确认自行承担部署": "并确认自行承担部署",
|
||||
"运营和收费行为产生的法律责任": "运营和收费行为产生的法律责任",
|
||||
",": ",",
|
||||
"、": "、"
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
|
||||
import { Banner, Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
compareObjects,
|
||||
@ -40,6 +40,9 @@ export default function SettingsCreditLimit(props) {
|
||||
});
|
||||
const refForm = useRef();
|
||||
const [inputsRow, setInputsRow] = useState(inputs);
|
||||
const complianceConfirmed =
|
||||
props.options?.['payment_setting.compliance_confirmed'] === true ||
|
||||
props.options?.['payment_setting.compliance_confirmed'] === 'true';
|
||||
|
||||
function onSubmit() {
|
||||
const updateArray = compareObjects(inputs, inputsRow);
|
||||
@ -90,6 +93,16 @@ export default function SettingsCreditLimit(props) {
|
||||
return (
|
||||
<>
|
||||
<Spin spinning={loading}>
|
||||
{!complianceConfirmed && (
|
||||
<Banner
|
||||
type='warning'
|
||||
description={t(
|
||||
'设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。',
|
||||
)}
|
||||
closeIcon={null}
|
||||
className='!rounded-lg mb-3'
|
||||
/>
|
||||
)}
|
||||
<Form
|
||||
values={inputs}
|
||||
getFormApi={(formAPI) => (refForm.current = formAPI)}
|
||||
@ -137,7 +150,9 @@ export default function SettingsCreditLimit(props) {
|
||||
step={1}
|
||||
min={0}
|
||||
suffix={'Token'}
|
||||
extraText={''}
|
||||
extraText={
|
||||
!complianceConfirmed ? t('非零值需先确认合规声明') : ''
|
||||
}
|
||||
placeholder={t('例如:2000')}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
@ -156,7 +171,9 @@ export default function SettingsCreditLimit(props) {
|
||||
step={1}
|
||||
min={0}
|
||||
suffix={'Token'}
|
||||
extraText={''}
|
||||
extraText={
|
||||
!complianceConfirmed ? t('非零值需先确认合规声明') : ''
|
||||
}
|
||||
placeholder={t('例如:1000')}
|
||||
onChange={(value) =>
|
||||
setInputs({
|
||||
|
||||
308
web/default/src/components/risk-acknowledgement-dialog.tsx
vendored
Normal file
308
web/default/src/components/risk-acknowledgement-dialog.tsx
vendored
Normal file
@ -0,0 +1,308 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
|
||||
type RequiredTextPart = {
|
||||
type: 'input' | 'static'
|
||||
text: string
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
type NormalizedRequiredTextPart = RequiredTextPart & {
|
||||
inputIndex?: number
|
||||
}
|
||||
|
||||
type RiskAcknowledgementDialogProps = {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
title: string
|
||||
description?: React.ReactNode
|
||||
items?: string[]
|
||||
checklist?: string[]
|
||||
requiredText?: string
|
||||
requiredTextParts?: RequiredTextPart[]
|
||||
inputPrompt?: string
|
||||
inputPlaceholder?: string
|
||||
mismatchHint?: string
|
||||
confirmText?: string
|
||||
cancelText?: string
|
||||
destructive?: boolean
|
||||
isLoading?: boolean
|
||||
onConfirm: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function RiskAcknowledgementDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
title,
|
||||
description,
|
||||
items = [],
|
||||
checklist = [],
|
||||
requiredText = '',
|
||||
requiredTextParts = [],
|
||||
inputPrompt,
|
||||
inputPlaceholder,
|
||||
mismatchHint,
|
||||
confirmText,
|
||||
cancelText,
|
||||
destructive = true,
|
||||
isLoading = false,
|
||||
onConfirm,
|
||||
className,
|
||||
}: RiskAcknowledgementDialogProps) {
|
||||
const { t } = useTranslation()
|
||||
const [checkedItems, setCheckedItems] = useState<boolean[]>([])
|
||||
const [typedText, setTypedText] = useState('')
|
||||
const [typedTextParts, setTypedTextParts] = useState<string[]>([])
|
||||
|
||||
const normalizedRequiredTextParts = useMemo<
|
||||
NormalizedRequiredTextPart[]
|
||||
>(() => {
|
||||
let inputIndex = 0
|
||||
return requiredTextParts.map((part) => {
|
||||
if (part.type === 'input') {
|
||||
const normalizedPart = { ...part, inputIndex }
|
||||
inputIndex += 1
|
||||
return normalizedPart
|
||||
}
|
||||
return part
|
||||
})
|
||||
}, [requiredTextParts])
|
||||
|
||||
const requiredTextInputCount = useMemo(
|
||||
() =>
|
||||
normalizedRequiredTextParts.filter((part) => part.type === 'input')
|
||||
.length,
|
||||
[normalizedRequiredTextParts]
|
||||
)
|
||||
const hasSegmentedRequiredText = requiredTextInputCount > 0
|
||||
const requiredTextToDisplay = hasSegmentedRequiredText
|
||||
? normalizedRequiredTextParts.map((part) => part.text).join('')
|
||||
: requiredText
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
setCheckedItems(Array(checklist.length).fill(false))
|
||||
setTypedText('')
|
||||
setTypedTextParts(Array(requiredTextInputCount).fill(''))
|
||||
}, [open, checklist.length, requiredTextInputCount])
|
||||
|
||||
const allChecked = useMemo(() => {
|
||||
if (checklist.length === 0) return true
|
||||
return (
|
||||
checkedItems.length === checklist.length &&
|
||||
checkedItems.every((checked) => checked)
|
||||
)
|
||||
}, [checkedItems, checklist.length])
|
||||
|
||||
const typedMatched = useMemo(() => {
|
||||
if (hasSegmentedRequiredText) {
|
||||
return normalizedRequiredTextParts.every((part) => {
|
||||
if (part.type === 'static') return true
|
||||
return typedTextParts[part.inputIndex ?? 0]?.trim() === part.text.trim()
|
||||
})
|
||||
}
|
||||
if (!requiredText) return true
|
||||
return typedText.trim() === requiredText.trim()
|
||||
}, [
|
||||
hasSegmentedRequiredText,
|
||||
normalizedRequiredTextParts,
|
||||
requiredText,
|
||||
typedText,
|
||||
typedTextParts,
|
||||
])
|
||||
const hasTypedRequiredText = hasSegmentedRequiredText
|
||||
? typedTextParts.some((part) => part.trim() !== '')
|
||||
: typedText.length > 0
|
||||
|
||||
const canConfirm = allChecked && typedMatched && !isLoading
|
||||
|
||||
const handleChecklistChange = (index: number, checked: boolean) => {
|
||||
setCheckedItems((previous) => {
|
||||
const next = [...previous]
|
||||
next[index] = checked
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const handleTextPartChange = (index: number, value: string) => {
|
||||
setTypedTextParts((previous) => {
|
||||
const next = [...previous]
|
||||
next[index] = value
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
||||
<AlertDialogContent
|
||||
className={cn(
|
||||
'flex max-h-[min(88dvh,760px)] w-[calc(100vw-1.5rem)] !max-w-[44rem] grid-rows-none flex-col gap-0 overflow-hidden !p-0 sm:w-[min(44rem,calc(100vw-3rem))]',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<AlertDialogHeader className='shrink-0 px-4 pt-4 pb-3 text-left sm:px-6 sm:pt-6'>
|
||||
<AlertDialogTitle>{title}</AlertDialogTitle>
|
||||
{description ? (
|
||||
<AlertDialogDescription
|
||||
render={<div />}
|
||||
className='mt-1 text-left leading-5'
|
||||
>
|
||||
{description}
|
||||
</AlertDialogDescription>
|
||||
) : null}
|
||||
</AlertDialogHeader>
|
||||
|
||||
<div className='min-h-0 flex-1 space-y-4 overflow-y-auto px-4 pb-4 sm:px-6'>
|
||||
{items.length > 0 ? (
|
||||
<ol className='border-border/70 bg-muted/30 text-foreground list-decimal space-y-2 rounded-lg border px-4 py-3 pl-8 text-sm leading-6 sm:px-5 sm:py-4 sm:pl-9'>
|
||||
{items.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ol>
|
||||
) : null}
|
||||
|
||||
{checklist.length > 0 ? (
|
||||
<div className='border-border/70 bg-muted/30 space-y-3 rounded-lg border p-3 sm:p-4'>
|
||||
{checklist.map((item, index) => {
|
||||
const id = `risk-acknowledgement-${index}`
|
||||
return (
|
||||
<div key={item} className='flex items-start gap-3'>
|
||||
<Checkbox
|
||||
id={id}
|
||||
checked={checkedItems[index] ?? false}
|
||||
onCheckedChange={(checked) =>
|
||||
handleChecklistChange(index, checked === true)
|
||||
}
|
||||
className='mt-0.5'
|
||||
/>
|
||||
<Label
|
||||
htmlFor={id}
|
||||
className='text-muted-foreground text-sm leading-5 font-normal'
|
||||
>
|
||||
{item}
|
||||
</Label>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{requiredTextToDisplay ? (
|
||||
<div className='border-destructive/30 bg-destructive/5 space-y-3 rounded-lg border p-3 sm:p-4'>
|
||||
<Label className='text-sm font-medium'>
|
||||
{inputPrompt ?? t('Please type the following text to confirm:')}
|
||||
</Label>
|
||||
<div className='bg-background border-border rounded-md border px-3 py-2 font-mono text-sm break-all'>
|
||||
{requiredTextToDisplay}
|
||||
</div>
|
||||
{hasSegmentedRequiredText ? (
|
||||
<div className='flex flex-wrap items-center gap-2'>
|
||||
{normalizedRequiredTextParts.map((part, index) =>
|
||||
part.type === 'static' ? (
|
||||
<span
|
||||
key={`static-${index}`}
|
||||
className='text-muted-foreground bg-background/70 border-border rounded-md border px-2 py-1.5 font-mono text-sm select-none'
|
||||
>
|
||||
{part.text}
|
||||
</span>
|
||||
) : (
|
||||
<Input
|
||||
key={`input-${index}`}
|
||||
value={typedTextParts[part.inputIndex ?? 0] ?? ''}
|
||||
onChange={(event) =>
|
||||
handleTextPartChange(
|
||||
part.inputIndex ?? 0,
|
||||
event.target.value
|
||||
)
|
||||
}
|
||||
placeholder={
|
||||
part.placeholder ??
|
||||
part.text ??
|
||||
inputPlaceholder ??
|
||||
t('Type the confirmation text here')
|
||||
}
|
||||
autoFocus={open && part.inputIndex === 0}
|
||||
onCopy={(event) => event.preventDefault()}
|
||||
onCut={(event) => event.preventDefault()}
|
||||
onPaste={(event) => event.preventDefault()}
|
||||
onDrop={(event) => event.preventDefault()}
|
||||
aria-invalid={hasTypedRequiredText && !typedMatched}
|
||||
className='w-full font-mono sm:w-64'
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Input
|
||||
value={typedText}
|
||||
onChange={(event) => setTypedText(event.target.value)}
|
||||
placeholder={
|
||||
inputPlaceholder ?? t('Type the confirmation text here')
|
||||
}
|
||||
autoFocus={open}
|
||||
onCopy={(event) => event.preventDefault()}
|
||||
onCut={(event) => event.preventDefault()}
|
||||
onPaste={(event) => event.preventDefault()}
|
||||
onDrop={(event) => event.preventDefault()}
|
||||
aria-invalid={hasTypedRequiredText && !typedMatched}
|
||||
/>
|
||||
)}
|
||||
{hasTypedRequiredText && !typedMatched ? (
|
||||
<p className='text-destructive text-xs'>
|
||||
{mismatchHint ??
|
||||
t('The entered text does not match the required text.')}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<AlertDialogFooter className='mx-0 mb-0 shrink-0 rounded-b-xl border-t p-3 sm:p-4'>
|
||||
<AlertDialogCancel disabled={isLoading}>
|
||||
{cancelText ?? t('Cancel')}
|
||||
</AlertDialogCancel>
|
||||
<Button
|
||||
variant={destructive ? 'destructive' : 'default'}
|
||||
disabled={!canConfirm}
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{confirmText ?? t('Confirm')}
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
@ -35,7 +35,7 @@ interface DataTableRowActionsProps {
|
||||
|
||||
export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
||||
const { t } = useTranslation()
|
||||
const { setOpen, setCurrentRow } = useSubscriptions()
|
||||
const { setOpen, setCurrentRow, complianceConfirmed } = useSubscriptions()
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@ -46,6 +46,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align='end'>
|
||||
<DropdownMenuItem
|
||||
disabled={!complianceConfirmed}
|
||||
onClick={() => {
|
||||
setCurrentRow(row.original)
|
||||
setOpen('update')
|
||||
@ -55,6 +56,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
||||
{t('Edit')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
disabled={!complianceConfirmed}
|
||||
onClick={() => {
|
||||
setCurrentRow(row.original)
|
||||
setOpen('toggle-status')
|
||||
|
||||
@ -23,10 +23,14 @@ import { useSubscriptions } from './subscriptions-provider'
|
||||
|
||||
export function SubscriptionsPrimaryButtons() {
|
||||
const { t } = useTranslation()
|
||||
const { setOpen } = useSubscriptions()
|
||||
const { setOpen, complianceConfirmed } = useSubscriptions()
|
||||
return (
|
||||
<div className='flex gap-2'>
|
||||
<Button size='sm' onClick={() => setOpen('create')}>
|
||||
<Button
|
||||
size='sm'
|
||||
onClick={() => setOpen('create')}
|
||||
disabled={!complianceConfirmed}
|
||||
>
|
||||
<Plus className='h-4 w-4' />
|
||||
{t('Create Plan')}
|
||||
</Button>
|
||||
|
||||
@ -18,8 +18,14 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import React, { useState } from 'react'
|
||||
import useDialogState from '@/hooks/use-dialog'
|
||||
import {
|
||||
getOptionValue,
|
||||
useSystemOptions,
|
||||
} from '@/features/system-settings/hooks/use-system-options'
|
||||
import { type PlanRecord, type SubscriptionsDialogType } from '../types'
|
||||
|
||||
const CURRENT_COMPLIANCE_TERMS_VERSION = 'v1'
|
||||
|
||||
type SubscriptionsContextType = {
|
||||
open: SubscriptionsDialogType | null
|
||||
setOpen: (str: SubscriptionsDialogType | null) => void
|
||||
@ -27,6 +33,7 @@ type SubscriptionsContextType = {
|
||||
setCurrentRow: React.Dispatch<React.SetStateAction<PlanRecord | null>>
|
||||
refreshTrigger: number
|
||||
triggerRefresh: () => void
|
||||
complianceConfirmed: boolean
|
||||
}
|
||||
|
||||
const SubscriptionsContext =
|
||||
@ -40,6 +47,15 @@ export function SubscriptionsProvider({
|
||||
const [open, setOpen] = useDialogState<SubscriptionsDialogType>(null)
|
||||
const [currentRow, setCurrentRow] = useState<PlanRecord | null>(null)
|
||||
const [refreshTrigger, setRefreshTrigger] = useState(0)
|
||||
const { data } = useSystemOptions()
|
||||
const complianceOptions = getOptionValue(data?.data, {
|
||||
'payment_setting.compliance_confirmed': false,
|
||||
'payment_setting.compliance_terms_version': '',
|
||||
})
|
||||
const complianceConfirmed =
|
||||
complianceOptions['payment_setting.compliance_confirmed'] &&
|
||||
complianceOptions['payment_setting.compliance_terms_version'] ===
|
||||
CURRENT_COMPLIANCE_TERMS_VERSION
|
||||
|
||||
const triggerRefresh = () => setRefreshTrigger((prev) => prev + 1)
|
||||
|
||||
@ -52,6 +68,7 @@ export function SubscriptionsProvider({
|
||||
setCurrentRow,
|
||||
refreshTrigger,
|
||||
triggerRefresh,
|
||||
complianceConfirmed,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
28
web/default/src/features/subscriptions/index.tsx
vendored
28
web/default/src/features/subscriptions/index.tsx
vendored
@ -22,13 +22,18 @@ import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { SectionPageLayout } from '@/components/layout'
|
||||
import { SubscriptionsDialogs } from './components/subscriptions-dialogs'
|
||||
import { SubscriptionsPrimaryButtons } from './components/subscriptions-primary-buttons'
|
||||
import { SubscriptionsProvider } from './components/subscriptions-provider'
|
||||
import {
|
||||
SubscriptionsProvider,
|
||||
useSubscriptions,
|
||||
} from './components/subscriptions-provider'
|
||||
import { SubscriptionsTable } from './components/subscriptions-table'
|
||||
|
||||
export function Subscriptions() {
|
||||
function SubscriptionsContent() {
|
||||
const { t } = useTranslation()
|
||||
const { complianceConfirmed } = useSubscriptions()
|
||||
|
||||
return (
|
||||
<SubscriptionsProvider>
|
||||
<>
|
||||
<SectionPageLayout>
|
||||
<SectionPageLayout.Title>
|
||||
{t('Subscription Management')}
|
||||
@ -50,11 +55,28 @@ export function Subscriptions() {
|
||||
</div>
|
||||
</SectionPageLayout.Actions>
|
||||
<SectionPageLayout.Content>
|
||||
{!complianceConfirmed ? (
|
||||
<Alert variant='destructive' className='mb-4'>
|
||||
<AlertDescription>
|
||||
{t(
|
||||
'Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.'
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : null}
|
||||
<SubscriptionsTable />
|
||||
</SectionPageLayout.Content>
|
||||
</SectionPageLayout>
|
||||
|
||||
<SubscriptionsDialogs />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function Subscriptions() {
|
||||
return (
|
||||
<SubscriptionsProvider>
|
||||
<SubscriptionsContent />
|
||||
</SubscriptionsProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { api } from '@/lib/api'
|
||||
import type {
|
||||
ConfirmPaymentComplianceResponse,
|
||||
DeleteLogsResponse,
|
||||
FetchUpstreamRatiosRequest,
|
||||
SystemOptionsResponse,
|
||||
@ -37,6 +38,14 @@ export async function updateSystemOption(request: UpdateOptionRequest) {
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function confirmPaymentCompliance() {
|
||||
const res = await api.post<ConfirmPaymentComplianceResponse>(
|
||||
'/api/option/payment_compliance',
|
||||
{ confirmed: true }
|
||||
)
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function deleteLogsBefore(targetTimestamp: number) {
|
||||
const res = await api.delete<DeleteLogsResponse>('/api/log/', {
|
||||
params: { target_timestamp: targetTimestamp },
|
||||
|
||||
@ -66,6 +66,11 @@ const defaultBillingSettings: BillingSettings = {
|
||||
PayMethods: '',
|
||||
'payment_setting.amount_options': '',
|
||||
'payment_setting.amount_discount': '',
|
||||
'payment_setting.compliance_confirmed': false,
|
||||
'payment_setting.compliance_terms_version': '',
|
||||
'payment_setting.compliance_confirmed_at': 0,
|
||||
'payment_setting.compliance_confirmed_by': 0,
|
||||
'payment_setting.compliance_confirmed_ip': '',
|
||||
StripeApiSecret: '',
|
||||
StripeWebhookSecret: '',
|
||||
StripePriceId: '',
|
||||
|
||||
@ -71,6 +71,10 @@ const BILLING_SECTIONS = [
|
||||
settings['quota_setting.enable_free_model_pre_consume'],
|
||||
},
|
||||
}}
|
||||
complianceConfirmed={
|
||||
(settings['payment_setting.compliance_confirmed'] ?? false) &&
|
||||
settings['payment_setting.compliance_terms_version'] === 'v1'
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -187,6 +191,13 @@ const BILLING_SECTIONS = [
|
||||
WaffoPancakeUnitPrice: settings.WaffoPancakeUnitPrice ?? 1,
|
||||
WaffoPancakeMinTopUp: settings.WaffoPancakeMinTopUp ?? 1,
|
||||
}}
|
||||
complianceDefaults={{
|
||||
confirmed: settings['payment_setting.compliance_confirmed'] ?? false,
|
||||
termsVersion:
|
||||
settings['payment_setting.compliance_terms_version'] ?? '',
|
||||
confirmedAt: settings['payment_setting.compliance_confirmed_at'] ?? 0,
|
||||
confirmedBy: settings['payment_setting.compliance_confirmed_by'] ?? 0,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
||||
@ -21,6 +21,7 @@ import * as z from 'zod'
|
||||
import type { Resolver } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
@ -57,10 +58,12 @@ type QuotaFormValues = z.infer<typeof quotaSchema>
|
||||
|
||||
type QuotaSettingsSectionProps = {
|
||||
defaultValues: QuotaFormValues
|
||||
complianceConfirmed?: boolean
|
||||
}
|
||||
|
||||
export function QuotaSettingsSection({
|
||||
defaultValues,
|
||||
complianceConfirmed = true,
|
||||
}: QuotaSettingsSectionProps) {
|
||||
const { t } = useTranslation()
|
||||
const updateOption = useUpdateOption()
|
||||
@ -97,6 +100,16 @@ export function QuotaSettingsSection({
|
||||
>
|
||||
<FormNavigationGuard when={isDirty} />
|
||||
|
||||
{!complianceConfirmed ? (
|
||||
<Alert variant='destructive'>
|
||||
<AlertDescription>
|
||||
{t(
|
||||
'Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.'
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={handleSubmit} className='space-y-6'>
|
||||
<FormDirtyIndicator isDirty={isDirty} />
|
||||
|
||||
@ -20,8 +20,17 @@ import * as React from 'react'
|
||||
import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { Code2, Eye } from 'lucide-react'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { Code2, Eye, ShieldAlert } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
Alert,
|
||||
AlertAction,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
@ -36,6 +45,8 @@ import { Input } from '@/components/ui/input'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { RiskAcknowledgementDialog } from '@/components/risk-acknowledgement-dialog'
|
||||
import { confirmPaymentCompliance } from '../api'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
import { AmountDiscountVisualEditor } from './amount-discount-visual-editor'
|
||||
@ -125,18 +136,30 @@ const paymentSchema = z.object({
|
||||
|
||||
type PaymentFormValues = z.infer<typeof paymentSchema>
|
||||
|
||||
const CURRENT_COMPLIANCE_TERMS_VERSION = 'v1'
|
||||
|
||||
type PaymentComplianceDefaults = {
|
||||
confirmed: boolean
|
||||
termsVersion: string
|
||||
confirmedAt: number
|
||||
confirmedBy: number
|
||||
}
|
||||
|
||||
type PaymentSettingsSectionProps = {
|
||||
defaultValues: PaymentFormValues
|
||||
waffoDefaultValues: WaffoSettingsValues
|
||||
waffoPancakeDefaultValues: WaffoPancakeSettingsValues
|
||||
complianceDefaults: PaymentComplianceDefaults
|
||||
}
|
||||
|
||||
export function PaymentSettingsSection({
|
||||
defaultValues,
|
||||
waffoDefaultValues,
|
||||
waffoPancakeDefaultValues,
|
||||
complianceDefaults,
|
||||
}: PaymentSettingsSectionProps) {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const updateOption = useUpdateOption()
|
||||
const initialRef = React.useRef(defaultValues)
|
||||
const defaultsSignature = React.useMemo(
|
||||
@ -151,6 +174,81 @@ export function PaymentSettingsSection({
|
||||
React.useState(true)
|
||||
const [creemProductsVisualMode, setCreemProductsVisualMode] =
|
||||
React.useState(true)
|
||||
const [showComplianceDialog, setShowComplianceDialog] = React.useState(false)
|
||||
|
||||
const complianceStatements = React.useMemo(
|
||||
() => [
|
||||
t(
|
||||
'You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.'
|
||||
),
|
||||
t(
|
||||
'You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.'
|
||||
),
|
||||
t(
|
||||
'If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.'
|
||||
),
|
||||
t(
|
||||
'You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.'
|
||||
),
|
||||
t(
|
||||
'You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.'
|
||||
),
|
||||
t(
|
||||
'You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.'
|
||||
),
|
||||
],
|
||||
[t]
|
||||
)
|
||||
|
||||
const complianceRequiredText = t(
|
||||
'I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.'
|
||||
)
|
||||
const complianceRequiredTextParts = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
type: 'input' as const,
|
||||
text: t('I have read and understood the above compliance reminder'),
|
||||
},
|
||||
{ type: 'static' as const, text: t(',') },
|
||||
{
|
||||
type: 'input' as const,
|
||||
text: t('acknowledge the related legal risks'),
|
||||
},
|
||||
{ type: 'static' as const, text: t(',and ') },
|
||||
{
|
||||
type: 'input' as const,
|
||||
text: t(
|
||||
'confirm that I bear legal responsibility arising from deployment'
|
||||
),
|
||||
},
|
||||
{ type: 'static' as const, text: t('、') },
|
||||
{
|
||||
type: 'input' as const,
|
||||
text: t('operation and charging behavior'),
|
||||
},
|
||||
],
|
||||
[t]
|
||||
)
|
||||
|
||||
const complianceConfirmed =
|
||||
complianceDefaults.confirmed &&
|
||||
complianceDefaults.termsVersion === CURRENT_COMPLIANCE_TERMS_VERSION
|
||||
|
||||
const confirmComplianceMutation = useMutation({
|
||||
mutationFn: confirmPaymentCompliance,
|
||||
onSuccess: (data) => {
|
||||
if (data.success) {
|
||||
toast.success(t('Compliance confirmed successfully'))
|
||||
setShowComplianceDialog(false)
|
||||
queryClient.invalidateQueries({ queryKey: ['system-options'] })
|
||||
} else {
|
||||
toast.error(data.message || t('Failed to confirm compliance'))
|
||||
}
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || t('Failed to confirm compliance'))
|
||||
},
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(paymentSchema),
|
||||
@ -562,11 +660,77 @@ export function PaymentSettingsSection({
|
||||
'Configure recharge pricing and payment gateway integrations'
|
||||
)}
|
||||
>
|
||||
{!complianceConfirmed ? (
|
||||
<Alert variant='destructive' className='mb-6'>
|
||||
<ShieldAlert className='h-4 w-4' />
|
||||
<AlertTitle>{t('Compliance confirmation required')}</AlertTitle>
|
||||
<AlertDescription>
|
||||
<div className='space-y-3'>
|
||||
<p>
|
||||
{t(
|
||||
'Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.'
|
||||
)}
|
||||
</p>
|
||||
<ol className='list-decimal space-y-1 pl-5'>
|
||||
{complianceStatements.map((statement) => (
|
||||
<li key={statement}>{statement}</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</AlertDescription>
|
||||
<AlertAction>
|
||||
<Button
|
||||
type='button'
|
||||
size='sm'
|
||||
variant='destructive'
|
||||
onClick={() => setShowComplianceDialog(true)}
|
||||
>
|
||||
{t('Confirm compliance')}
|
||||
</Button>
|
||||
</AlertAction>
|
||||
</Alert>
|
||||
) : (
|
||||
<Alert className='mb-6'>
|
||||
<AlertTitle>{t('Compliance confirmed')}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{t('Confirmed at {{time}} by user #{{userId}}', {
|
||||
time: complianceDefaults.confirmedAt
|
||||
? new Date(
|
||||
complianceDefaults.confirmedAt * 1000
|
||||
).toLocaleString()
|
||||
: '-',
|
||||
userId: complianceDefaults.confirmedBy || '-',
|
||||
})}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<RiskAcknowledgementDialog
|
||||
open={showComplianceDialog}
|
||||
onOpenChange={setShowComplianceDialog}
|
||||
title={t('Confirm compliance terms')}
|
||||
description={t(
|
||||
'This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.'
|
||||
)}
|
||||
items={complianceStatements}
|
||||
requiredText={complianceRequiredText}
|
||||
requiredTextParts={complianceRequiredTextParts}
|
||||
inputPrompt={t('Please type the following text to confirm:')}
|
||||
inputPlaceholder={t('Type the confirmation text here')}
|
||||
mismatchHint={t('The entered text does not match the required text.')}
|
||||
confirmText={t('Confirm and enable')}
|
||||
isLoading={confirmComplianceMutation.isPending}
|
||||
onConfirm={() => confirmComplianceMutation.mutate()}
|
||||
/>
|
||||
|
||||
{/* eslint-disable react-hooks/refs */}
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className='space-y-8'
|
||||
className={cn(
|
||||
'space-y-8',
|
||||
!complianceConfirmed && 'pointer-events-none opacity-40'
|
||||
)}
|
||||
data-no-autosubmit='true'
|
||||
>
|
||||
<div className='space-y-4'>
|
||||
|
||||
@ -39,6 +39,17 @@ export type UpdateOptionResponse = {
|
||||
message: string
|
||||
}
|
||||
|
||||
export type ConfirmPaymentComplianceResponse = {
|
||||
success: boolean
|
||||
message: string
|
||||
data?: {
|
||||
confirmed: boolean
|
||||
terms_version: string
|
||||
confirmed_at: number
|
||||
confirmed_by: number
|
||||
}
|
||||
}
|
||||
|
||||
export type DeleteLogsResponse = {
|
||||
success: boolean
|
||||
message: string
|
||||
@ -215,6 +226,11 @@ export type BillingSettings = {
|
||||
PayMethods: string
|
||||
'payment_setting.amount_options': string
|
||||
'payment_setting.amount_discount': string
|
||||
'payment_setting.compliance_confirmed': boolean
|
||||
'payment_setting.compliance_terms_version': string
|
||||
'payment_setting.compliance_confirmed_at': number
|
||||
'payment_setting.compliance_confirmed_by': number
|
||||
'payment_setting.compliance_confirmed_ip': string
|
||||
StripeApiSecret: string
|
||||
StripeWebhookSecret: string
|
||||
StripePriceId: string
|
||||
|
||||
@ -30,6 +30,7 @@ interface AffiliateRewardsCardProps {
|
||||
user: UserWalletData | null
|
||||
affiliateLink: string
|
||||
onTransfer: () => void
|
||||
complianceConfirmed?: boolean
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
@ -37,6 +38,7 @@ export function AffiliateRewardsCard({
|
||||
user,
|
||||
affiliateLink,
|
||||
onTransfer,
|
||||
complianceConfirmed = true,
|
||||
loading,
|
||||
}: AffiliateRewardsCardProps) {
|
||||
const { t } = useTranslation()
|
||||
@ -110,6 +112,7 @@ export function AffiliateRewardsCard({
|
||||
{hasRewards && (
|
||||
<Button
|
||||
onClick={onTransfer}
|
||||
disabled={!complianceConfirmed}
|
||||
className='h-9 shrink-0 px-3'
|
||||
size='sm'
|
||||
>
|
||||
@ -117,6 +120,13 @@ export function AffiliateRewardsCard({
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{!complianceConfirmed ? (
|
||||
<p className='text-muted-foreground text-xs lg:col-span-3'>
|
||||
{t(
|
||||
'Referral reward transfer is disabled until the administrator confirms compliance terms.'
|
||||
)}
|
||||
</p>
|
||||
) : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
||||
@ -135,6 +135,7 @@ export function RechargeFormCard({
|
||||
const hasWaffoPaymentMethods =
|
||||
Array.isArray(waffoPayMethods) && waffoPayMethods.length > 0
|
||||
const minTopup = getMinTopupAmount(topupInfo)
|
||||
const redemptionEnabled = topupInfo?.enable_redemption !== false
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@ -445,49 +446,59 @@ export function RechargeFormCard({
|
||||
)}
|
||||
|
||||
{/* Redemption Code Section */}
|
||||
<div className='space-y-2.5 border-t pt-4 sm:space-y-3 sm:pt-6'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Gift className='text-muted-foreground h-4 w-4' />
|
||||
<Label
|
||||
htmlFor='redemption-code'
|
||||
className='text-muted-foreground text-xs font-medium tracking-wider uppercase'
|
||||
>
|
||||
{t('Have a Code?')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className='grid grid-cols-[minmax(0,1fr)_auto] gap-2'>
|
||||
<Input
|
||||
id='redemption-code'
|
||||
value={redemptionCode}
|
||||
onChange={(e) => onRedemptionCodeChange(e.target.value)}
|
||||
placeholder={t('Enter your redemption code')}
|
||||
className='h-9 min-w-0'
|
||||
/>
|
||||
<Button
|
||||
onClick={onRedeem}
|
||||
disabled={redeeming}
|
||||
variant='outline'
|
||||
className='h-9 px-4'
|
||||
>
|
||||
{redeeming && <Loader2 className='mr-2 h-4 w-4 animate-spin' />}
|
||||
{t('Redeem')}
|
||||
</Button>
|
||||
</div>
|
||||
{topupLink && (
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{t('Need a redemption code?')}{' '}
|
||||
<a
|
||||
href={topupLink}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='inline-flex items-center gap-1 underline-offset-4 hover:underline'
|
||||
{redemptionEnabled ? (
|
||||
<div className='space-y-2.5 border-t pt-4 sm:space-y-3 sm:pt-6'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Gift className='text-muted-foreground h-4 w-4' />
|
||||
<Label
|
||||
htmlFor='redemption-code'
|
||||
className='text-muted-foreground text-xs font-medium tracking-wider uppercase'
|
||||
>
|
||||
{t('Get one here')}
|
||||
<ExternalLink className='h-3 w-3' />
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{t('Have a Code?')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className='grid grid-cols-[minmax(0,1fr)_auto] gap-2'>
|
||||
<Input
|
||||
id='redemption-code'
|
||||
value={redemptionCode}
|
||||
onChange={(e) => onRedemptionCodeChange(e.target.value)}
|
||||
placeholder={t('Enter your redemption code')}
|
||||
className='h-9 min-w-0'
|
||||
/>
|
||||
<Button
|
||||
onClick={onRedeem}
|
||||
disabled={redeeming}
|
||||
variant='outline'
|
||||
className='h-9 px-4'
|
||||
>
|
||||
{redeeming && <Loader2 className='mr-2 h-4 w-4 animate-spin' />}
|
||||
{t('Redeem')}
|
||||
</Button>
|
||||
</div>
|
||||
{topupLink && (
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{t('Need a redemption code?')}{' '}
|
||||
<a
|
||||
href={topupLink}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='inline-flex items-center gap-1 underline-offset-4 hover:underline'
|
||||
>
|
||||
{t('Get one here')}
|
||||
<ExternalLink className='h-3 w-3' />
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Alert className='border-t'>
|
||||
<AlertDescription>
|
||||
{t(
|
||||
'Redemption codes are disabled until the administrator confirms compliance terms.'
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</TitledCard>
|
||||
)
|
||||
}
|
||||
|
||||
3
web/default/src/features/wallet/index.tsx
vendored
3
web/default/src/features/wallet/index.tsx
vendored
@ -319,6 +319,9 @@ export function Wallet(props: WalletProps) {
|
||||
user={user}
|
||||
affiliateLink={affiliateLink}
|
||||
onTransfer={() => setTransferDialogOpen(true)}
|
||||
complianceConfirmed={
|
||||
topupInfo?.payment_compliance_confirmed !== false
|
||||
}
|
||||
loading={affiliateLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
6
web/default/src/features/wallet/types.ts
vendored
6
web/default/src/features/wallet/types.ts
vendored
@ -145,6 +145,12 @@ export interface TopupInfo {
|
||||
enable_waffo_pancake_topup?: boolean
|
||||
/** Minimum topup amount for Waffo Pancake */
|
||||
waffo_pancake_min_topup?: number
|
||||
/** Whether redemption code usage is enabled */
|
||||
enable_redemption?: boolean
|
||||
/** Whether compliance confirmation has been completed */
|
||||
payment_compliance_confirmed?: boolean
|
||||
/** Current compliance terms version */
|
||||
payment_compliance_terms_version?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
35
web/default/src/i18n/locales/en.json
vendored
35
web/default/src/i18n/locales/en.json
vendored
@ -1756,7 +1756,7 @@
|
||||
"footer.columns.related.links.oneApi": "One API",
|
||||
"footer.columns.related.title": "Related Projects",
|
||||
"footer.defaultCopyright": "All rights reserved.",
|
||||
"footer.new\u0061pi.projectAttributionSuffix": "All rights reserved. Designed and developed by the project contributors.",
|
||||
"footer.newapi.projectAttributionSuffix": "All rights reserved. Designed and developed by the project contributors.",
|
||||
"For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "For channels added after May 10, 2025, no need to remove \".\" from model names during deployment",
|
||||
"For private deployments, format: https://fastgpt.run/api/openapi": "For private deployments, format: https://fastgpt.run/api/openapi",
|
||||
"Force a syntactically valid JSON response": "Force a syntactically valid JSON response",
|
||||
@ -4423,6 +4423,37 @@
|
||||
"Zero retention": "Zero retention",
|
||||
"Zhipu": "Zhipu",
|
||||
"Zhipu V4": "Zhipu V4",
|
||||
"Zoom": "Zoom"
|
||||
"Zoom": "Zoom",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"Please type the following text to confirm:": "Please type the following text to confirm:",
|
||||
"Type the confirmation text here": "Type the confirmation text here",
|
||||
"The entered text does not match the required text.": "The entered text does not match the required text.",
|
||||
"Compliance confirmation required": "Compliance confirmation required",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.",
|
||||
"Confirm compliance": "Confirm compliance",
|
||||
"Compliance confirmed": "Compliance confirmed",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"Confirm compliance terms": "Confirm compliance terms",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
|
||||
"Confirm and enable": "Confirm and enable",
|
||||
"Compliance confirmed successfully": "Compliance confirmed successfully",
|
||||
"Failed to confirm compliance": "Failed to confirm compliance",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Referral reward transfer is disabled until the administrator confirms compliance terms.",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.",
|
||||
"I have read and understood the above compliance reminder": "I have read and understood the above compliance reminder",
|
||||
"acknowledge the related legal risks": "acknowledge the related legal risks",
|
||||
"confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
|
||||
"operation and charging behavior": "operation and charging behavior",
|
||||
",": ", ",
|
||||
",and ": ", and ",
|
||||
"、": ", "
|
||||
}
|
||||
}
|
||||
|
||||
35
web/default/src/i18n/locales/fr.json
vendored
35
web/default/src/i18n/locales/fr.json
vendored
@ -1756,7 +1756,7 @@
|
||||
"footer.columns.related.links.oneApi": "One API",
|
||||
"footer.columns.related.title": "Projets liés",
|
||||
"footer.defaultCopyright": "Tous droits réservés.",
|
||||
"footer.new\u0061pi.projectAttributionSuffix": "Tous droits réservés. Conçu et développé par les contributeurs du projet.",
|
||||
"footer.newapi.projectAttributionSuffix": "Tous droits réservés. Conçu et développé par les contributeurs du projet.",
|
||||
"For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "Pour les canaux ajoutés après le 10 mai 2025, pas besoin de supprimer \".\" des noms de modèles lors du déploiement",
|
||||
"For private deployments, format: https://fastgpt.run/api/openapi": "Pour les déploiements privés, format : https://fastgpt.run/api/openapi",
|
||||
"Force a syntactically valid JSON response": "Imposer une réponse JSON syntaxiquement valide",
|
||||
@ -4423,6 +4423,37 @@
|
||||
"Zero retention": "Aucune rétention",
|
||||
"Zhipu": "Zhipu",
|
||||
"Zhipu V4": "Zhipu V4",
|
||||
"Zoom": "Zoom"
|
||||
"Zoom": "Zoom",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"Please type the following text to confirm:": "Please type the following text to confirm:",
|
||||
"Type the confirmation text here": "Type the confirmation text here",
|
||||
"The entered text does not match the required text.": "The entered text does not match the required text.",
|
||||
"Compliance confirmation required": "Compliance confirmation required",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.",
|
||||
"Confirm compliance": "Confirm compliance",
|
||||
"Compliance confirmed": "Compliance confirmed",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"Confirm compliance terms": "Confirm compliance terms",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
|
||||
"Confirm and enable": "Confirm and enable",
|
||||
"Compliance confirmed successfully": "Compliance confirmed successfully",
|
||||
"Failed to confirm compliance": "Failed to confirm compliance",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Referral reward transfer is disabled until the administrator confirms compliance terms.",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.",
|
||||
"I have read and understood the above compliance reminder": "I have read and understood the above compliance reminder",
|
||||
"acknowledge the related legal risks": "acknowledge the related legal risks",
|
||||
"confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
|
||||
"operation and charging behavior": "operation and charging behavior",
|
||||
",": ", ",
|
||||
",and ": ", and ",
|
||||
"、": ", "
|
||||
}
|
||||
}
|
||||
|
||||
35
web/default/src/i18n/locales/ja.json
vendored
35
web/default/src/i18n/locales/ja.json
vendored
@ -1756,7 +1756,7 @@
|
||||
"footer.columns.related.links.oneApi": "1つのAPI",
|
||||
"footer.columns.related.title": "関連プロジェクト",
|
||||
"footer.defaultCopyright": "すべての権利を留保します。",
|
||||
"footer.new\u0061pi.projectAttributionSuffix": "すべての権利を留保します。プロジェクトコントリビューターにより設計・開発されています。",
|
||||
"footer.newapi.projectAttributionSuffix": "すべての権利を留保します。プロジェクトコントリビューターにより設計・開発されています。",
|
||||
"For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "2025 年 5 月 10 日以降に追加されたチャネルの場合、デプロイ時にモデル名から「.」を削除する必要はありません",
|
||||
"For private deployments, format: https://fastgpt.run/api/openapi": "プライベートデプロイメントの場合、形式: https://fastgpt.run/api/openapi",
|
||||
"Force a syntactically valid JSON response": "構文的に有効な JSON 応答を強制",
|
||||
@ -4423,6 +4423,37 @@
|
||||
"Zero retention": "データ保持なし",
|
||||
"Zhipu": "Zhipu",
|
||||
"Zhipu V4": "Zhipu V 4",
|
||||
"Zoom": "ズーム"
|
||||
"Zoom": "ズーム",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"Please type the following text to confirm:": "Please type the following text to confirm:",
|
||||
"Type the confirmation text here": "Type the confirmation text here",
|
||||
"The entered text does not match the required text.": "The entered text does not match the required text.",
|
||||
"Compliance confirmation required": "Compliance confirmation required",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.",
|
||||
"Confirm compliance": "Confirm compliance",
|
||||
"Compliance confirmed": "Compliance confirmed",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"Confirm compliance terms": "Confirm compliance terms",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
|
||||
"Confirm and enable": "Confirm and enable",
|
||||
"Compliance confirmed successfully": "Compliance confirmed successfully",
|
||||
"Failed to confirm compliance": "Failed to confirm compliance",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Referral reward transfer is disabled until the administrator confirms compliance terms.",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.",
|
||||
"I have read and understood the above compliance reminder": "I have read and understood the above compliance reminder",
|
||||
"acknowledge the related legal risks": "acknowledge the related legal risks",
|
||||
"confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
|
||||
"operation and charging behavior": "operation and charging behavior",
|
||||
",": ", ",
|
||||
",and ": ", and ",
|
||||
"、": ", "
|
||||
}
|
||||
}
|
||||
|
||||
35
web/default/src/i18n/locales/ru.json
vendored
35
web/default/src/i18n/locales/ru.json
vendored
@ -1756,7 +1756,7 @@
|
||||
"footer.columns.related.links.oneApi": "Один API",
|
||||
"footer.columns.related.title": "Связанные проекты",
|
||||
"footer.defaultCopyright": "Все права защищены.",
|
||||
"footer.new\u0061pi.projectAttributionSuffix": "Все права защищены. Разработано участниками проекта.",
|
||||
"footer.newapi.projectAttributionSuffix": "Все права защищены. Разработано участниками проекта.",
|
||||
"For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "Для каналов, добавленных после 10 мая 2025 г., не нужно удалять \".\" из имён моделей при развёртывании",
|
||||
"For private deployments, format: https://fastgpt.run/api/openapi": "Для частных развертываний, формат: https://fastgpt.run/api/openapi",
|
||||
"Force a syntactically valid JSON response": "Принудительно возвращать синтаксически корректный JSON",
|
||||
@ -4423,6 +4423,37 @@
|
||||
"Zero retention": "Без хранения данных",
|
||||
"Zhipu": "Zhipu",
|
||||
"Zhipu V4": "Zhipu V4",
|
||||
"Zoom": "Zoom"
|
||||
"Zoom": "Zoom",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"Please type the following text to confirm:": "Please type the following text to confirm:",
|
||||
"Type the confirmation text here": "Type the confirmation text here",
|
||||
"The entered text does not match the required text.": "The entered text does not match the required text.",
|
||||
"Compliance confirmation required": "Compliance confirmation required",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.",
|
||||
"Confirm compliance": "Confirm compliance",
|
||||
"Compliance confirmed": "Compliance confirmed",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"Confirm compliance terms": "Confirm compliance terms",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
|
||||
"Confirm and enable": "Confirm and enable",
|
||||
"Compliance confirmed successfully": "Compliance confirmed successfully",
|
||||
"Failed to confirm compliance": "Failed to confirm compliance",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Referral reward transfer is disabled until the administrator confirms compliance terms.",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.",
|
||||
"I have read and understood the above compliance reminder": "I have read and understood the above compliance reminder",
|
||||
"acknowledge the related legal risks": "acknowledge the related legal risks",
|
||||
"confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
|
||||
"operation and charging behavior": "operation and charging behavior",
|
||||
",": ", ",
|
||||
",and ": ", and ",
|
||||
"、": ", "
|
||||
}
|
||||
}
|
||||
|
||||
35
web/default/src/i18n/locales/vi.json
vendored
35
web/default/src/i18n/locales/vi.json
vendored
@ -1756,7 +1756,7 @@
|
||||
"footer.columns.related.links.oneApi": "One API",
|
||||
"footer.columns.related.title": "Các Dự Án Liên Quan",
|
||||
"footer.defaultCopyright": "Bản quyền được bảo lưu.",
|
||||
"footer.new\u0061pi.projectAttributionSuffix": "Bản quyền được bảo lưu. Được thiết kế và phát triển bởi các cộng tác viên dự án.",
|
||||
"footer.newapi.projectAttributionSuffix": "Bản quyền được bảo lưu. Được thiết kế và phát triển bởi các cộng tác viên dự án.",
|
||||
"For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "Đối với các kênh được thêm sau ngày 10 tháng 5 năm 2025, không cần loại bỏ \".\" khỏi tên mô hình trong quá trình triển khai",
|
||||
"For private deployments, format: https://fastgpt.run/api/openapi": "Đối với các triển khai riêng tư, định dạng: https://fastgpt.run/api/openapi",
|
||||
"Force a syntactically valid JSON response": "Buộc phản hồi JSON hợp lệ về cú pháp",
|
||||
@ -4423,6 +4423,37 @@
|
||||
"Zero retention": "Không lưu dữ liệu",
|
||||
"Zhipu": "Zhipu",
|
||||
"Zhipu V4": "Zhipu V4",
|
||||
"Zoom": "Zoom"
|
||||
"Zoom": "Zoom",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"Please type the following text to confirm:": "Please type the following text to confirm:",
|
||||
"Type the confirmation text here": "Type the confirmation text here",
|
||||
"The entered text does not match the required text.": "The entered text does not match the required text.",
|
||||
"Compliance confirmation required": "Compliance confirmation required",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.",
|
||||
"Confirm compliance": "Confirm compliance",
|
||||
"Compliance confirmed": "Compliance confirmed",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"Confirm compliance terms": "Confirm compliance terms",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
|
||||
"Confirm and enable": "Confirm and enable",
|
||||
"Compliance confirmed successfully": "Compliance confirmed successfully",
|
||||
"Failed to confirm compliance": "Failed to confirm compliance",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Referral reward transfer is disabled until the administrator confirms compliance terms.",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.",
|
||||
"I have read and understood the above compliance reminder": "I have read and understood the above compliance reminder",
|
||||
"acknowledge the related legal risks": "acknowledge the related legal risks",
|
||||
"confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
|
||||
"operation and charging behavior": "operation and charging behavior",
|
||||
",": ", ",
|
||||
",and ": ", and ",
|
||||
"、": ", "
|
||||
}
|
||||
}
|
||||
|
||||
35
web/default/src/i18n/locales/zh.json
vendored
35
web/default/src/i18n/locales/zh.json
vendored
@ -1756,7 +1756,7 @@
|
||||
"footer.columns.related.links.oneApi": "One API",
|
||||
"footer.columns.related.title": "相关项目",
|
||||
"footer.defaultCopyright": "版权所有。",
|
||||
"footer.new\u0061pi.projectAttributionSuffix": "版权所有,由项目贡献者设计与开发。",
|
||||
"footer.newapi.projectAttributionSuffix": "版权所有,由项目贡献者设计与开发。",
|
||||
"For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "对于 2025 年 5 月 10 日之后添加的渠道,在部署时无需从模型名称中移除 \".\"",
|
||||
"For private deployments, format: https://fastgpt.run/api/openapi": "对于私有部署,格式为:https://fastgpt.run/api/openapi",
|
||||
"Force a syntactically valid JSON response": "强制返回语法合法的 JSON",
|
||||
@ -4423,6 +4423,37 @@
|
||||
"Zero retention": "零数据保留",
|
||||
"Zhipu": "智谱",
|
||||
"Zhipu V4": "智谱 V4",
|
||||
"Zoom": "缩放"
|
||||
"Zoom": "缩放",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "你已合法取得所接入模型 API、账号、密钥和额度的授权。",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务。",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "你理解并自行承担部署、运营和收费行为产生的法律责任。",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任",
|
||||
"Please type the following text to confirm:": "请输入以下文字以确认:",
|
||||
"Type the confirmation text here": "请输入确认文案",
|
||||
"The entered text does not match the required text.": "输入内容与要求文案不一致。",
|
||||
"Compliance confirmation required": "需要确认合规声明",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。",
|
||||
"Confirm compliance": "确认合规声明",
|
||||
"Compliance confirmed": "合规声明已确认",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "确认时间:{{time}},确认用户:#{{userId}}",
|
||||
"Confirm compliance terms": "确认合规声明",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。",
|
||||
"Confirm and enable": "确认并启用",
|
||||
"Compliance confirmed successfully": "合规声明确认成功",
|
||||
"Failed to confirm compliance": "确认失败",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "兑换码功能已禁用,管理员需先确认合规声明。",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "邀请奖励划转已禁用,管理员需先确认合规声明。",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。",
|
||||
"I have read and understood the above compliance reminder": "我已阅读并理解上述合规提醒",
|
||||
"acknowledge the related legal risks": "知悉相关法律风险",
|
||||
"confirm that I bear legal responsibility arising from deployment": "并确认自行承担部署",
|
||||
"operation and charging behavior": "运营和收费行为产生的法律责任",
|
||||
",": ",",
|
||||
",and ": ",",
|
||||
"、": "、"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user