mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-21 14:19:18 +08:00
Merge pull request #3775 from heathermhuang/codex/grok-media-pricing-labels
fix: add Grok video pricing controls
This commit is contained in:
+66
-2
@@ -73,6 +73,16 @@ type Group struct {
|
||||
BatchImageDiscountMultiplier float64 `json:"batch_image_discount_multiplier,omitempty"`
|
||||
// 批量图片生成冻结价格比例,按普通生图原价乘以该比例冻结,结算后释放差额
|
||||
BatchImageHoldMultiplier float64 `json:"batch_image_hold_multiplier,omitempty"`
|
||||
// 视频生成是否使用独立倍率;false 表示共享分组有效倍率
|
||||
VideoRateIndependent bool `json:"video_rate_independent,omitempty"`
|
||||
// 视频生成独立倍率,仅 video_rate_independent=true 时生效
|
||||
VideoRateMultiplier float64 `json:"video_rate_multiplier,omitempty"`
|
||||
// VideoPrice480p holds the value of the "video_price_480p" field.
|
||||
VideoPrice480p *float64 `json:"video_price_480p,omitempty"`
|
||||
// VideoPrice720p holds the value of the "video_price_720p" field.
|
||||
VideoPrice720p *float64 `json:"video_price_720p,omitempty"`
|
||||
// VideoPrice1080p holds the value of the "video_price_1080p" field.
|
||||
VideoPrice1080p *float64 `json:"video_price_1080p,omitempty"`
|
||||
// 是否仅允许 Claude Code 客户端
|
||||
ClaudeCodeOnly bool `json:"claude_code_only,omitempty"`
|
||||
// 非 Claude Code 请求降级使用的分组 ID
|
||||
@@ -211,9 +221,9 @@ func (*Group) scanValues(columns []string) ([]any, error) {
|
||||
switch columns[i] {
|
||||
case group.FieldModelRouting, group.FieldSupportedModelScopes, group.FieldMessagesDispatchModelConfig, group.FieldModelsListConfig:
|
||||
values[i] = new([]byte)
|
||||
case group.FieldPeakRateEnabled, group.FieldIsExclusive, group.FieldAllowImageGeneration, group.FieldAllowBatchImageGeneration, group.FieldImageRateIndependent, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject, group.FieldAllowMessagesDispatch, group.FieldRequireOauthOnly, group.FieldRequirePrivacySet:
|
||||
case group.FieldPeakRateEnabled, group.FieldIsExclusive, group.FieldAllowImageGeneration, group.FieldAllowBatchImageGeneration, group.FieldImageRateIndependent, group.FieldVideoRateIndependent, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject, group.FieldAllowMessagesDispatch, group.FieldRequireOauthOnly, group.FieldRequirePrivacySet:
|
||||
values[i] = new(sql.NullBool)
|
||||
case group.FieldRateMultiplier, group.FieldPeakRateMultiplier, group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, group.FieldImageRateMultiplier, group.FieldImagePrice1k, group.FieldImagePrice2k, group.FieldImagePrice4k, group.FieldBatchImageDiscountMultiplier, group.FieldBatchImageHoldMultiplier:
|
||||
case group.FieldRateMultiplier, group.FieldPeakRateMultiplier, group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, group.FieldImageRateMultiplier, group.FieldImagePrice1k, group.FieldImagePrice2k, group.FieldImagePrice4k, group.FieldBatchImageDiscountMultiplier, group.FieldBatchImageHoldMultiplier, group.FieldVideoRateMultiplier, group.FieldVideoPrice480p, group.FieldVideoPrice720p, group.FieldVideoPrice1080p:
|
||||
values[i] = new(sql.NullFloat64)
|
||||
case group.FieldID, group.FieldDefaultValidityDays, group.FieldFallbackGroupID, group.FieldFallbackGroupIDOnInvalidRequest, group.FieldSortOrder, group.FieldRpmLimit:
|
||||
values[i] = new(sql.NullInt64)
|
||||
@@ -412,6 +422,39 @@ func (_m *Group) assignValues(columns []string, values []any) error {
|
||||
} else if value.Valid {
|
||||
_m.BatchImageHoldMultiplier = value.Float64
|
||||
}
|
||||
case group.FieldVideoRateIndependent:
|
||||
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field video_rate_independent", values[i])
|
||||
} else if value.Valid {
|
||||
_m.VideoRateIndependent = value.Bool
|
||||
}
|
||||
case group.FieldVideoRateMultiplier:
|
||||
if value, ok := values[i].(*sql.NullFloat64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field video_rate_multiplier", values[i])
|
||||
} else if value.Valid {
|
||||
_m.VideoRateMultiplier = value.Float64
|
||||
}
|
||||
case group.FieldVideoPrice480p:
|
||||
if value, ok := values[i].(*sql.NullFloat64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field video_price_480p", values[i])
|
||||
} else if value.Valid {
|
||||
_m.VideoPrice480p = new(float64)
|
||||
*_m.VideoPrice480p = value.Float64
|
||||
}
|
||||
case group.FieldVideoPrice720p:
|
||||
if value, ok := values[i].(*sql.NullFloat64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field video_price_720p", values[i])
|
||||
} else if value.Valid {
|
||||
_m.VideoPrice720p = new(float64)
|
||||
*_m.VideoPrice720p = value.Float64
|
||||
}
|
||||
case group.FieldVideoPrice1080p:
|
||||
if value, ok := values[i].(*sql.NullFloat64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field video_price_1080p", values[i])
|
||||
} else if value.Valid {
|
||||
_m.VideoPrice1080p = new(float64)
|
||||
*_m.VideoPrice1080p = value.Float64
|
||||
}
|
||||
case group.FieldClaudeCodeOnly:
|
||||
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field claude_code_only", values[i])
|
||||
@@ -685,6 +728,27 @@ func (_m *Group) String() string {
|
||||
builder.WriteString("batch_image_hold_multiplier=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.BatchImageHoldMultiplier))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("video_rate_independent=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.VideoRateIndependent))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("video_rate_multiplier=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.VideoRateMultiplier))
|
||||
builder.WriteString(", ")
|
||||
if v := _m.VideoPrice480p; v != nil {
|
||||
builder.WriteString("video_price_480p=")
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.VideoPrice720p; v != nil {
|
||||
builder.WriteString("video_price_720p=")
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.VideoPrice1080p; v != nil {
|
||||
builder.WriteString("video_price_1080p=")
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("claude_code_only=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.ClaudeCodeOnly))
|
||||
builder.WriteString(", ")
|
||||
|
||||
@@ -70,6 +70,16 @@ const (
|
||||
FieldBatchImageDiscountMultiplier = "batch_image_discount_multiplier"
|
||||
// FieldBatchImageHoldMultiplier holds the string denoting the batch_image_hold_multiplier field in the database.
|
||||
FieldBatchImageHoldMultiplier = "batch_image_hold_multiplier"
|
||||
// FieldVideoRateIndependent holds the string denoting the video_rate_independent field in the database.
|
||||
FieldVideoRateIndependent = "video_rate_independent"
|
||||
// FieldVideoRateMultiplier holds the string denoting the video_rate_multiplier field in the database.
|
||||
FieldVideoRateMultiplier = "video_rate_multiplier"
|
||||
// FieldVideoPrice480p holds the string denoting the video_price_480p field in the database.
|
||||
FieldVideoPrice480p = "video_price_480p"
|
||||
// FieldVideoPrice720p holds the string denoting the video_price_720p field in the database.
|
||||
FieldVideoPrice720p = "video_price_720p"
|
||||
// FieldVideoPrice1080p holds the string denoting the video_price_1080p field in the database.
|
||||
FieldVideoPrice1080p = "video_price_1080p"
|
||||
// FieldClaudeCodeOnly holds the string denoting the claude_code_only field in the database.
|
||||
FieldClaudeCodeOnly = "claude_code_only"
|
||||
// FieldFallbackGroupID holds the string denoting the fallback_group_id field in the database.
|
||||
@@ -202,6 +212,11 @@ var Columns = []string{
|
||||
FieldImagePrice4k,
|
||||
FieldBatchImageDiscountMultiplier,
|
||||
FieldBatchImageHoldMultiplier,
|
||||
FieldVideoRateIndependent,
|
||||
FieldVideoRateMultiplier,
|
||||
FieldVideoPrice480p,
|
||||
FieldVideoPrice720p,
|
||||
FieldVideoPrice1080p,
|
||||
FieldClaudeCodeOnly,
|
||||
FieldFallbackGroupID,
|
||||
FieldFallbackGroupIDOnInvalidRequest,
|
||||
@@ -296,6 +311,10 @@ var (
|
||||
DefaultBatchImageDiscountMultiplier float64
|
||||
// DefaultBatchImageHoldMultiplier holds the default value on creation for the "batch_image_hold_multiplier" field.
|
||||
DefaultBatchImageHoldMultiplier float64
|
||||
// DefaultVideoRateIndependent holds the default value on creation for the "video_rate_independent" field.
|
||||
DefaultVideoRateIndependent bool
|
||||
// DefaultVideoRateMultiplier holds the default value on creation for the "video_rate_multiplier" field.
|
||||
DefaultVideoRateMultiplier float64
|
||||
// DefaultClaudeCodeOnly holds the default value on creation for the "claude_code_only" field.
|
||||
DefaultClaudeCodeOnly bool
|
||||
// DefaultModelRoutingEnabled holds the default value on creation for the "model_routing_enabled" field.
|
||||
@@ -467,6 +486,31 @@ func ByBatchImageHoldMultiplier(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldBatchImageHoldMultiplier, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByVideoRateIndependent orders the results by the video_rate_independent field.
|
||||
func ByVideoRateIndependent(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldVideoRateIndependent, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByVideoRateMultiplier orders the results by the video_rate_multiplier field.
|
||||
func ByVideoRateMultiplier(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldVideoRateMultiplier, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByVideoPrice480p orders the results by the video_price_480p field.
|
||||
func ByVideoPrice480p(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldVideoPrice480p, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByVideoPrice720p orders the results by the video_price_720p field.
|
||||
func ByVideoPrice720p(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldVideoPrice720p, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByVideoPrice1080p orders the results by the video_price_1080p field.
|
||||
func ByVideoPrice1080p(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldVideoPrice1080p, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByClaudeCodeOnly orders the results by the claude_code_only field.
|
||||
func ByClaudeCodeOnly(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldClaudeCodeOnly, opts...).ToFunc()
|
||||
|
||||
@@ -190,6 +190,31 @@ func BatchImageHoldMultiplier(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldBatchImageHoldMultiplier, v))
|
||||
}
|
||||
|
||||
// VideoRateIndependent applies equality check predicate on the "video_rate_independent" field. It's identical to VideoRateIndependentEQ.
|
||||
func VideoRateIndependent(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldVideoRateIndependent, v))
|
||||
}
|
||||
|
||||
// VideoRateMultiplier applies equality check predicate on the "video_rate_multiplier" field. It's identical to VideoRateMultiplierEQ.
|
||||
func VideoRateMultiplier(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldVideoRateMultiplier, v))
|
||||
}
|
||||
|
||||
// VideoPrice480p applies equality check predicate on the "video_price_480p" field. It's identical to VideoPrice480pEQ.
|
||||
func VideoPrice480p(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldVideoPrice480p, v))
|
||||
}
|
||||
|
||||
// VideoPrice720p applies equality check predicate on the "video_price_720p" field. It's identical to VideoPrice720pEQ.
|
||||
func VideoPrice720p(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldVideoPrice720p, v))
|
||||
}
|
||||
|
||||
// VideoPrice1080p applies equality check predicate on the "video_price_1080p" field. It's identical to VideoPrice1080pEQ.
|
||||
func VideoPrice1080p(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldVideoPrice1080p, v))
|
||||
}
|
||||
|
||||
// ClaudeCodeOnly applies equality check predicate on the "claude_code_only" field. It's identical to ClaudeCodeOnlyEQ.
|
||||
func ClaudeCodeOnly(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldClaudeCodeOnly, v))
|
||||
@@ -1430,6 +1455,206 @@ func BatchImageHoldMultiplierLTE(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldLTE(FieldBatchImageHoldMultiplier, v))
|
||||
}
|
||||
|
||||
// VideoRateIndependentEQ applies the EQ predicate on the "video_rate_independent" field.
|
||||
func VideoRateIndependentEQ(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldVideoRateIndependent, v))
|
||||
}
|
||||
|
||||
// VideoRateIndependentNEQ applies the NEQ predicate on the "video_rate_independent" field.
|
||||
func VideoRateIndependentNEQ(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldNEQ(FieldVideoRateIndependent, v))
|
||||
}
|
||||
|
||||
// VideoRateMultiplierEQ applies the EQ predicate on the "video_rate_multiplier" field.
|
||||
func VideoRateMultiplierEQ(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldVideoRateMultiplier, v))
|
||||
}
|
||||
|
||||
// VideoRateMultiplierNEQ applies the NEQ predicate on the "video_rate_multiplier" field.
|
||||
func VideoRateMultiplierNEQ(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldNEQ(FieldVideoRateMultiplier, v))
|
||||
}
|
||||
|
||||
// VideoRateMultiplierIn applies the In predicate on the "video_rate_multiplier" field.
|
||||
func VideoRateMultiplierIn(vs ...float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldIn(FieldVideoRateMultiplier, vs...))
|
||||
}
|
||||
|
||||
// VideoRateMultiplierNotIn applies the NotIn predicate on the "video_rate_multiplier" field.
|
||||
func VideoRateMultiplierNotIn(vs ...float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldNotIn(FieldVideoRateMultiplier, vs...))
|
||||
}
|
||||
|
||||
// VideoRateMultiplierGT applies the GT predicate on the "video_rate_multiplier" field.
|
||||
func VideoRateMultiplierGT(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldGT(FieldVideoRateMultiplier, v))
|
||||
}
|
||||
|
||||
// VideoRateMultiplierGTE applies the GTE predicate on the "video_rate_multiplier" field.
|
||||
func VideoRateMultiplierGTE(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldGTE(FieldVideoRateMultiplier, v))
|
||||
}
|
||||
|
||||
// VideoRateMultiplierLT applies the LT predicate on the "video_rate_multiplier" field.
|
||||
func VideoRateMultiplierLT(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldLT(FieldVideoRateMultiplier, v))
|
||||
}
|
||||
|
||||
// VideoRateMultiplierLTE applies the LTE predicate on the "video_rate_multiplier" field.
|
||||
func VideoRateMultiplierLTE(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldLTE(FieldVideoRateMultiplier, v))
|
||||
}
|
||||
|
||||
// VideoPrice480pEQ applies the EQ predicate on the "video_price_480p" field.
|
||||
func VideoPrice480pEQ(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldVideoPrice480p, v))
|
||||
}
|
||||
|
||||
// VideoPrice480pNEQ applies the NEQ predicate on the "video_price_480p" field.
|
||||
func VideoPrice480pNEQ(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldNEQ(FieldVideoPrice480p, v))
|
||||
}
|
||||
|
||||
// VideoPrice480pIn applies the In predicate on the "video_price_480p" field.
|
||||
func VideoPrice480pIn(vs ...float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldIn(FieldVideoPrice480p, vs...))
|
||||
}
|
||||
|
||||
// VideoPrice480pNotIn applies the NotIn predicate on the "video_price_480p" field.
|
||||
func VideoPrice480pNotIn(vs ...float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldNotIn(FieldVideoPrice480p, vs...))
|
||||
}
|
||||
|
||||
// VideoPrice480pGT applies the GT predicate on the "video_price_480p" field.
|
||||
func VideoPrice480pGT(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldGT(FieldVideoPrice480p, v))
|
||||
}
|
||||
|
||||
// VideoPrice480pGTE applies the GTE predicate on the "video_price_480p" field.
|
||||
func VideoPrice480pGTE(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldGTE(FieldVideoPrice480p, v))
|
||||
}
|
||||
|
||||
// VideoPrice480pLT applies the LT predicate on the "video_price_480p" field.
|
||||
func VideoPrice480pLT(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldLT(FieldVideoPrice480p, v))
|
||||
}
|
||||
|
||||
// VideoPrice480pLTE applies the LTE predicate on the "video_price_480p" field.
|
||||
func VideoPrice480pLTE(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldLTE(FieldVideoPrice480p, v))
|
||||
}
|
||||
|
||||
// VideoPrice480pIsNil applies the IsNil predicate on the "video_price_480p" field.
|
||||
func VideoPrice480pIsNil() predicate.Group {
|
||||
return predicate.Group(sql.FieldIsNull(FieldVideoPrice480p))
|
||||
}
|
||||
|
||||
// VideoPrice480pNotNil applies the NotNil predicate on the "video_price_480p" field.
|
||||
func VideoPrice480pNotNil() predicate.Group {
|
||||
return predicate.Group(sql.FieldNotNull(FieldVideoPrice480p))
|
||||
}
|
||||
|
||||
// VideoPrice720pEQ applies the EQ predicate on the "video_price_720p" field.
|
||||
func VideoPrice720pEQ(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldVideoPrice720p, v))
|
||||
}
|
||||
|
||||
// VideoPrice720pNEQ applies the NEQ predicate on the "video_price_720p" field.
|
||||
func VideoPrice720pNEQ(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldNEQ(FieldVideoPrice720p, v))
|
||||
}
|
||||
|
||||
// VideoPrice720pIn applies the In predicate on the "video_price_720p" field.
|
||||
func VideoPrice720pIn(vs ...float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldIn(FieldVideoPrice720p, vs...))
|
||||
}
|
||||
|
||||
// VideoPrice720pNotIn applies the NotIn predicate on the "video_price_720p" field.
|
||||
func VideoPrice720pNotIn(vs ...float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldNotIn(FieldVideoPrice720p, vs...))
|
||||
}
|
||||
|
||||
// VideoPrice720pGT applies the GT predicate on the "video_price_720p" field.
|
||||
func VideoPrice720pGT(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldGT(FieldVideoPrice720p, v))
|
||||
}
|
||||
|
||||
// VideoPrice720pGTE applies the GTE predicate on the "video_price_720p" field.
|
||||
func VideoPrice720pGTE(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldGTE(FieldVideoPrice720p, v))
|
||||
}
|
||||
|
||||
// VideoPrice720pLT applies the LT predicate on the "video_price_720p" field.
|
||||
func VideoPrice720pLT(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldLT(FieldVideoPrice720p, v))
|
||||
}
|
||||
|
||||
// VideoPrice720pLTE applies the LTE predicate on the "video_price_720p" field.
|
||||
func VideoPrice720pLTE(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldLTE(FieldVideoPrice720p, v))
|
||||
}
|
||||
|
||||
// VideoPrice720pIsNil applies the IsNil predicate on the "video_price_720p" field.
|
||||
func VideoPrice720pIsNil() predicate.Group {
|
||||
return predicate.Group(sql.FieldIsNull(FieldVideoPrice720p))
|
||||
}
|
||||
|
||||
// VideoPrice720pNotNil applies the NotNil predicate on the "video_price_720p" field.
|
||||
func VideoPrice720pNotNil() predicate.Group {
|
||||
return predicate.Group(sql.FieldNotNull(FieldVideoPrice720p))
|
||||
}
|
||||
|
||||
// VideoPrice1080pEQ applies the EQ predicate on the "video_price_1080p" field.
|
||||
func VideoPrice1080pEQ(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldVideoPrice1080p, v))
|
||||
}
|
||||
|
||||
// VideoPrice1080pNEQ applies the NEQ predicate on the "video_price_1080p" field.
|
||||
func VideoPrice1080pNEQ(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldNEQ(FieldVideoPrice1080p, v))
|
||||
}
|
||||
|
||||
// VideoPrice1080pIn applies the In predicate on the "video_price_1080p" field.
|
||||
func VideoPrice1080pIn(vs ...float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldIn(FieldVideoPrice1080p, vs...))
|
||||
}
|
||||
|
||||
// VideoPrice1080pNotIn applies the NotIn predicate on the "video_price_1080p" field.
|
||||
func VideoPrice1080pNotIn(vs ...float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldNotIn(FieldVideoPrice1080p, vs...))
|
||||
}
|
||||
|
||||
// VideoPrice1080pGT applies the GT predicate on the "video_price_1080p" field.
|
||||
func VideoPrice1080pGT(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldGT(FieldVideoPrice1080p, v))
|
||||
}
|
||||
|
||||
// VideoPrice1080pGTE applies the GTE predicate on the "video_price_1080p" field.
|
||||
func VideoPrice1080pGTE(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldGTE(FieldVideoPrice1080p, v))
|
||||
}
|
||||
|
||||
// VideoPrice1080pLT applies the LT predicate on the "video_price_1080p" field.
|
||||
func VideoPrice1080pLT(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldLT(FieldVideoPrice1080p, v))
|
||||
}
|
||||
|
||||
// VideoPrice1080pLTE applies the LTE predicate on the "video_price_1080p" field.
|
||||
func VideoPrice1080pLTE(v float64) predicate.Group {
|
||||
return predicate.Group(sql.FieldLTE(FieldVideoPrice1080p, v))
|
||||
}
|
||||
|
||||
// VideoPrice1080pIsNil applies the IsNil predicate on the "video_price_1080p" field.
|
||||
func VideoPrice1080pIsNil() predicate.Group {
|
||||
return predicate.Group(sql.FieldIsNull(FieldVideoPrice1080p))
|
||||
}
|
||||
|
||||
// VideoPrice1080pNotNil applies the NotNil predicate on the "video_price_1080p" field.
|
||||
func VideoPrice1080pNotNil() predicate.Group {
|
||||
return predicate.Group(sql.FieldNotNull(FieldVideoPrice1080p))
|
||||
}
|
||||
|
||||
// ClaudeCodeOnlyEQ applies the EQ predicate on the "claude_code_only" field.
|
||||
func ClaudeCodeOnlyEQ(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldClaudeCodeOnly, v))
|
||||
|
||||
@@ -399,6 +399,76 @@ func (_c *GroupCreate) SetNillableBatchImageHoldMultiplier(v *float64) *GroupCre
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetVideoRateIndependent sets the "video_rate_independent" field.
|
||||
func (_c *GroupCreate) SetVideoRateIndependent(v bool) *GroupCreate {
|
||||
_c.mutation.SetVideoRateIndependent(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableVideoRateIndependent sets the "video_rate_independent" field if the given value is not nil.
|
||||
func (_c *GroupCreate) SetNillableVideoRateIndependent(v *bool) *GroupCreate {
|
||||
if v != nil {
|
||||
_c.SetVideoRateIndependent(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetVideoRateMultiplier sets the "video_rate_multiplier" field.
|
||||
func (_c *GroupCreate) SetVideoRateMultiplier(v float64) *GroupCreate {
|
||||
_c.mutation.SetVideoRateMultiplier(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableVideoRateMultiplier sets the "video_rate_multiplier" field if the given value is not nil.
|
||||
func (_c *GroupCreate) SetNillableVideoRateMultiplier(v *float64) *GroupCreate {
|
||||
if v != nil {
|
||||
_c.SetVideoRateMultiplier(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetVideoPrice480p sets the "video_price_480p" field.
|
||||
func (_c *GroupCreate) SetVideoPrice480p(v float64) *GroupCreate {
|
||||
_c.mutation.SetVideoPrice480p(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableVideoPrice480p sets the "video_price_480p" field if the given value is not nil.
|
||||
func (_c *GroupCreate) SetNillableVideoPrice480p(v *float64) *GroupCreate {
|
||||
if v != nil {
|
||||
_c.SetVideoPrice480p(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetVideoPrice720p sets the "video_price_720p" field.
|
||||
func (_c *GroupCreate) SetVideoPrice720p(v float64) *GroupCreate {
|
||||
_c.mutation.SetVideoPrice720p(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableVideoPrice720p sets the "video_price_720p" field if the given value is not nil.
|
||||
func (_c *GroupCreate) SetNillableVideoPrice720p(v *float64) *GroupCreate {
|
||||
if v != nil {
|
||||
_c.SetVideoPrice720p(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetVideoPrice1080p sets the "video_price_1080p" field.
|
||||
func (_c *GroupCreate) SetVideoPrice1080p(v float64) *GroupCreate {
|
||||
_c.mutation.SetVideoPrice1080p(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableVideoPrice1080p sets the "video_price_1080p" field if the given value is not nil.
|
||||
func (_c *GroupCreate) SetNillableVideoPrice1080p(v *float64) *GroupCreate {
|
||||
if v != nil {
|
||||
_c.SetVideoPrice1080p(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetClaudeCodeOnly sets the "claude_code_only" field.
|
||||
func (_c *GroupCreate) SetClaudeCodeOnly(v bool) *GroupCreate {
|
||||
_c.mutation.SetClaudeCodeOnly(v)
|
||||
@@ -798,6 +868,14 @@ func (_c *GroupCreate) defaults() error {
|
||||
v := group.DefaultBatchImageHoldMultiplier
|
||||
_c.mutation.SetBatchImageHoldMultiplier(v)
|
||||
}
|
||||
if _, ok := _c.mutation.VideoRateIndependent(); !ok {
|
||||
v := group.DefaultVideoRateIndependent
|
||||
_c.mutation.SetVideoRateIndependent(v)
|
||||
}
|
||||
if _, ok := _c.mutation.VideoRateMultiplier(); !ok {
|
||||
v := group.DefaultVideoRateMultiplier
|
||||
_c.mutation.SetVideoRateMultiplier(v)
|
||||
}
|
||||
if _, ok := _c.mutation.ClaudeCodeOnly(); !ok {
|
||||
v := group.DefaultClaudeCodeOnly
|
||||
_c.mutation.SetClaudeCodeOnly(v)
|
||||
@@ -938,6 +1016,12 @@ func (_c *GroupCreate) check() error {
|
||||
if _, ok := _c.mutation.BatchImageHoldMultiplier(); !ok {
|
||||
return &ValidationError{Name: "batch_image_hold_multiplier", err: errors.New(`ent: missing required field "Group.batch_image_hold_multiplier"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.VideoRateIndependent(); !ok {
|
||||
return &ValidationError{Name: "video_rate_independent", err: errors.New(`ent: missing required field "Group.video_rate_independent"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.VideoRateMultiplier(); !ok {
|
||||
return &ValidationError{Name: "video_rate_multiplier", err: errors.New(`ent: missing required field "Group.video_rate_multiplier"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.ClaudeCodeOnly(); !ok {
|
||||
return &ValidationError{Name: "claude_code_only", err: errors.New(`ent: missing required field "Group.claude_code_only"`)}
|
||||
}
|
||||
@@ -1114,6 +1198,26 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
|
||||
_spec.SetField(group.FieldBatchImageHoldMultiplier, field.TypeFloat64, value)
|
||||
_node.BatchImageHoldMultiplier = value
|
||||
}
|
||||
if value, ok := _c.mutation.VideoRateIndependent(); ok {
|
||||
_spec.SetField(group.FieldVideoRateIndependent, field.TypeBool, value)
|
||||
_node.VideoRateIndependent = value
|
||||
}
|
||||
if value, ok := _c.mutation.VideoRateMultiplier(); ok {
|
||||
_spec.SetField(group.FieldVideoRateMultiplier, field.TypeFloat64, value)
|
||||
_node.VideoRateMultiplier = value
|
||||
}
|
||||
if value, ok := _c.mutation.VideoPrice480p(); ok {
|
||||
_spec.SetField(group.FieldVideoPrice480p, field.TypeFloat64, value)
|
||||
_node.VideoPrice480p = &value
|
||||
}
|
||||
if value, ok := _c.mutation.VideoPrice720p(); ok {
|
||||
_spec.SetField(group.FieldVideoPrice720p, field.TypeFloat64, value)
|
||||
_node.VideoPrice720p = &value
|
||||
}
|
||||
if value, ok := _c.mutation.VideoPrice1080p(); ok {
|
||||
_spec.SetField(group.FieldVideoPrice1080p, field.TypeFloat64, value)
|
||||
_node.VideoPrice1080p = &value
|
||||
}
|
||||
if value, ok := _c.mutation.ClaudeCodeOnly(); ok {
|
||||
_spec.SetField(group.FieldClaudeCodeOnly, field.TypeBool, value)
|
||||
_node.ClaudeCodeOnly = value
|
||||
@@ -1762,6 +1866,108 @@ func (u *GroupUpsert) AddBatchImageHoldMultiplier(v float64) *GroupUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetVideoRateIndependent sets the "video_rate_independent" field.
|
||||
func (u *GroupUpsert) SetVideoRateIndependent(v bool) *GroupUpsert {
|
||||
u.Set(group.FieldVideoRateIndependent, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateVideoRateIndependent sets the "video_rate_independent" field to the value that was provided on create.
|
||||
func (u *GroupUpsert) UpdateVideoRateIndependent() *GroupUpsert {
|
||||
u.SetExcluded(group.FieldVideoRateIndependent)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetVideoRateMultiplier sets the "video_rate_multiplier" field.
|
||||
func (u *GroupUpsert) SetVideoRateMultiplier(v float64) *GroupUpsert {
|
||||
u.Set(group.FieldVideoRateMultiplier, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateVideoRateMultiplier sets the "video_rate_multiplier" field to the value that was provided on create.
|
||||
func (u *GroupUpsert) UpdateVideoRateMultiplier() *GroupUpsert {
|
||||
u.SetExcluded(group.FieldVideoRateMultiplier)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddVideoRateMultiplier adds v to the "video_rate_multiplier" field.
|
||||
func (u *GroupUpsert) AddVideoRateMultiplier(v float64) *GroupUpsert {
|
||||
u.Add(group.FieldVideoRateMultiplier, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetVideoPrice480p sets the "video_price_480p" field.
|
||||
func (u *GroupUpsert) SetVideoPrice480p(v float64) *GroupUpsert {
|
||||
u.Set(group.FieldVideoPrice480p, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateVideoPrice480p sets the "video_price_480p" field to the value that was provided on create.
|
||||
func (u *GroupUpsert) UpdateVideoPrice480p() *GroupUpsert {
|
||||
u.SetExcluded(group.FieldVideoPrice480p)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddVideoPrice480p adds v to the "video_price_480p" field.
|
||||
func (u *GroupUpsert) AddVideoPrice480p(v float64) *GroupUpsert {
|
||||
u.Add(group.FieldVideoPrice480p, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearVideoPrice480p clears the value of the "video_price_480p" field.
|
||||
func (u *GroupUpsert) ClearVideoPrice480p() *GroupUpsert {
|
||||
u.SetNull(group.FieldVideoPrice480p)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetVideoPrice720p sets the "video_price_720p" field.
|
||||
func (u *GroupUpsert) SetVideoPrice720p(v float64) *GroupUpsert {
|
||||
u.Set(group.FieldVideoPrice720p, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateVideoPrice720p sets the "video_price_720p" field to the value that was provided on create.
|
||||
func (u *GroupUpsert) UpdateVideoPrice720p() *GroupUpsert {
|
||||
u.SetExcluded(group.FieldVideoPrice720p)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddVideoPrice720p adds v to the "video_price_720p" field.
|
||||
func (u *GroupUpsert) AddVideoPrice720p(v float64) *GroupUpsert {
|
||||
u.Add(group.FieldVideoPrice720p, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearVideoPrice720p clears the value of the "video_price_720p" field.
|
||||
func (u *GroupUpsert) ClearVideoPrice720p() *GroupUpsert {
|
||||
u.SetNull(group.FieldVideoPrice720p)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetVideoPrice1080p sets the "video_price_1080p" field.
|
||||
func (u *GroupUpsert) SetVideoPrice1080p(v float64) *GroupUpsert {
|
||||
u.Set(group.FieldVideoPrice1080p, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateVideoPrice1080p sets the "video_price_1080p" field to the value that was provided on create.
|
||||
func (u *GroupUpsert) UpdateVideoPrice1080p() *GroupUpsert {
|
||||
u.SetExcluded(group.FieldVideoPrice1080p)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddVideoPrice1080p adds v to the "video_price_1080p" field.
|
||||
func (u *GroupUpsert) AddVideoPrice1080p(v float64) *GroupUpsert {
|
||||
u.Add(group.FieldVideoPrice1080p, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearVideoPrice1080p clears the value of the "video_price_1080p" field.
|
||||
func (u *GroupUpsert) ClearVideoPrice1080p() *GroupUpsert {
|
||||
u.SetNull(group.FieldVideoPrice1080p)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetClaudeCodeOnly sets the "claude_code_only" field.
|
||||
func (u *GroupUpsert) SetClaudeCodeOnly(v bool) *GroupUpsert {
|
||||
u.Set(group.FieldClaudeCodeOnly, v)
|
||||
@@ -2533,6 +2739,125 @@ func (u *GroupUpsertOne) UpdateBatchImageHoldMultiplier() *GroupUpsertOne {
|
||||
})
|
||||
}
|
||||
|
||||
// SetVideoRateIndependent sets the "video_rate_independent" field.
|
||||
func (u *GroupUpsertOne) SetVideoRateIndependent(v bool) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetVideoRateIndependent(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateVideoRateIndependent sets the "video_rate_independent" field to the value that was provided on create.
|
||||
func (u *GroupUpsertOne) UpdateVideoRateIndependent() *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateVideoRateIndependent()
|
||||
})
|
||||
}
|
||||
|
||||
// SetVideoRateMultiplier sets the "video_rate_multiplier" field.
|
||||
func (u *GroupUpsertOne) SetVideoRateMultiplier(v float64) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetVideoRateMultiplier(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddVideoRateMultiplier adds v to the "video_rate_multiplier" field.
|
||||
func (u *GroupUpsertOne) AddVideoRateMultiplier(v float64) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.AddVideoRateMultiplier(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateVideoRateMultiplier sets the "video_rate_multiplier" field to the value that was provided on create.
|
||||
func (u *GroupUpsertOne) UpdateVideoRateMultiplier() *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateVideoRateMultiplier()
|
||||
})
|
||||
}
|
||||
|
||||
// SetVideoPrice480p sets the "video_price_480p" field.
|
||||
func (u *GroupUpsertOne) SetVideoPrice480p(v float64) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetVideoPrice480p(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddVideoPrice480p adds v to the "video_price_480p" field.
|
||||
func (u *GroupUpsertOne) AddVideoPrice480p(v float64) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.AddVideoPrice480p(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateVideoPrice480p sets the "video_price_480p" field to the value that was provided on create.
|
||||
func (u *GroupUpsertOne) UpdateVideoPrice480p() *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateVideoPrice480p()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearVideoPrice480p clears the value of the "video_price_480p" field.
|
||||
func (u *GroupUpsertOne) ClearVideoPrice480p() *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.ClearVideoPrice480p()
|
||||
})
|
||||
}
|
||||
|
||||
// SetVideoPrice720p sets the "video_price_720p" field.
|
||||
func (u *GroupUpsertOne) SetVideoPrice720p(v float64) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetVideoPrice720p(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddVideoPrice720p adds v to the "video_price_720p" field.
|
||||
func (u *GroupUpsertOne) AddVideoPrice720p(v float64) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.AddVideoPrice720p(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateVideoPrice720p sets the "video_price_720p" field to the value that was provided on create.
|
||||
func (u *GroupUpsertOne) UpdateVideoPrice720p() *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateVideoPrice720p()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearVideoPrice720p clears the value of the "video_price_720p" field.
|
||||
func (u *GroupUpsertOne) ClearVideoPrice720p() *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.ClearVideoPrice720p()
|
||||
})
|
||||
}
|
||||
|
||||
// SetVideoPrice1080p sets the "video_price_1080p" field.
|
||||
func (u *GroupUpsertOne) SetVideoPrice1080p(v float64) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetVideoPrice1080p(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddVideoPrice1080p adds v to the "video_price_1080p" field.
|
||||
func (u *GroupUpsertOne) AddVideoPrice1080p(v float64) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.AddVideoPrice1080p(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateVideoPrice1080p sets the "video_price_1080p" field to the value that was provided on create.
|
||||
func (u *GroupUpsertOne) UpdateVideoPrice1080p() *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateVideoPrice1080p()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearVideoPrice1080p clears the value of the "video_price_1080p" field.
|
||||
func (u *GroupUpsertOne) ClearVideoPrice1080p() *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.ClearVideoPrice1080p()
|
||||
})
|
||||
}
|
||||
|
||||
// SetClaudeCodeOnly sets the "claude_code_only" field.
|
||||
func (u *GroupUpsertOne) SetClaudeCodeOnly(v bool) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
@@ -3507,6 +3832,125 @@ func (u *GroupUpsertBulk) UpdateBatchImageHoldMultiplier() *GroupUpsertBulk {
|
||||
})
|
||||
}
|
||||
|
||||
// SetVideoRateIndependent sets the "video_rate_independent" field.
|
||||
func (u *GroupUpsertBulk) SetVideoRateIndependent(v bool) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetVideoRateIndependent(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateVideoRateIndependent sets the "video_rate_independent" field to the value that was provided on create.
|
||||
func (u *GroupUpsertBulk) UpdateVideoRateIndependent() *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateVideoRateIndependent()
|
||||
})
|
||||
}
|
||||
|
||||
// SetVideoRateMultiplier sets the "video_rate_multiplier" field.
|
||||
func (u *GroupUpsertBulk) SetVideoRateMultiplier(v float64) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetVideoRateMultiplier(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddVideoRateMultiplier adds v to the "video_rate_multiplier" field.
|
||||
func (u *GroupUpsertBulk) AddVideoRateMultiplier(v float64) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.AddVideoRateMultiplier(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateVideoRateMultiplier sets the "video_rate_multiplier" field to the value that was provided on create.
|
||||
func (u *GroupUpsertBulk) UpdateVideoRateMultiplier() *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateVideoRateMultiplier()
|
||||
})
|
||||
}
|
||||
|
||||
// SetVideoPrice480p sets the "video_price_480p" field.
|
||||
func (u *GroupUpsertBulk) SetVideoPrice480p(v float64) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetVideoPrice480p(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddVideoPrice480p adds v to the "video_price_480p" field.
|
||||
func (u *GroupUpsertBulk) AddVideoPrice480p(v float64) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.AddVideoPrice480p(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateVideoPrice480p sets the "video_price_480p" field to the value that was provided on create.
|
||||
func (u *GroupUpsertBulk) UpdateVideoPrice480p() *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateVideoPrice480p()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearVideoPrice480p clears the value of the "video_price_480p" field.
|
||||
func (u *GroupUpsertBulk) ClearVideoPrice480p() *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.ClearVideoPrice480p()
|
||||
})
|
||||
}
|
||||
|
||||
// SetVideoPrice720p sets the "video_price_720p" field.
|
||||
func (u *GroupUpsertBulk) SetVideoPrice720p(v float64) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetVideoPrice720p(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddVideoPrice720p adds v to the "video_price_720p" field.
|
||||
func (u *GroupUpsertBulk) AddVideoPrice720p(v float64) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.AddVideoPrice720p(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateVideoPrice720p sets the "video_price_720p" field to the value that was provided on create.
|
||||
func (u *GroupUpsertBulk) UpdateVideoPrice720p() *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateVideoPrice720p()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearVideoPrice720p clears the value of the "video_price_720p" field.
|
||||
func (u *GroupUpsertBulk) ClearVideoPrice720p() *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.ClearVideoPrice720p()
|
||||
})
|
||||
}
|
||||
|
||||
// SetVideoPrice1080p sets the "video_price_1080p" field.
|
||||
func (u *GroupUpsertBulk) SetVideoPrice1080p(v float64) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetVideoPrice1080p(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddVideoPrice1080p adds v to the "video_price_1080p" field.
|
||||
func (u *GroupUpsertBulk) AddVideoPrice1080p(v float64) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.AddVideoPrice1080p(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateVideoPrice1080p sets the "video_price_1080p" field to the value that was provided on create.
|
||||
func (u *GroupUpsertBulk) UpdateVideoPrice1080p() *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateVideoPrice1080p()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearVideoPrice1080p clears the value of the "video_price_1080p" field.
|
||||
func (u *GroupUpsertBulk) ClearVideoPrice1080p() *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.ClearVideoPrice1080p()
|
||||
})
|
||||
}
|
||||
|
||||
// SetClaudeCodeOnly sets the "claude_code_only" field.
|
||||
func (u *GroupUpsertBulk) SetClaudeCodeOnly(v bool) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
|
||||
@@ -524,6 +524,122 @@ func (_u *GroupUpdate) AddBatchImageHoldMultiplier(v float64) *GroupUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetVideoRateIndependent sets the "video_rate_independent" field.
|
||||
func (_u *GroupUpdate) SetVideoRateIndependent(v bool) *GroupUpdate {
|
||||
_u.mutation.SetVideoRateIndependent(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableVideoRateIndependent sets the "video_rate_independent" field if the given value is not nil.
|
||||
func (_u *GroupUpdate) SetNillableVideoRateIndependent(v *bool) *GroupUpdate {
|
||||
if v != nil {
|
||||
_u.SetVideoRateIndependent(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetVideoRateMultiplier sets the "video_rate_multiplier" field.
|
||||
func (_u *GroupUpdate) SetVideoRateMultiplier(v float64) *GroupUpdate {
|
||||
_u.mutation.ResetVideoRateMultiplier()
|
||||
_u.mutation.SetVideoRateMultiplier(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableVideoRateMultiplier sets the "video_rate_multiplier" field if the given value is not nil.
|
||||
func (_u *GroupUpdate) SetNillableVideoRateMultiplier(v *float64) *GroupUpdate {
|
||||
if v != nil {
|
||||
_u.SetVideoRateMultiplier(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddVideoRateMultiplier adds value to the "video_rate_multiplier" field.
|
||||
func (_u *GroupUpdate) AddVideoRateMultiplier(v float64) *GroupUpdate {
|
||||
_u.mutation.AddVideoRateMultiplier(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetVideoPrice480p sets the "video_price_480p" field.
|
||||
func (_u *GroupUpdate) SetVideoPrice480p(v float64) *GroupUpdate {
|
||||
_u.mutation.ResetVideoPrice480p()
|
||||
_u.mutation.SetVideoPrice480p(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableVideoPrice480p sets the "video_price_480p" field if the given value is not nil.
|
||||
func (_u *GroupUpdate) SetNillableVideoPrice480p(v *float64) *GroupUpdate {
|
||||
if v != nil {
|
||||
_u.SetVideoPrice480p(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddVideoPrice480p adds value to the "video_price_480p" field.
|
||||
func (_u *GroupUpdate) AddVideoPrice480p(v float64) *GroupUpdate {
|
||||
_u.mutation.AddVideoPrice480p(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearVideoPrice480p clears the value of the "video_price_480p" field.
|
||||
func (_u *GroupUpdate) ClearVideoPrice480p() *GroupUpdate {
|
||||
_u.mutation.ClearVideoPrice480p()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetVideoPrice720p sets the "video_price_720p" field.
|
||||
func (_u *GroupUpdate) SetVideoPrice720p(v float64) *GroupUpdate {
|
||||
_u.mutation.ResetVideoPrice720p()
|
||||
_u.mutation.SetVideoPrice720p(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableVideoPrice720p sets the "video_price_720p" field if the given value is not nil.
|
||||
func (_u *GroupUpdate) SetNillableVideoPrice720p(v *float64) *GroupUpdate {
|
||||
if v != nil {
|
||||
_u.SetVideoPrice720p(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddVideoPrice720p adds value to the "video_price_720p" field.
|
||||
func (_u *GroupUpdate) AddVideoPrice720p(v float64) *GroupUpdate {
|
||||
_u.mutation.AddVideoPrice720p(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearVideoPrice720p clears the value of the "video_price_720p" field.
|
||||
func (_u *GroupUpdate) ClearVideoPrice720p() *GroupUpdate {
|
||||
_u.mutation.ClearVideoPrice720p()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetVideoPrice1080p sets the "video_price_1080p" field.
|
||||
func (_u *GroupUpdate) SetVideoPrice1080p(v float64) *GroupUpdate {
|
||||
_u.mutation.ResetVideoPrice1080p()
|
||||
_u.mutation.SetVideoPrice1080p(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableVideoPrice1080p sets the "video_price_1080p" field if the given value is not nil.
|
||||
func (_u *GroupUpdate) SetNillableVideoPrice1080p(v *float64) *GroupUpdate {
|
||||
if v != nil {
|
||||
_u.SetVideoPrice1080p(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddVideoPrice1080p adds value to the "video_price_1080p" field.
|
||||
func (_u *GroupUpdate) AddVideoPrice1080p(v float64) *GroupUpdate {
|
||||
_u.mutation.AddVideoPrice1080p(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearVideoPrice1080p clears the value of the "video_price_1080p" field.
|
||||
func (_u *GroupUpdate) ClearVideoPrice1080p() *GroupUpdate {
|
||||
_u.mutation.ClearVideoPrice1080p()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetClaudeCodeOnly sets the "claude_code_only" field.
|
||||
func (_u *GroupUpdate) SetClaudeCodeOnly(v bool) *GroupUpdate {
|
||||
_u.mutation.SetClaudeCodeOnly(v)
|
||||
@@ -1223,6 +1339,42 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if value, ok := _u.mutation.AddedBatchImageHoldMultiplier(); ok {
|
||||
_spec.AddField(group.FieldBatchImageHoldMultiplier, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.VideoRateIndependent(); ok {
|
||||
_spec.SetField(group.FieldVideoRateIndependent, field.TypeBool, value)
|
||||
}
|
||||
if value, ok := _u.mutation.VideoRateMultiplier(); ok {
|
||||
_spec.SetField(group.FieldVideoRateMultiplier, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedVideoRateMultiplier(); ok {
|
||||
_spec.AddField(group.FieldVideoRateMultiplier, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.VideoPrice480p(); ok {
|
||||
_spec.SetField(group.FieldVideoPrice480p, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedVideoPrice480p(); ok {
|
||||
_spec.AddField(group.FieldVideoPrice480p, field.TypeFloat64, value)
|
||||
}
|
||||
if _u.mutation.VideoPrice480pCleared() {
|
||||
_spec.ClearField(group.FieldVideoPrice480p, field.TypeFloat64)
|
||||
}
|
||||
if value, ok := _u.mutation.VideoPrice720p(); ok {
|
||||
_spec.SetField(group.FieldVideoPrice720p, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedVideoPrice720p(); ok {
|
||||
_spec.AddField(group.FieldVideoPrice720p, field.TypeFloat64, value)
|
||||
}
|
||||
if _u.mutation.VideoPrice720pCleared() {
|
||||
_spec.ClearField(group.FieldVideoPrice720p, field.TypeFloat64)
|
||||
}
|
||||
if value, ok := _u.mutation.VideoPrice1080p(); ok {
|
||||
_spec.SetField(group.FieldVideoPrice1080p, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedVideoPrice1080p(); ok {
|
||||
_spec.AddField(group.FieldVideoPrice1080p, field.TypeFloat64, value)
|
||||
}
|
||||
if _u.mutation.VideoPrice1080pCleared() {
|
||||
_spec.ClearField(group.FieldVideoPrice1080p, field.TypeFloat64)
|
||||
}
|
||||
if value, ok := _u.mutation.ClaudeCodeOnly(); ok {
|
||||
_spec.SetField(group.FieldClaudeCodeOnly, field.TypeBool, value)
|
||||
}
|
||||
@@ -2096,6 +2248,122 @@ func (_u *GroupUpdateOne) AddBatchImageHoldMultiplier(v float64) *GroupUpdateOne
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetVideoRateIndependent sets the "video_rate_independent" field.
|
||||
func (_u *GroupUpdateOne) SetVideoRateIndependent(v bool) *GroupUpdateOne {
|
||||
_u.mutation.SetVideoRateIndependent(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableVideoRateIndependent sets the "video_rate_independent" field if the given value is not nil.
|
||||
func (_u *GroupUpdateOne) SetNillableVideoRateIndependent(v *bool) *GroupUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetVideoRateIndependent(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetVideoRateMultiplier sets the "video_rate_multiplier" field.
|
||||
func (_u *GroupUpdateOne) SetVideoRateMultiplier(v float64) *GroupUpdateOne {
|
||||
_u.mutation.ResetVideoRateMultiplier()
|
||||
_u.mutation.SetVideoRateMultiplier(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableVideoRateMultiplier sets the "video_rate_multiplier" field if the given value is not nil.
|
||||
func (_u *GroupUpdateOne) SetNillableVideoRateMultiplier(v *float64) *GroupUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetVideoRateMultiplier(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddVideoRateMultiplier adds value to the "video_rate_multiplier" field.
|
||||
func (_u *GroupUpdateOne) AddVideoRateMultiplier(v float64) *GroupUpdateOne {
|
||||
_u.mutation.AddVideoRateMultiplier(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetVideoPrice480p sets the "video_price_480p" field.
|
||||
func (_u *GroupUpdateOne) SetVideoPrice480p(v float64) *GroupUpdateOne {
|
||||
_u.mutation.ResetVideoPrice480p()
|
||||
_u.mutation.SetVideoPrice480p(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableVideoPrice480p sets the "video_price_480p" field if the given value is not nil.
|
||||
func (_u *GroupUpdateOne) SetNillableVideoPrice480p(v *float64) *GroupUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetVideoPrice480p(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddVideoPrice480p adds value to the "video_price_480p" field.
|
||||
func (_u *GroupUpdateOne) AddVideoPrice480p(v float64) *GroupUpdateOne {
|
||||
_u.mutation.AddVideoPrice480p(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearVideoPrice480p clears the value of the "video_price_480p" field.
|
||||
func (_u *GroupUpdateOne) ClearVideoPrice480p() *GroupUpdateOne {
|
||||
_u.mutation.ClearVideoPrice480p()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetVideoPrice720p sets the "video_price_720p" field.
|
||||
func (_u *GroupUpdateOne) SetVideoPrice720p(v float64) *GroupUpdateOne {
|
||||
_u.mutation.ResetVideoPrice720p()
|
||||
_u.mutation.SetVideoPrice720p(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableVideoPrice720p sets the "video_price_720p" field if the given value is not nil.
|
||||
func (_u *GroupUpdateOne) SetNillableVideoPrice720p(v *float64) *GroupUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetVideoPrice720p(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddVideoPrice720p adds value to the "video_price_720p" field.
|
||||
func (_u *GroupUpdateOne) AddVideoPrice720p(v float64) *GroupUpdateOne {
|
||||
_u.mutation.AddVideoPrice720p(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearVideoPrice720p clears the value of the "video_price_720p" field.
|
||||
func (_u *GroupUpdateOne) ClearVideoPrice720p() *GroupUpdateOne {
|
||||
_u.mutation.ClearVideoPrice720p()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetVideoPrice1080p sets the "video_price_1080p" field.
|
||||
func (_u *GroupUpdateOne) SetVideoPrice1080p(v float64) *GroupUpdateOne {
|
||||
_u.mutation.ResetVideoPrice1080p()
|
||||
_u.mutation.SetVideoPrice1080p(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableVideoPrice1080p sets the "video_price_1080p" field if the given value is not nil.
|
||||
func (_u *GroupUpdateOne) SetNillableVideoPrice1080p(v *float64) *GroupUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetVideoPrice1080p(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddVideoPrice1080p adds value to the "video_price_1080p" field.
|
||||
func (_u *GroupUpdateOne) AddVideoPrice1080p(v float64) *GroupUpdateOne {
|
||||
_u.mutation.AddVideoPrice1080p(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearVideoPrice1080p clears the value of the "video_price_1080p" field.
|
||||
func (_u *GroupUpdateOne) ClearVideoPrice1080p() *GroupUpdateOne {
|
||||
_u.mutation.ClearVideoPrice1080p()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetClaudeCodeOnly sets the "claude_code_only" field.
|
||||
func (_u *GroupUpdateOne) SetClaudeCodeOnly(v bool) *GroupUpdateOne {
|
||||
_u.mutation.SetClaudeCodeOnly(v)
|
||||
@@ -2825,6 +3093,42 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
|
||||
if value, ok := _u.mutation.AddedBatchImageHoldMultiplier(); ok {
|
||||
_spec.AddField(group.FieldBatchImageHoldMultiplier, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.VideoRateIndependent(); ok {
|
||||
_spec.SetField(group.FieldVideoRateIndependent, field.TypeBool, value)
|
||||
}
|
||||
if value, ok := _u.mutation.VideoRateMultiplier(); ok {
|
||||
_spec.SetField(group.FieldVideoRateMultiplier, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedVideoRateMultiplier(); ok {
|
||||
_spec.AddField(group.FieldVideoRateMultiplier, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.VideoPrice480p(); ok {
|
||||
_spec.SetField(group.FieldVideoPrice480p, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedVideoPrice480p(); ok {
|
||||
_spec.AddField(group.FieldVideoPrice480p, field.TypeFloat64, value)
|
||||
}
|
||||
if _u.mutation.VideoPrice480pCleared() {
|
||||
_spec.ClearField(group.FieldVideoPrice480p, field.TypeFloat64)
|
||||
}
|
||||
if value, ok := _u.mutation.VideoPrice720p(); ok {
|
||||
_spec.SetField(group.FieldVideoPrice720p, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedVideoPrice720p(); ok {
|
||||
_spec.AddField(group.FieldVideoPrice720p, field.TypeFloat64, value)
|
||||
}
|
||||
if _u.mutation.VideoPrice720pCleared() {
|
||||
_spec.ClearField(group.FieldVideoPrice720p, field.TypeFloat64)
|
||||
}
|
||||
if value, ok := _u.mutation.VideoPrice1080p(); ok {
|
||||
_spec.SetField(group.FieldVideoPrice1080p, field.TypeFloat64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedVideoPrice1080p(); ok {
|
||||
_spec.AddField(group.FieldVideoPrice1080p, field.TypeFloat64, value)
|
||||
}
|
||||
if _u.mutation.VideoPrice1080pCleared() {
|
||||
_spec.ClearField(group.FieldVideoPrice1080p, field.TypeFloat64)
|
||||
}
|
||||
if value, ok := _u.mutation.ClaudeCodeOnly(); ok {
|
||||
_spec.SetField(group.FieldClaudeCodeOnly, field.TypeBool, value)
|
||||
}
|
||||
|
||||
@@ -860,6 +860,11 @@ var (
|
||||
{Name: "image_price_4k", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "batch_image_discount_multiplier", Type: field.TypeFloat64, Default: 0.5, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
|
||||
{Name: "batch_image_hold_multiplier", Type: field.TypeFloat64, Default: 0.6, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
|
||||
{Name: "video_rate_independent", Type: field.TypeBool, Default: false},
|
||||
{Name: "video_rate_multiplier", Type: field.TypeFloat64, Default: 1, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
|
||||
{Name: "video_price_480p", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "video_price_720p", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "video_price_1080p", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
|
||||
{Name: "claude_code_only", Type: field.TypeBool, Default: false},
|
||||
{Name: "fallback_group_id", Type: field.TypeInt64, Nullable: true},
|
||||
{Name: "fallback_group_id_on_invalid_request", Type: field.TypeInt64, Nullable: true},
|
||||
@@ -910,7 +915,7 @@ var (
|
||||
{
|
||||
Name: "group_sort_order",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{GroupsColumns[35]},
|
||||
Columns: []*schema.Column{GroupsColumns[40]},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+463
-1
@@ -20833,6 +20833,15 @@ type GroupMutation struct {
|
||||
addbatch_image_discount_multiplier *float64
|
||||
batch_image_hold_multiplier *float64
|
||||
addbatch_image_hold_multiplier *float64
|
||||
video_rate_independent *bool
|
||||
video_rate_multiplier *float64
|
||||
addvideo_rate_multiplier *float64
|
||||
video_price_480p *float64
|
||||
addvideo_price_480p *float64
|
||||
video_price_720p *float64
|
||||
addvideo_price_720p *float64
|
||||
video_price_1080p *float64
|
||||
addvideo_price_1080p *float64
|
||||
claude_code_only *bool
|
||||
fallback_group_id *int64
|
||||
addfallback_group_id *int64
|
||||
@@ -22297,6 +22306,308 @@ func (m *GroupMutation) ResetBatchImageHoldMultiplier() {
|
||||
m.addbatch_image_hold_multiplier = nil
|
||||
}
|
||||
|
||||
// SetVideoRateIndependent sets the "video_rate_independent" field.
|
||||
func (m *GroupMutation) SetVideoRateIndependent(b bool) {
|
||||
m.video_rate_independent = &b
|
||||
}
|
||||
|
||||
// VideoRateIndependent returns the value of the "video_rate_independent" field in the mutation.
|
||||
func (m *GroupMutation) VideoRateIndependent() (r bool, exists bool) {
|
||||
v := m.video_rate_independent
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldVideoRateIndependent returns the old "video_rate_independent" field's value of the Group entity.
|
||||
// If the Group object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *GroupMutation) OldVideoRateIndependent(ctx context.Context) (v bool, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldVideoRateIndependent is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldVideoRateIndependent requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldVideoRateIndependent: %w", err)
|
||||
}
|
||||
return oldValue.VideoRateIndependent, nil
|
||||
}
|
||||
|
||||
// ResetVideoRateIndependent resets all changes to the "video_rate_independent" field.
|
||||
func (m *GroupMutation) ResetVideoRateIndependent() {
|
||||
m.video_rate_independent = nil
|
||||
}
|
||||
|
||||
// SetVideoRateMultiplier sets the "video_rate_multiplier" field.
|
||||
func (m *GroupMutation) SetVideoRateMultiplier(f float64) {
|
||||
m.video_rate_multiplier = &f
|
||||
m.addvideo_rate_multiplier = nil
|
||||
}
|
||||
|
||||
// VideoRateMultiplier returns the value of the "video_rate_multiplier" field in the mutation.
|
||||
func (m *GroupMutation) VideoRateMultiplier() (r float64, exists bool) {
|
||||
v := m.video_rate_multiplier
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldVideoRateMultiplier returns the old "video_rate_multiplier" field's value of the Group entity.
|
||||
// If the Group object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *GroupMutation) OldVideoRateMultiplier(ctx context.Context) (v float64, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldVideoRateMultiplier is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldVideoRateMultiplier requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldVideoRateMultiplier: %w", err)
|
||||
}
|
||||
return oldValue.VideoRateMultiplier, nil
|
||||
}
|
||||
|
||||
// AddVideoRateMultiplier adds f to the "video_rate_multiplier" field.
|
||||
func (m *GroupMutation) AddVideoRateMultiplier(f float64) {
|
||||
if m.addvideo_rate_multiplier != nil {
|
||||
*m.addvideo_rate_multiplier += f
|
||||
} else {
|
||||
m.addvideo_rate_multiplier = &f
|
||||
}
|
||||
}
|
||||
|
||||
// AddedVideoRateMultiplier returns the value that was added to the "video_rate_multiplier" field in this mutation.
|
||||
func (m *GroupMutation) AddedVideoRateMultiplier() (r float64, exists bool) {
|
||||
v := m.addvideo_rate_multiplier
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// ResetVideoRateMultiplier resets all changes to the "video_rate_multiplier" field.
|
||||
func (m *GroupMutation) ResetVideoRateMultiplier() {
|
||||
m.video_rate_multiplier = nil
|
||||
m.addvideo_rate_multiplier = nil
|
||||
}
|
||||
|
||||
// SetVideoPrice480p sets the "video_price_480p" field.
|
||||
func (m *GroupMutation) SetVideoPrice480p(f float64) {
|
||||
m.video_price_480p = &f
|
||||
m.addvideo_price_480p = nil
|
||||
}
|
||||
|
||||
// VideoPrice480p returns the value of the "video_price_480p" field in the mutation.
|
||||
func (m *GroupMutation) VideoPrice480p() (r float64, exists bool) {
|
||||
v := m.video_price_480p
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldVideoPrice480p returns the old "video_price_480p" field's value of the Group entity.
|
||||
// If the Group object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *GroupMutation) OldVideoPrice480p(ctx context.Context) (v *float64, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldVideoPrice480p is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldVideoPrice480p requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldVideoPrice480p: %w", err)
|
||||
}
|
||||
return oldValue.VideoPrice480p, nil
|
||||
}
|
||||
|
||||
// AddVideoPrice480p adds f to the "video_price_480p" field.
|
||||
func (m *GroupMutation) AddVideoPrice480p(f float64) {
|
||||
if m.addvideo_price_480p != nil {
|
||||
*m.addvideo_price_480p += f
|
||||
} else {
|
||||
m.addvideo_price_480p = &f
|
||||
}
|
||||
}
|
||||
|
||||
// AddedVideoPrice480p returns the value that was added to the "video_price_480p" field in this mutation.
|
||||
func (m *GroupMutation) AddedVideoPrice480p() (r float64, exists bool) {
|
||||
v := m.addvideo_price_480p
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// ClearVideoPrice480p clears the value of the "video_price_480p" field.
|
||||
func (m *GroupMutation) ClearVideoPrice480p() {
|
||||
m.video_price_480p = nil
|
||||
m.addvideo_price_480p = nil
|
||||
m.clearedFields[group.FieldVideoPrice480p] = struct{}{}
|
||||
}
|
||||
|
||||
// VideoPrice480pCleared returns if the "video_price_480p" field was cleared in this mutation.
|
||||
func (m *GroupMutation) VideoPrice480pCleared() bool {
|
||||
_, ok := m.clearedFields[group.FieldVideoPrice480p]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ResetVideoPrice480p resets all changes to the "video_price_480p" field.
|
||||
func (m *GroupMutation) ResetVideoPrice480p() {
|
||||
m.video_price_480p = nil
|
||||
m.addvideo_price_480p = nil
|
||||
delete(m.clearedFields, group.FieldVideoPrice480p)
|
||||
}
|
||||
|
||||
// SetVideoPrice720p sets the "video_price_720p" field.
|
||||
func (m *GroupMutation) SetVideoPrice720p(f float64) {
|
||||
m.video_price_720p = &f
|
||||
m.addvideo_price_720p = nil
|
||||
}
|
||||
|
||||
// VideoPrice720p returns the value of the "video_price_720p" field in the mutation.
|
||||
func (m *GroupMutation) VideoPrice720p() (r float64, exists bool) {
|
||||
v := m.video_price_720p
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldVideoPrice720p returns the old "video_price_720p" field's value of the Group entity.
|
||||
// If the Group object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *GroupMutation) OldVideoPrice720p(ctx context.Context) (v *float64, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldVideoPrice720p is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldVideoPrice720p requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldVideoPrice720p: %w", err)
|
||||
}
|
||||
return oldValue.VideoPrice720p, nil
|
||||
}
|
||||
|
||||
// AddVideoPrice720p adds f to the "video_price_720p" field.
|
||||
func (m *GroupMutation) AddVideoPrice720p(f float64) {
|
||||
if m.addvideo_price_720p != nil {
|
||||
*m.addvideo_price_720p += f
|
||||
} else {
|
||||
m.addvideo_price_720p = &f
|
||||
}
|
||||
}
|
||||
|
||||
// AddedVideoPrice720p returns the value that was added to the "video_price_720p" field in this mutation.
|
||||
func (m *GroupMutation) AddedVideoPrice720p() (r float64, exists bool) {
|
||||
v := m.addvideo_price_720p
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// ClearVideoPrice720p clears the value of the "video_price_720p" field.
|
||||
func (m *GroupMutation) ClearVideoPrice720p() {
|
||||
m.video_price_720p = nil
|
||||
m.addvideo_price_720p = nil
|
||||
m.clearedFields[group.FieldVideoPrice720p] = struct{}{}
|
||||
}
|
||||
|
||||
// VideoPrice720pCleared returns if the "video_price_720p" field was cleared in this mutation.
|
||||
func (m *GroupMutation) VideoPrice720pCleared() bool {
|
||||
_, ok := m.clearedFields[group.FieldVideoPrice720p]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ResetVideoPrice720p resets all changes to the "video_price_720p" field.
|
||||
func (m *GroupMutation) ResetVideoPrice720p() {
|
||||
m.video_price_720p = nil
|
||||
m.addvideo_price_720p = nil
|
||||
delete(m.clearedFields, group.FieldVideoPrice720p)
|
||||
}
|
||||
|
||||
// SetVideoPrice1080p sets the "video_price_1080p" field.
|
||||
func (m *GroupMutation) SetVideoPrice1080p(f float64) {
|
||||
m.video_price_1080p = &f
|
||||
m.addvideo_price_1080p = nil
|
||||
}
|
||||
|
||||
// VideoPrice1080p returns the value of the "video_price_1080p" field in the mutation.
|
||||
func (m *GroupMutation) VideoPrice1080p() (r float64, exists bool) {
|
||||
v := m.video_price_1080p
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldVideoPrice1080p returns the old "video_price_1080p" field's value of the Group entity.
|
||||
// If the Group object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *GroupMutation) OldVideoPrice1080p(ctx context.Context) (v *float64, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldVideoPrice1080p is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldVideoPrice1080p requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldVideoPrice1080p: %w", err)
|
||||
}
|
||||
return oldValue.VideoPrice1080p, nil
|
||||
}
|
||||
|
||||
// AddVideoPrice1080p adds f to the "video_price_1080p" field.
|
||||
func (m *GroupMutation) AddVideoPrice1080p(f float64) {
|
||||
if m.addvideo_price_1080p != nil {
|
||||
*m.addvideo_price_1080p += f
|
||||
} else {
|
||||
m.addvideo_price_1080p = &f
|
||||
}
|
||||
}
|
||||
|
||||
// AddedVideoPrice1080p returns the value that was added to the "video_price_1080p" field in this mutation.
|
||||
func (m *GroupMutation) AddedVideoPrice1080p() (r float64, exists bool) {
|
||||
v := m.addvideo_price_1080p
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// ClearVideoPrice1080p clears the value of the "video_price_1080p" field.
|
||||
func (m *GroupMutation) ClearVideoPrice1080p() {
|
||||
m.video_price_1080p = nil
|
||||
m.addvideo_price_1080p = nil
|
||||
m.clearedFields[group.FieldVideoPrice1080p] = struct{}{}
|
||||
}
|
||||
|
||||
// VideoPrice1080pCleared returns if the "video_price_1080p" field was cleared in this mutation.
|
||||
func (m *GroupMutation) VideoPrice1080pCleared() bool {
|
||||
_, ok := m.clearedFields[group.FieldVideoPrice1080p]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ResetVideoPrice1080p resets all changes to the "video_price_1080p" field.
|
||||
func (m *GroupMutation) ResetVideoPrice1080p() {
|
||||
m.video_price_1080p = nil
|
||||
m.addvideo_price_1080p = nil
|
||||
delete(m.clearedFields, group.FieldVideoPrice1080p)
|
||||
}
|
||||
|
||||
// SetClaudeCodeOnly sets the "claude_code_only" field.
|
||||
func (m *GroupMutation) SetClaudeCodeOnly(b bool) {
|
||||
m.claude_code_only = &b
|
||||
@@ -23331,7 +23642,7 @@ func (m *GroupMutation) Type() string {
|
||||
// order to get all numeric fields that were incremented/decremented, call
|
||||
// AddedFields().
|
||||
func (m *GroupMutation) Fields() []string {
|
||||
fields := make([]string, 0, 42)
|
||||
fields := make([]string, 0, 47)
|
||||
if m.created_at != nil {
|
||||
fields = append(fields, group.FieldCreatedAt)
|
||||
}
|
||||
@@ -23413,6 +23724,21 @@ func (m *GroupMutation) Fields() []string {
|
||||
if m.batch_image_hold_multiplier != nil {
|
||||
fields = append(fields, group.FieldBatchImageHoldMultiplier)
|
||||
}
|
||||
if m.video_rate_independent != nil {
|
||||
fields = append(fields, group.FieldVideoRateIndependent)
|
||||
}
|
||||
if m.video_rate_multiplier != nil {
|
||||
fields = append(fields, group.FieldVideoRateMultiplier)
|
||||
}
|
||||
if m.video_price_480p != nil {
|
||||
fields = append(fields, group.FieldVideoPrice480p)
|
||||
}
|
||||
if m.video_price_720p != nil {
|
||||
fields = append(fields, group.FieldVideoPrice720p)
|
||||
}
|
||||
if m.video_price_1080p != nil {
|
||||
fields = append(fields, group.FieldVideoPrice1080p)
|
||||
}
|
||||
if m.claude_code_only != nil {
|
||||
fields = append(fields, group.FieldClaudeCodeOnly)
|
||||
}
|
||||
@@ -23520,6 +23846,16 @@ func (m *GroupMutation) Field(name string) (ent.Value, bool) {
|
||||
return m.BatchImageDiscountMultiplier()
|
||||
case group.FieldBatchImageHoldMultiplier:
|
||||
return m.BatchImageHoldMultiplier()
|
||||
case group.FieldVideoRateIndependent:
|
||||
return m.VideoRateIndependent()
|
||||
case group.FieldVideoRateMultiplier:
|
||||
return m.VideoRateMultiplier()
|
||||
case group.FieldVideoPrice480p:
|
||||
return m.VideoPrice480p()
|
||||
case group.FieldVideoPrice720p:
|
||||
return m.VideoPrice720p()
|
||||
case group.FieldVideoPrice1080p:
|
||||
return m.VideoPrice1080p()
|
||||
case group.FieldClaudeCodeOnly:
|
||||
return m.ClaudeCodeOnly()
|
||||
case group.FieldFallbackGroupID:
|
||||
@@ -23613,6 +23949,16 @@ func (m *GroupMutation) OldField(ctx context.Context, name string) (ent.Value, e
|
||||
return m.OldBatchImageDiscountMultiplier(ctx)
|
||||
case group.FieldBatchImageHoldMultiplier:
|
||||
return m.OldBatchImageHoldMultiplier(ctx)
|
||||
case group.FieldVideoRateIndependent:
|
||||
return m.OldVideoRateIndependent(ctx)
|
||||
case group.FieldVideoRateMultiplier:
|
||||
return m.OldVideoRateMultiplier(ctx)
|
||||
case group.FieldVideoPrice480p:
|
||||
return m.OldVideoPrice480p(ctx)
|
||||
case group.FieldVideoPrice720p:
|
||||
return m.OldVideoPrice720p(ctx)
|
||||
case group.FieldVideoPrice1080p:
|
||||
return m.OldVideoPrice1080p(ctx)
|
||||
case group.FieldClaudeCodeOnly:
|
||||
return m.OldClaudeCodeOnly(ctx)
|
||||
case group.FieldFallbackGroupID:
|
||||
@@ -23841,6 +24187,41 @@ func (m *GroupMutation) SetField(name string, value ent.Value) error {
|
||||
}
|
||||
m.SetBatchImageHoldMultiplier(v)
|
||||
return nil
|
||||
case group.FieldVideoRateIndependent:
|
||||
v, ok := value.(bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetVideoRateIndependent(v)
|
||||
return nil
|
||||
case group.FieldVideoRateMultiplier:
|
||||
v, ok := value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetVideoRateMultiplier(v)
|
||||
return nil
|
||||
case group.FieldVideoPrice480p:
|
||||
v, ok := value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetVideoPrice480p(v)
|
||||
return nil
|
||||
case group.FieldVideoPrice720p:
|
||||
v, ok := value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetVideoPrice720p(v)
|
||||
return nil
|
||||
case group.FieldVideoPrice1080p:
|
||||
v, ok := value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetVideoPrice1080p(v)
|
||||
return nil
|
||||
case group.FieldClaudeCodeOnly:
|
||||
v, ok := value.(bool)
|
||||
if !ok {
|
||||
@@ -23990,6 +24371,18 @@ func (m *GroupMutation) AddedFields() []string {
|
||||
if m.addbatch_image_hold_multiplier != nil {
|
||||
fields = append(fields, group.FieldBatchImageHoldMultiplier)
|
||||
}
|
||||
if m.addvideo_rate_multiplier != nil {
|
||||
fields = append(fields, group.FieldVideoRateMultiplier)
|
||||
}
|
||||
if m.addvideo_price_480p != nil {
|
||||
fields = append(fields, group.FieldVideoPrice480p)
|
||||
}
|
||||
if m.addvideo_price_720p != nil {
|
||||
fields = append(fields, group.FieldVideoPrice720p)
|
||||
}
|
||||
if m.addvideo_price_1080p != nil {
|
||||
fields = append(fields, group.FieldVideoPrice1080p)
|
||||
}
|
||||
if m.addfallback_group_id != nil {
|
||||
fields = append(fields, group.FieldFallbackGroupID)
|
||||
}
|
||||
@@ -24034,6 +24427,14 @@ func (m *GroupMutation) AddedField(name string) (ent.Value, bool) {
|
||||
return m.AddedBatchImageDiscountMultiplier()
|
||||
case group.FieldBatchImageHoldMultiplier:
|
||||
return m.AddedBatchImageHoldMultiplier()
|
||||
case group.FieldVideoRateMultiplier:
|
||||
return m.AddedVideoRateMultiplier()
|
||||
case group.FieldVideoPrice480p:
|
||||
return m.AddedVideoPrice480p()
|
||||
case group.FieldVideoPrice720p:
|
||||
return m.AddedVideoPrice720p()
|
||||
case group.FieldVideoPrice1080p:
|
||||
return m.AddedVideoPrice1080p()
|
||||
case group.FieldFallbackGroupID:
|
||||
return m.AddedFallbackGroupID()
|
||||
case group.FieldFallbackGroupIDOnInvalidRequest:
|
||||
@@ -24135,6 +24536,34 @@ func (m *GroupMutation) AddField(name string, value ent.Value) error {
|
||||
}
|
||||
m.AddBatchImageHoldMultiplier(v)
|
||||
return nil
|
||||
case group.FieldVideoRateMultiplier:
|
||||
v, ok := value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.AddVideoRateMultiplier(v)
|
||||
return nil
|
||||
case group.FieldVideoPrice480p:
|
||||
v, ok := value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.AddVideoPrice480p(v)
|
||||
return nil
|
||||
case group.FieldVideoPrice720p:
|
||||
v, ok := value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.AddVideoPrice720p(v)
|
||||
return nil
|
||||
case group.FieldVideoPrice1080p:
|
||||
v, ok := value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.AddVideoPrice1080p(v)
|
||||
return nil
|
||||
case group.FieldFallbackGroupID:
|
||||
v, ok := value.(int64)
|
||||
if !ok {
|
||||
@@ -24195,6 +24624,15 @@ func (m *GroupMutation) ClearedFields() []string {
|
||||
if m.FieldCleared(group.FieldImagePrice4k) {
|
||||
fields = append(fields, group.FieldImagePrice4k)
|
||||
}
|
||||
if m.FieldCleared(group.FieldVideoPrice480p) {
|
||||
fields = append(fields, group.FieldVideoPrice480p)
|
||||
}
|
||||
if m.FieldCleared(group.FieldVideoPrice720p) {
|
||||
fields = append(fields, group.FieldVideoPrice720p)
|
||||
}
|
||||
if m.FieldCleared(group.FieldVideoPrice1080p) {
|
||||
fields = append(fields, group.FieldVideoPrice1080p)
|
||||
}
|
||||
if m.FieldCleared(group.FieldFallbackGroupID) {
|
||||
fields = append(fields, group.FieldFallbackGroupID)
|
||||
}
|
||||
@@ -24242,6 +24680,15 @@ func (m *GroupMutation) ClearField(name string) error {
|
||||
case group.FieldImagePrice4k:
|
||||
m.ClearImagePrice4k()
|
||||
return nil
|
||||
case group.FieldVideoPrice480p:
|
||||
m.ClearVideoPrice480p()
|
||||
return nil
|
||||
case group.FieldVideoPrice720p:
|
||||
m.ClearVideoPrice720p()
|
||||
return nil
|
||||
case group.FieldVideoPrice1080p:
|
||||
m.ClearVideoPrice1080p()
|
||||
return nil
|
||||
case group.FieldFallbackGroupID:
|
||||
m.ClearFallbackGroupID()
|
||||
return nil
|
||||
@@ -24340,6 +24787,21 @@ func (m *GroupMutation) ResetField(name string) error {
|
||||
case group.FieldBatchImageHoldMultiplier:
|
||||
m.ResetBatchImageHoldMultiplier()
|
||||
return nil
|
||||
case group.FieldVideoRateIndependent:
|
||||
m.ResetVideoRateIndependent()
|
||||
return nil
|
||||
case group.FieldVideoRateMultiplier:
|
||||
m.ResetVideoRateMultiplier()
|
||||
return nil
|
||||
case group.FieldVideoPrice480p:
|
||||
m.ResetVideoPrice480p()
|
||||
return nil
|
||||
case group.FieldVideoPrice720p:
|
||||
m.ResetVideoPrice720p()
|
||||
return nil
|
||||
case group.FieldVideoPrice1080p:
|
||||
m.ResetVideoPrice1080p()
|
||||
return nil
|
||||
case group.FieldClaudeCodeOnly:
|
||||
m.ResetClaudeCodeOnly()
|
||||
return nil
|
||||
|
||||
@@ -1035,54 +1035,62 @@ func init() {
|
||||
groupDescBatchImageHoldMultiplier := groupFields[23].Descriptor()
|
||||
// group.DefaultBatchImageHoldMultiplier holds the default value on creation for the batch_image_hold_multiplier field.
|
||||
group.DefaultBatchImageHoldMultiplier = groupDescBatchImageHoldMultiplier.Default.(float64)
|
||||
// groupDescVideoRateIndependent is the schema descriptor for video_rate_independent field.
|
||||
groupDescVideoRateIndependent := groupFields[24].Descriptor()
|
||||
// group.DefaultVideoRateIndependent holds the default value on creation for the video_rate_independent field.
|
||||
group.DefaultVideoRateIndependent = groupDescVideoRateIndependent.Default.(bool)
|
||||
// groupDescVideoRateMultiplier is the schema descriptor for video_rate_multiplier field.
|
||||
groupDescVideoRateMultiplier := groupFields[25].Descriptor()
|
||||
// group.DefaultVideoRateMultiplier holds the default value on creation for the video_rate_multiplier field.
|
||||
group.DefaultVideoRateMultiplier = groupDescVideoRateMultiplier.Default.(float64)
|
||||
// groupDescClaudeCodeOnly is the schema descriptor for claude_code_only field.
|
||||
groupDescClaudeCodeOnly := groupFields[24].Descriptor()
|
||||
groupDescClaudeCodeOnly := groupFields[29].Descriptor()
|
||||
// group.DefaultClaudeCodeOnly holds the default value on creation for the claude_code_only field.
|
||||
group.DefaultClaudeCodeOnly = groupDescClaudeCodeOnly.Default.(bool)
|
||||
// groupDescModelRoutingEnabled is the schema descriptor for model_routing_enabled field.
|
||||
groupDescModelRoutingEnabled := groupFields[28].Descriptor()
|
||||
groupDescModelRoutingEnabled := groupFields[33].Descriptor()
|
||||
// group.DefaultModelRoutingEnabled holds the default value on creation for the model_routing_enabled field.
|
||||
group.DefaultModelRoutingEnabled = groupDescModelRoutingEnabled.Default.(bool)
|
||||
// groupDescMcpXMLInject is the schema descriptor for mcp_xml_inject field.
|
||||
groupDescMcpXMLInject := groupFields[29].Descriptor()
|
||||
groupDescMcpXMLInject := groupFields[34].Descriptor()
|
||||
// group.DefaultMcpXMLInject holds the default value on creation for the mcp_xml_inject field.
|
||||
group.DefaultMcpXMLInject = groupDescMcpXMLInject.Default.(bool)
|
||||
// groupDescSupportedModelScopes is the schema descriptor for supported_model_scopes field.
|
||||
groupDescSupportedModelScopes := groupFields[30].Descriptor()
|
||||
groupDescSupportedModelScopes := groupFields[35].Descriptor()
|
||||
// group.DefaultSupportedModelScopes holds the default value on creation for the supported_model_scopes field.
|
||||
group.DefaultSupportedModelScopes = groupDescSupportedModelScopes.Default.([]string)
|
||||
// groupDescSortOrder is the schema descriptor for sort_order field.
|
||||
groupDescSortOrder := groupFields[31].Descriptor()
|
||||
groupDescSortOrder := groupFields[36].Descriptor()
|
||||
// group.DefaultSortOrder holds the default value on creation for the sort_order field.
|
||||
group.DefaultSortOrder = groupDescSortOrder.Default.(int)
|
||||
// groupDescAllowMessagesDispatch is the schema descriptor for allow_messages_dispatch field.
|
||||
groupDescAllowMessagesDispatch := groupFields[32].Descriptor()
|
||||
groupDescAllowMessagesDispatch := groupFields[37].Descriptor()
|
||||
// group.DefaultAllowMessagesDispatch holds the default value on creation for the allow_messages_dispatch field.
|
||||
group.DefaultAllowMessagesDispatch = groupDescAllowMessagesDispatch.Default.(bool)
|
||||
// groupDescRequireOauthOnly is the schema descriptor for require_oauth_only field.
|
||||
groupDescRequireOauthOnly := groupFields[33].Descriptor()
|
||||
groupDescRequireOauthOnly := groupFields[38].Descriptor()
|
||||
// group.DefaultRequireOauthOnly holds the default value on creation for the require_oauth_only field.
|
||||
group.DefaultRequireOauthOnly = groupDescRequireOauthOnly.Default.(bool)
|
||||
// groupDescRequirePrivacySet is the schema descriptor for require_privacy_set field.
|
||||
groupDescRequirePrivacySet := groupFields[34].Descriptor()
|
||||
groupDescRequirePrivacySet := groupFields[39].Descriptor()
|
||||
// group.DefaultRequirePrivacySet holds the default value on creation for the require_privacy_set field.
|
||||
group.DefaultRequirePrivacySet = groupDescRequirePrivacySet.Default.(bool)
|
||||
// groupDescDefaultMappedModel is the schema descriptor for default_mapped_model field.
|
||||
groupDescDefaultMappedModel := groupFields[35].Descriptor()
|
||||
groupDescDefaultMappedModel := groupFields[40].Descriptor()
|
||||
// group.DefaultDefaultMappedModel holds the default value on creation for the default_mapped_model field.
|
||||
group.DefaultDefaultMappedModel = groupDescDefaultMappedModel.Default.(string)
|
||||
// group.DefaultMappedModelValidator is a validator for the "default_mapped_model" field. It is called by the builders before save.
|
||||
group.DefaultMappedModelValidator = groupDescDefaultMappedModel.Validators[0].(func(string) error)
|
||||
// groupDescMessagesDispatchModelConfig is the schema descriptor for messages_dispatch_model_config field.
|
||||
groupDescMessagesDispatchModelConfig := groupFields[36].Descriptor()
|
||||
groupDescMessagesDispatchModelConfig := groupFields[41].Descriptor()
|
||||
// group.DefaultMessagesDispatchModelConfig holds the default value on creation for the messages_dispatch_model_config field.
|
||||
group.DefaultMessagesDispatchModelConfig = groupDescMessagesDispatchModelConfig.Default.(domain.OpenAIMessagesDispatchModelConfig)
|
||||
// groupDescModelsListConfig is the schema descriptor for models_list_config field.
|
||||
groupDescModelsListConfig := groupFields[37].Descriptor()
|
||||
groupDescModelsListConfig := groupFields[42].Descriptor()
|
||||
// group.DefaultModelsListConfig holds the default value on creation for the models_list_config field.
|
||||
group.DefaultModelsListConfig = groupDescModelsListConfig.Default.(domain.GroupModelsListConfig)
|
||||
// groupDescRpmLimit is the schema descriptor for rpm_limit field.
|
||||
groupDescRpmLimit := groupFields[38].Descriptor()
|
||||
groupDescRpmLimit := groupFields[43].Descriptor()
|
||||
// group.DefaultRpmLimit holds the default value on creation for the rpm_limit field.
|
||||
group.DefaultRpmLimit = groupDescRpmLimit.Default.(int)
|
||||
idempotencyrecordMixin := schema.IdempotencyRecord{}.Mixin()
|
||||
|
||||
@@ -123,6 +123,25 @@ func (Group) Fields() []ent.Field {
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}).
|
||||
Default(0.6).
|
||||
Comment("批量图片生成冻结价格比例,按普通生图原价乘以该比例冻结,结算后释放差额"),
|
||||
field.Bool("video_rate_independent").
|
||||
Default(false).
|
||||
Comment("视频生成是否使用独立倍率;false 表示共享分组有效倍率"),
|
||||
field.Float("video_rate_multiplier").
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}).
|
||||
Default(1.0).
|
||||
Comment("视频生成独立倍率,仅 video_rate_independent=true 时生效"),
|
||||
field.Float("video_price_480p").
|
||||
Optional().
|
||||
Nillable().
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}),
|
||||
field.Float("video_price_720p").
|
||||
Optional().
|
||||
Nillable().
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}),
|
||||
field.Float("video_price_1080p").
|
||||
Optional().
|
||||
Nillable().
|
||||
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}),
|
||||
|
||||
// Claude Code 客户端限制 (added by migration 029)
|
||||
field.Bool("claude_code_only").
|
||||
|
||||
@@ -98,6 +98,8 @@ type CreateGroupRequest struct {
|
||||
ImageRateMultiplier *float64 `json:"image_rate_multiplier"`
|
||||
BatchImageDiscountMultiplier *float64 `json:"batch_image_discount_multiplier"`
|
||||
BatchImageHoldMultiplier *float64 `json:"batch_image_hold_multiplier"`
|
||||
VideoRateIndependent bool `json:"video_rate_independent"`
|
||||
VideoRateMultiplier *float64 `json:"video_rate_multiplier"`
|
||||
PeakRateEnabled bool `json:"peak_rate_enabled"`
|
||||
PeakStart string `json:"peak_start"`
|
||||
PeakEnd string `json:"peak_end"`
|
||||
@@ -105,6 +107,9 @@ type CreateGroupRequest struct {
|
||||
ImagePrice1K *float64 `json:"image_price_1k"`
|
||||
ImagePrice2K *float64 `json:"image_price_2k"`
|
||||
ImagePrice4K *float64 `json:"image_price_4k"`
|
||||
VideoPrice480P *float64 `json:"video_price_480p"`
|
||||
VideoPrice720P *float64 `json:"video_price_720p"`
|
||||
VideoPrice1080P *float64 `json:"video_price_1080p"`
|
||||
ClaudeCodeOnly bool `json:"claude_code_only"`
|
||||
FallbackGroupID *int64 `json:"fallback_group_id"`
|
||||
FallbackGroupIDOnInvalidRequest *int64 `json:"fallback_group_id_on_invalid_request"`
|
||||
@@ -146,6 +151,8 @@ type UpdateGroupRequest struct {
|
||||
ImageRateMultiplier *float64 `json:"image_rate_multiplier"`
|
||||
BatchImageDiscountMultiplier *float64 `json:"batch_image_discount_multiplier"`
|
||||
BatchImageHoldMultiplier *float64 `json:"batch_image_hold_multiplier"`
|
||||
VideoRateIndependent *bool `json:"video_rate_independent"`
|
||||
VideoRateMultiplier *float64 `json:"video_rate_multiplier"`
|
||||
PeakRateEnabled *bool `json:"peak_rate_enabled"`
|
||||
PeakStart *string `json:"peak_start"`
|
||||
PeakEnd *string `json:"peak_end"`
|
||||
@@ -153,6 +160,9 @@ type UpdateGroupRequest struct {
|
||||
ImagePrice1K *float64 `json:"image_price_1k"`
|
||||
ImagePrice2K *float64 `json:"image_price_2k"`
|
||||
ImagePrice4K *float64 `json:"image_price_4k"`
|
||||
VideoPrice480P *float64 `json:"video_price_480p"`
|
||||
VideoPrice720P *float64 `json:"video_price_720p"`
|
||||
VideoPrice1080P *float64 `json:"video_price_1080p"`
|
||||
ClaudeCodeOnly *bool `json:"claude_code_only"`
|
||||
FallbackGroupID *int64 `json:"fallback_group_id"`
|
||||
FallbackGroupIDOnInvalidRequest *int64 `json:"fallback_group_id_on_invalid_request"`
|
||||
@@ -312,6 +322,8 @@ func (h *GroupHandler) Create(c *gin.Context) {
|
||||
ImageRateMultiplier: req.ImageRateMultiplier,
|
||||
BatchImageDiscountMultiplier: req.BatchImageDiscountMultiplier,
|
||||
BatchImageHoldMultiplier: req.BatchImageHoldMultiplier,
|
||||
VideoRateIndependent: req.VideoRateIndependent,
|
||||
VideoRateMultiplier: req.VideoRateMultiplier,
|
||||
PeakRateEnabled: req.PeakRateEnabled,
|
||||
PeakStart: req.PeakStart,
|
||||
PeakEnd: req.PeakEnd,
|
||||
@@ -319,6 +331,9 @@ func (h *GroupHandler) Create(c *gin.Context) {
|
||||
ImagePrice1K: req.ImagePrice1K,
|
||||
ImagePrice2K: req.ImagePrice2K,
|
||||
ImagePrice4K: req.ImagePrice4K,
|
||||
VideoPrice480P: req.VideoPrice480P,
|
||||
VideoPrice720P: req.VideoPrice720P,
|
||||
VideoPrice1080P: req.VideoPrice1080P,
|
||||
ClaudeCodeOnly: req.ClaudeCodeOnly,
|
||||
FallbackGroupID: req.FallbackGroupID,
|
||||
FallbackGroupIDOnInvalidRequest: req.FallbackGroupIDOnInvalidRequest,
|
||||
@@ -375,6 +390,8 @@ func (h *GroupHandler) Update(c *gin.Context) {
|
||||
ImageRateMultiplier: req.ImageRateMultiplier,
|
||||
BatchImageDiscountMultiplier: req.BatchImageDiscountMultiplier,
|
||||
BatchImageHoldMultiplier: req.BatchImageHoldMultiplier,
|
||||
VideoRateIndependent: req.VideoRateIndependent,
|
||||
VideoRateMultiplier: req.VideoRateMultiplier,
|
||||
PeakRateEnabled: req.PeakRateEnabled,
|
||||
PeakStart: req.PeakStart,
|
||||
PeakEnd: req.PeakEnd,
|
||||
@@ -382,6 +399,9 @@ func (h *GroupHandler) Update(c *gin.Context) {
|
||||
ImagePrice1K: req.ImagePrice1K,
|
||||
ImagePrice2K: req.ImagePrice2K,
|
||||
ImagePrice4K: req.ImagePrice4K,
|
||||
VideoPrice480P: req.VideoPrice480P,
|
||||
VideoPrice720P: req.VideoPrice720P,
|
||||
VideoPrice1080P: req.VideoPrice1080P,
|
||||
ClaudeCodeOnly: req.ClaudeCodeOnly,
|
||||
FallbackGroupID: req.FallbackGroupID,
|
||||
FallbackGroupIDOnInvalidRequest: req.FallbackGroupIDOnInvalidRequest,
|
||||
|
||||
@@ -187,6 +187,8 @@ func groupFromServiceBase(g *service.Group) Group {
|
||||
ImageRateMultiplier: g.ImageRateMultiplier,
|
||||
BatchImageDiscountMultiplier: g.BatchImageDiscountMultiplier,
|
||||
BatchImageHoldMultiplier: g.BatchImageHoldMultiplier,
|
||||
VideoRateIndependent: g.VideoRateIndependent,
|
||||
VideoRateMultiplier: g.VideoRateMultiplier,
|
||||
PeakRateEnabled: g.PeakRateEnabled,
|
||||
PeakStart: g.PeakStart,
|
||||
PeakEnd: g.PeakEnd,
|
||||
@@ -194,6 +196,9 @@ func groupFromServiceBase(g *service.Group) Group {
|
||||
ImagePrice1K: g.ImagePrice1K,
|
||||
ImagePrice2K: g.ImagePrice2K,
|
||||
ImagePrice4K: g.ImagePrice4K,
|
||||
VideoPrice480P: g.VideoPrice480P,
|
||||
VideoPrice720P: g.VideoPrice720P,
|
||||
VideoPrice1080P: g.VideoPrice1080P,
|
||||
ClaudeCodeOnly: g.ClaudeCodeOnly,
|
||||
FallbackGroupID: g.FallbackGroupID,
|
||||
FallbackGroupIDOnInvalidRequest: g.FallbackGroupIDOnInvalidRequest,
|
||||
|
||||
@@ -107,6 +107,8 @@ type Group struct {
|
||||
ImageRateMultiplier float64 `json:"image_rate_multiplier"`
|
||||
BatchImageDiscountMultiplier float64 `json:"batch_image_discount_multiplier"`
|
||||
BatchImageHoldMultiplier float64 `json:"batch_image_hold_multiplier"`
|
||||
VideoRateIndependent bool `json:"video_rate_independent"`
|
||||
VideoRateMultiplier float64 `json:"video_rate_multiplier"`
|
||||
// 高峰时段倍率配置
|
||||
PeakRateEnabled bool `json:"peak_rate_enabled"`
|
||||
PeakStart string `json:"peak_start"`
|
||||
@@ -115,6 +117,9 @@ type Group struct {
|
||||
ImagePrice1K *float64 `json:"image_price_1k"`
|
||||
ImagePrice2K *float64 `json:"image_price_2k"`
|
||||
ImagePrice4K *float64 `json:"image_price_4k"`
|
||||
VideoPrice480P *float64 `json:"video_price_480p"`
|
||||
VideoPrice720P *float64 `json:"video_price_720p"`
|
||||
VideoPrice1080P *float64 `json:"video_price_1080p"`
|
||||
|
||||
// Claude Code 客户端限制
|
||||
ClaudeCodeOnly bool `json:"claude_code_only"`
|
||||
|
||||
@@ -138,7 +138,7 @@ func (h *UsageHandler) parseUserUsageFilters(c *gin.Context, requireRange bool)
|
||||
}
|
||||
|
||||
billingMode := strings.TrimSpace(c.Query("billing_mode"))
|
||||
if billingMode != "" && !service.BillingMode(billingMode).IsValid() {
|
||||
if billingMode != "" && !service.BillingMode(billingMode).IsValidUsageFilter() {
|
||||
response.BadRequest(c, "Invalid billing_mode")
|
||||
return nil, false
|
||||
}
|
||||
|
||||
@@ -162,6 +162,18 @@ func TestUserUsageListInvalidBillingMode(t *testing.T) {
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code)
|
||||
}
|
||||
|
||||
func TestUserUsageListAllowsVideoBillingMode(t *testing.T) {
|
||||
repo := &userUsageRepoCapture{}
|
||||
router := newUserUsageRequestTypeTestRouter(repo)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/usage?billing_mode=video", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
require.Equal(t, "video", repo.listFilters.BillingMode)
|
||||
}
|
||||
|
||||
func TestUserUsageListKeepsUserBillingAndIPWithoutAdminCostFields(t *testing.T) {
|
||||
ipAddress := "203.0.113.10"
|
||||
upstreamModel := "upstream-private-model"
|
||||
|
||||
@@ -185,6 +185,11 @@ func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*se
|
||||
group.FieldImagePrice1k,
|
||||
group.FieldImagePrice2k,
|
||||
group.FieldImagePrice4k,
|
||||
group.FieldVideoRateIndependent,
|
||||
group.FieldVideoRateMultiplier,
|
||||
group.FieldVideoPrice480p,
|
||||
group.FieldVideoPrice720p,
|
||||
group.FieldVideoPrice1080p,
|
||||
group.FieldClaudeCodeOnly,
|
||||
group.FieldFallbackGroupID,
|
||||
group.FieldFallbackGroupIDOnInvalidRequest,
|
||||
@@ -899,6 +904,11 @@ func groupEntityToService(g *dbent.Group) *service.Group {
|
||||
ImagePrice4K: g.ImagePrice4k,
|
||||
BatchImageDiscountMultiplier: g.BatchImageDiscountMultiplier,
|
||||
BatchImageHoldMultiplier: g.BatchImageHoldMultiplier,
|
||||
VideoRateIndependent: g.VideoRateIndependent,
|
||||
VideoRateMultiplier: g.VideoRateMultiplier,
|
||||
VideoPrice480P: g.VideoPrice480p,
|
||||
VideoPrice720P: g.VideoPrice720p,
|
||||
VideoPrice1080P: g.VideoPrice1080p,
|
||||
DefaultValidityDays: g.DefaultValidityDays,
|
||||
ClaudeCodeOnly: g.ClaudeCodeOnly,
|
||||
FallbackGroupID: g.FallbackGroupID,
|
||||
|
||||
@@ -58,6 +58,11 @@ func (r *groupRepository) Create(ctx context.Context, groupIn *service.Group) er
|
||||
SetNillableImagePrice4k(groupIn.ImagePrice4K).
|
||||
SetBatchImageDiscountMultiplier(groupIn.BatchImageDiscountMultiplier).
|
||||
SetBatchImageHoldMultiplier(groupIn.BatchImageHoldMultiplier).
|
||||
SetVideoRateIndependent(groupIn.VideoRateIndependent).
|
||||
SetVideoRateMultiplier(groupIn.VideoRateMultiplier).
|
||||
SetNillableVideoPrice480p(groupIn.VideoPrice480P).
|
||||
SetNillableVideoPrice720p(groupIn.VideoPrice720P).
|
||||
SetNillableVideoPrice1080p(groupIn.VideoPrice1080P).
|
||||
SetDefaultValidityDays(groupIn.DefaultValidityDays).
|
||||
SetClaudeCodeOnly(groupIn.ClaudeCodeOnly).
|
||||
SetNillableFallbackGroupID(groupIn.FallbackGroupID).
|
||||
@@ -143,6 +148,11 @@ func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) er
|
||||
SetNillableImagePrice4k(groupIn.ImagePrice4K).
|
||||
SetBatchImageDiscountMultiplier(groupIn.BatchImageDiscountMultiplier).
|
||||
SetBatchImageHoldMultiplier(groupIn.BatchImageHoldMultiplier).
|
||||
SetVideoRateIndependent(groupIn.VideoRateIndependent).
|
||||
SetVideoRateMultiplier(groupIn.VideoRateMultiplier).
|
||||
SetNillableVideoPrice480p(groupIn.VideoPrice480P).
|
||||
SetNillableVideoPrice720p(groupIn.VideoPrice720P).
|
||||
SetNillableVideoPrice1080p(groupIn.VideoPrice1080P).
|
||||
SetDefaultValidityDays(groupIn.DefaultValidityDays).
|
||||
SetClaudeCodeOnly(groupIn.ClaudeCodeOnly).
|
||||
SetModelRoutingEnabled(groupIn.ModelRoutingEnabled).
|
||||
@@ -190,6 +200,21 @@ func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) er
|
||||
} else {
|
||||
builder = builder.ClearImagePrice4k()
|
||||
}
|
||||
if groupIn.VideoPrice480P != nil {
|
||||
builder = builder.SetVideoPrice480p(*groupIn.VideoPrice480P)
|
||||
} else {
|
||||
builder = builder.ClearVideoPrice480p()
|
||||
}
|
||||
if groupIn.VideoPrice720P != nil {
|
||||
builder = builder.SetVideoPrice720p(*groupIn.VideoPrice720P)
|
||||
} else {
|
||||
builder = builder.ClearVideoPrice720p()
|
||||
}
|
||||
if groupIn.VideoPrice1080P != nil {
|
||||
builder = builder.SetVideoPrice1080p(*groupIn.VideoPrice1080P)
|
||||
} else {
|
||||
builder = builder.ClearVideoPrice1080p()
|
||||
}
|
||||
|
||||
// 处理 FallbackGroupID:nil 时清除,否则设置
|
||||
if groupIn.FallbackGroupID != nil {
|
||||
|
||||
@@ -66,6 +66,8 @@ func TestMigrationsRunner_IsIdempotent_AndSchemaIsUpToDate(t *testing.T) {
|
||||
"usage_logs",
|
||||
"usage_logs_image_billing_size_check",
|
||||
"image_count",
|
||||
"billing_mode",
|
||||
"'video'",
|
||||
"image_size IS NOT NULL",
|
||||
"'1K'",
|
||||
"'2K'",
|
||||
|
||||
@@ -80,7 +80,9 @@ func appendUsageLogBillingModeWhereConditionWithAlias(conditions []string, args
|
||||
placeholder := fmt.Sprintf("$%d", len(args)+1)
|
||||
switch service.BillingMode(mode) {
|
||||
case service.BillingModeImage:
|
||||
conditions = append(conditions, fmt.Sprintf("(%s = %s OR COALESCE(%s, 0) > 0)", column("billing_mode"), placeholder, column("image_count")))
|
||||
conditions = append(conditions, fmt.Sprintf("(%s = %s OR ((%s IS NULL OR %s = '') AND COALESCE(%s, 0) > 0))", column("billing_mode"), placeholder, column("billing_mode"), column("billing_mode"), column("image_count")))
|
||||
case service.BillingModeVideo:
|
||||
conditions = append(conditions, fmt.Sprintf("%s = %s", column("billing_mode"), placeholder))
|
||||
case service.BillingModeToken:
|
||||
conditions = append(conditions, fmt.Sprintf("(%s = %s OR ((%s IS NULL OR %s = '') AND COALESCE(%s, 0) <= 0))", column("billing_mode"), placeholder, column("billing_mode"), column("billing_mode"), column("image_count")))
|
||||
default:
|
||||
|
||||
@@ -281,9 +281,14 @@ func TestAppendUsageLogBillingModeWhereCondition(t *testing.T) {
|
||||
wantCondition string
|
||||
}{
|
||||
{
|
||||
name: "image includes legacy image rows",
|
||||
name: "image includes explicit image and legacy image rows",
|
||||
billingMode: string(service.BillingModeImage),
|
||||
wantCondition: "(billing_mode = $1 OR COALESCE(image_count, 0) > 0)",
|
||||
wantCondition: "(billing_mode = $1 OR ((billing_mode IS NULL OR billing_mode = '') AND COALESCE(image_count, 0) > 0))",
|
||||
},
|
||||
{
|
||||
name: "video remains exact",
|
||||
billingMode: string(service.BillingModeVideo),
|
||||
wantCondition: "billing_mode = $1",
|
||||
},
|
||||
{
|
||||
name: "token includes legacy non-image rows",
|
||||
@@ -309,7 +314,7 @@ func TestAppendUsageLogBillingModeWhereCondition(t *testing.T) {
|
||||
func TestAppendUsageLogBillingModeWhereConditionWithAlias(t *testing.T) {
|
||||
conditions, args := appendUsageLogBillingModeWhereConditionWithAlias(nil, nil, string(service.BillingModeImage), "ul")
|
||||
|
||||
require.Equal(t, []string{"(ul.billing_mode = $1 OR COALESCE(ul.image_count, 0) > 0)"}, conditions)
|
||||
require.Equal(t, []string{"(ul.billing_mode = $1 OR ((ul.billing_mode IS NULL OR ul.billing_mode = '') AND COALESCE(ul.image_count, 0) > 0))"}, conditions)
|
||||
require.Equal(t, []any{string(service.BillingModeImage)}, args)
|
||||
}
|
||||
|
||||
|
||||
@@ -363,12 +363,17 @@ func TestAPIContracts(t *testing.T) {
|
||||
"image_price_1k": null,
|
||||
"image_price_2k": null,
|
||||
"image_price_4k": null,
|
||||
"video_price_480p": null,
|
||||
"video_price_720p": null,
|
||||
"video_price_1080p": null,
|
||||
"allow_image_generation": false,
|
||||
"allow_batch_image_generation": false,
|
||||
"batch_image_discount_multiplier": 0,
|
||||
"batch_image_hold_multiplier": 0,
|
||||
"image_rate_independent": false,
|
||||
"image_rate_multiplier": 0,
|
||||
"video_rate_independent": false,
|
||||
"video_rate_multiplier": 0,
|
||||
"claude_code_only": false,
|
||||
"allow_messages_dispatch": false,
|
||||
"fallback_group_id": null,
|
||||
|
||||
@@ -153,6 +153,9 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
|
||||
imagePrice1K := normalizePrice(input.ImagePrice1K)
|
||||
imagePrice2K := normalizePrice(input.ImagePrice2K)
|
||||
imagePrice4K := normalizePrice(input.ImagePrice4K)
|
||||
videoPrice480P := normalizePrice(input.VideoPrice480P)
|
||||
videoPrice720P := normalizePrice(input.VideoPrice720P)
|
||||
videoPrice1080P := normalizePrice(input.VideoPrice1080P)
|
||||
imageRateMultiplier := 1.0
|
||||
if input.ImageRateMultiplier != nil {
|
||||
if *input.ImageRateMultiplier < 0 {
|
||||
@@ -179,6 +182,13 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
|
||||
if batchImageHoldMultiplier < batchImageDiscountMultiplier {
|
||||
return nil, errors.New("batch_image_hold_multiplier must be >= batch_image_discount_multiplier")
|
||||
}
|
||||
videoRateMultiplier := 1.0
|
||||
if input.VideoRateMultiplier != nil {
|
||||
if *input.VideoRateMultiplier < 0 {
|
||||
return nil, errors.New("video_rate_multiplier must be >= 0")
|
||||
}
|
||||
videoRateMultiplier = *input.VideoRateMultiplier
|
||||
}
|
||||
|
||||
peakRateMultiplier := 1.0
|
||||
if input.PeakRateMultiplier != nil {
|
||||
@@ -265,6 +275,8 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
|
||||
ImageRateMultiplier: imageRateMultiplier,
|
||||
BatchImageDiscountMultiplier: batchImageDiscountMultiplier,
|
||||
BatchImageHoldMultiplier: batchImageHoldMultiplier,
|
||||
VideoRateIndependent: input.VideoRateIndependent,
|
||||
VideoRateMultiplier: videoRateMultiplier,
|
||||
PeakRateEnabled: peakRateEnabled,
|
||||
PeakStart: peakStart,
|
||||
PeakEnd: peakEnd,
|
||||
@@ -272,6 +284,9 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
|
||||
ImagePrice1K: imagePrice1K,
|
||||
ImagePrice2K: imagePrice2K,
|
||||
ImagePrice4K: imagePrice4K,
|
||||
VideoPrice480P: videoPrice480P,
|
||||
VideoPrice720P: videoPrice720P,
|
||||
VideoPrice1080P: videoPrice1080P,
|
||||
ClaudeCodeOnly: input.ClaudeCodeOnly,
|
||||
FallbackGroupID: input.FallbackGroupID,
|
||||
FallbackGroupIDOnInvalidRequest: fallbackOnInvalidRequest,
|
||||
@@ -482,6 +497,15 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd
|
||||
group.BatchImageHoldMultiplier < group.BatchImageDiscountMultiplier {
|
||||
return nil, errors.New("batch_image_hold_multiplier must be >= batch_image_discount_multiplier")
|
||||
}
|
||||
if input.VideoRateIndependent != nil {
|
||||
group.VideoRateIndependent = *input.VideoRateIndependent
|
||||
}
|
||||
if input.VideoRateMultiplier != nil {
|
||||
if *input.VideoRateMultiplier < 0 {
|
||||
return nil, errors.New("video_rate_multiplier must be >= 0")
|
||||
}
|
||||
group.VideoRateMultiplier = *input.VideoRateMultiplier
|
||||
}
|
||||
if input.PeakRateEnabled != nil {
|
||||
group.PeakRateEnabled = *input.PeakRateEnabled
|
||||
}
|
||||
@@ -510,6 +534,15 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd
|
||||
if input.ImagePrice4K != nil {
|
||||
group.ImagePrice4K = normalizePrice(input.ImagePrice4K)
|
||||
}
|
||||
if input.VideoPrice480P != nil {
|
||||
group.VideoPrice480P = normalizePrice(input.VideoPrice480P)
|
||||
}
|
||||
if input.VideoPrice720P != nil {
|
||||
group.VideoPrice720P = normalizePrice(input.VideoPrice720P)
|
||||
}
|
||||
if input.VideoPrice1080P != nil {
|
||||
group.VideoPrice1080P = normalizePrice(input.VideoPrice1080P)
|
||||
}
|
||||
|
||||
// Claude Code 客户端限制
|
||||
if input.ClaudeCodeOnly != nil {
|
||||
|
||||
@@ -201,6 +201,8 @@ type CreateGroupInput struct {
|
||||
ImageRateMultiplier *float64
|
||||
BatchImageDiscountMultiplier *float64
|
||||
BatchImageHoldMultiplier *float64
|
||||
VideoRateIndependent bool
|
||||
VideoRateMultiplier *float64
|
||||
// 高峰时段倍率配置(PeakRateMultiplier 为 nil 时按 1.0 处理)
|
||||
PeakRateEnabled bool
|
||||
PeakStart string
|
||||
@@ -209,6 +211,9 @@ type CreateGroupInput struct {
|
||||
ImagePrice1K *float64
|
||||
ImagePrice2K *float64
|
||||
ImagePrice4K *float64
|
||||
VideoPrice480P *float64
|
||||
VideoPrice720P *float64
|
||||
VideoPrice1080P *float64
|
||||
ClaudeCodeOnly bool // 仅允许 Claude Code 客户端
|
||||
FallbackGroupID *int64 // 降级分组 ID
|
||||
// 无效请求兜底分组 ID(仅 anthropic 平台使用)
|
||||
@@ -250,6 +255,8 @@ type UpdateGroupInput struct {
|
||||
ImageRateMultiplier *float64
|
||||
BatchImageDiscountMultiplier *float64
|
||||
BatchImageHoldMultiplier *float64
|
||||
VideoRateIndependent *bool
|
||||
VideoRateMultiplier *float64
|
||||
// 高峰时段倍率配置(nil 表示不修改)
|
||||
PeakRateEnabled *bool
|
||||
PeakStart *string
|
||||
@@ -258,6 +265,9 @@ type UpdateGroupInput struct {
|
||||
ImagePrice1K *float64
|
||||
ImagePrice2K *float64
|
||||
ImagePrice4K *float64
|
||||
VideoPrice480P *float64
|
||||
VideoPrice720P *float64
|
||||
VideoPrice1080P *float64
|
||||
ClaudeCodeOnly *bool // 仅允许 Claude Code 客户端
|
||||
FallbackGroupID *int64 // 降级分组 ID
|
||||
// 无效请求兜底分组 ID(仅 anthropic 平台使用)
|
||||
|
||||
@@ -174,6 +174,42 @@ func TestAdminService_CreateGroup_WithImagePricing(t *testing.T) {
|
||||
require.InDelta(t, 0.30, *repo.created.ImagePrice4K, 0.0001)
|
||||
}
|
||||
|
||||
func TestAdminService_CreateGroup_WithVideoPricing(t *testing.T) {
|
||||
repo := &groupRepoStubForAdmin{}
|
||||
svc := &adminServiceImpl{groupRepo: repo}
|
||||
|
||||
price480P := 0.08
|
||||
price720P := 0.12
|
||||
price1080P := 0.18
|
||||
videoMultiplier := 0.75
|
||||
|
||||
input := &CreateGroupInput{
|
||||
Name: "grok-video",
|
||||
Description: "Grok video group",
|
||||
Platform: PlatformGrok,
|
||||
RateMultiplier: 1.0,
|
||||
VideoRateIndependent: true,
|
||||
VideoRateMultiplier: &videoMultiplier,
|
||||
VideoPrice480P: &price480P,
|
||||
VideoPrice720P: &price720P,
|
||||
VideoPrice1080P: &price1080P,
|
||||
}
|
||||
|
||||
group, err := svc.CreateGroup(context.Background(), input)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, group)
|
||||
|
||||
require.NotNil(t, repo.created)
|
||||
require.True(t, repo.created.VideoRateIndependent)
|
||||
require.InDelta(t, 0.75, repo.created.VideoRateMultiplier, 1e-12)
|
||||
require.NotNil(t, repo.created.VideoPrice480P)
|
||||
require.NotNil(t, repo.created.VideoPrice720P)
|
||||
require.NotNil(t, repo.created.VideoPrice1080P)
|
||||
require.InDelta(t, 0.08, *repo.created.VideoPrice480P, 0.0001)
|
||||
require.InDelta(t, 0.12, *repo.created.VideoPrice720P, 0.0001)
|
||||
require.InDelta(t, 0.18, *repo.created.VideoPrice1080P, 0.0001)
|
||||
}
|
||||
|
||||
// TestAdminService_CreateGroup_NilImagePricing 测试 ImagePrice 为 nil 时正常创建
|
||||
func TestAdminService_CreateGroup_NilImagePricing(t *testing.T) {
|
||||
repo := &groupRepoStubForAdmin{}
|
||||
@@ -307,6 +343,42 @@ func TestAdminService_UpdateGroup_WithImagePricing(t *testing.T) {
|
||||
require.InDelta(t, 0.36, *repo.updated.ImagePrice4K, 0.0001)
|
||||
}
|
||||
|
||||
func TestAdminService_UpdateGroup_WithVideoPricing(t *testing.T) {
|
||||
existingGroup := &Group{
|
||||
ID: 1,
|
||||
Name: "existing-grok",
|
||||
Platform: PlatformGrok,
|
||||
Status: StatusActive,
|
||||
}
|
||||
repo := &groupRepoStubForAdmin{getByID: existingGroup}
|
||||
svc := &adminServiceImpl{groupRepo: repo}
|
||||
|
||||
price480P := 0.09
|
||||
price720P := 0.13
|
||||
price1080P := 0.19
|
||||
videoMultiplier := 0.6
|
||||
independent := true
|
||||
|
||||
input := &UpdateGroupInput{
|
||||
VideoRateIndependent: &independent,
|
||||
VideoRateMultiplier: &videoMultiplier,
|
||||
VideoPrice480P: &price480P,
|
||||
VideoPrice720P: &price720P,
|
||||
VideoPrice1080P: &price1080P,
|
||||
}
|
||||
|
||||
group, err := svc.UpdateGroup(context.Background(), 1, input)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, group)
|
||||
|
||||
require.NotNil(t, repo.updated)
|
||||
require.True(t, repo.updated.VideoRateIndependent)
|
||||
require.InDelta(t, 0.6, repo.updated.VideoRateMultiplier, 1e-12)
|
||||
require.InDelta(t, 0.09, *repo.updated.VideoPrice480P, 0.0001)
|
||||
require.InDelta(t, 0.13, *repo.updated.VideoPrice720P, 0.0001)
|
||||
require.InDelta(t, 0.19, *repo.updated.VideoPrice1080P, 0.0001)
|
||||
}
|
||||
|
||||
// TestAdminService_UpdateGroup_PartialImagePricing 测试仅更新部分 ImagePrice 字段
|
||||
func TestAdminService_UpdateGroup_PartialImagePricing(t *testing.T) {
|
||||
oldPrice2K := 0.15
|
||||
@@ -542,6 +614,25 @@ func TestAdminService_GroupBatchImagePricingValidation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_UpdateGroup_RejectsNegativeVideoRateMultiplier(t *testing.T) {
|
||||
existingGroup := &Group{
|
||||
ID: 1,
|
||||
Name: "existing-group",
|
||||
Platform: PlatformGrok,
|
||||
Status: StatusActive,
|
||||
VideoRateMultiplier: 1,
|
||||
}
|
||||
repo := &groupRepoStubForAdmin{getByID: existingGroup}
|
||||
svc := &adminServiceImpl{groupRepo: repo}
|
||||
negative := -0.1
|
||||
|
||||
_, err := svc.UpdateGroup(context.Background(), 1, &UpdateGroupInput{
|
||||
VideoRateMultiplier: &negative,
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Nil(t, repo.updated)
|
||||
}
|
||||
|
||||
func TestAdminService_UpdateGroup_InvalidatesAuthCacheOnRPMLimitChange(t *testing.T) {
|
||||
existingGroup := &Group{
|
||||
ID: 1,
|
||||
|
||||
@@ -73,6 +73,11 @@ type APIKeyAuthGroupSnapshot struct {
|
||||
ImagePrice1K *float64 `json:"image_price_1k,omitempty"`
|
||||
ImagePrice2K *float64 `json:"image_price_2k,omitempty"`
|
||||
ImagePrice4K *float64 `json:"image_price_4k,omitempty"`
|
||||
VideoRateIndependent bool `json:"video_rate_independent"`
|
||||
VideoRateMultiplier float64 `json:"video_rate_multiplier"`
|
||||
VideoPrice480P *float64 `json:"video_price_480p,omitempty"`
|
||||
VideoPrice720P *float64 `json:"video_price_720p,omitempty"`
|
||||
VideoPrice1080P *float64 `json:"video_price_1080p,omitempty"`
|
||||
ClaudeCodeOnly bool `json:"claude_code_only"`
|
||||
FallbackGroupID *int64 `json:"fallback_group_id,omitempty"`
|
||||
FallbackGroupIDOnInvalidRequest *int64 `json:"fallback_group_id_on_invalid_request,omitempty"`
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/dgraph-io/ristretto"
|
||||
)
|
||||
|
||||
const apiKeyAuthSnapshotVersion = 13 // v13: include group peak rate fields
|
||||
const apiKeyAuthSnapshotVersion = 14 // v14: include group video pricing fields
|
||||
|
||||
type apiKeyAuthCacheConfig struct {
|
||||
l1Size int
|
||||
@@ -265,6 +265,11 @@ func (s *APIKeyService) snapshotFromAPIKey(ctx context.Context, apiKey *APIKey)
|
||||
ImagePrice1K: apiKey.Group.ImagePrice1K,
|
||||
ImagePrice2K: apiKey.Group.ImagePrice2K,
|
||||
ImagePrice4K: apiKey.Group.ImagePrice4K,
|
||||
VideoRateIndependent: apiKey.Group.VideoRateIndependent,
|
||||
VideoRateMultiplier: apiKey.Group.VideoRateMultiplier,
|
||||
VideoPrice480P: apiKey.Group.VideoPrice480P,
|
||||
VideoPrice720P: apiKey.Group.VideoPrice720P,
|
||||
VideoPrice1080P: apiKey.Group.VideoPrice1080P,
|
||||
ClaudeCodeOnly: apiKey.Group.ClaudeCodeOnly,
|
||||
FallbackGroupID: apiKey.Group.FallbackGroupID,
|
||||
FallbackGroupIDOnInvalidRequest: apiKey.Group.FallbackGroupIDOnInvalidRequest,
|
||||
@@ -343,6 +348,11 @@ func (s *APIKeyService) snapshotToAPIKey(key string, snapshot *APIKeyAuthSnapsho
|
||||
ImagePrice1K: snapshot.Group.ImagePrice1K,
|
||||
ImagePrice2K: snapshot.Group.ImagePrice2K,
|
||||
ImagePrice4K: snapshot.Group.ImagePrice4K,
|
||||
VideoRateIndependent: snapshot.Group.VideoRateIndependent,
|
||||
VideoRateMultiplier: snapshot.Group.VideoRateMultiplier,
|
||||
VideoPrice480P: snapshot.Group.VideoPrice480P,
|
||||
VideoPrice720P: snapshot.Group.VideoPrice720P,
|
||||
VideoPrice1080P: snapshot.Group.VideoPrice1080P,
|
||||
ClaudeCodeOnly: snapshot.Group.ClaudeCodeOnly,
|
||||
FallbackGroupID: snapshot.Group.FallbackGroupID,
|
||||
FallbackGroupIDOnInvalidRequest: snapshot.Group.FallbackGroupIDOnInvalidRequest,
|
||||
|
||||
@@ -1231,6 +1231,28 @@ type ImagePriceConfig struct {
|
||||
Price4K *float64 // 4K 尺寸价格(nil 表示使用默认值)
|
||||
}
|
||||
|
||||
// VideoPriceConfig 视频生成计费配置。
|
||||
type VideoPriceConfig struct {
|
||||
Price480P *float64 // 480p 视频价格(nil 表示使用默认值)
|
||||
Price720P *float64 // 720p 视频价格(nil 表示使用默认值)
|
||||
Price1080P *float64 // 1080p 视频价格(nil 表示使用默认值)
|
||||
}
|
||||
|
||||
const (
|
||||
defaultImageGenerationPrice = 0.134
|
||||
|
||||
defaultGrokImagineImagePrice1K = 0.02
|
||||
defaultGrokImagineImagePrice2K = 0.02
|
||||
defaultGrokImagineImageQualityPrice1K = 0.05
|
||||
defaultGrokImagineImageQualityPrice2K = 0.07
|
||||
|
||||
defaultGrokImagineVideoPrice480P = 0.05
|
||||
defaultGrokImagineVideoPrice720P = 0.07
|
||||
defaultGrokImagineVideo15Price480P = 0.08
|
||||
defaultGrokImagineVideo15Price720P = 0.14
|
||||
defaultGrokImagineVideo15Price1080P = 0.25
|
||||
)
|
||||
|
||||
// CalculateImageCost 计算图片生成费用
|
||||
// model: 请求的模型名称(用于获取 LiteLLM 默认价格)
|
||||
// imageSize: 图片尺寸 "1K", "2K", "4K"
|
||||
@@ -1262,6 +1284,33 @@ func (s *BillingService) CalculateImageCost(model string, imageSize string, imag
|
||||
}
|
||||
}
|
||||
|
||||
// CalculateVideoCost 计算视频生成费用。
|
||||
// model: 请求的模型名称(用于获取默认价格)
|
||||
// resolution: 视频分辨率 "480p", "720p", "1080p"
|
||||
// videoCount: 生成的视频数量
|
||||
// groupConfig: 分组配置的价格(可能为 nil,表示使用默认值)
|
||||
// rateMultiplier: 费率倍数
|
||||
func (s *BillingService) CalculateVideoCost(model string, resolution string, videoCount int, groupConfig *VideoPriceConfig, rateMultiplier float64) *CostBreakdown {
|
||||
if videoCount <= 0 {
|
||||
return &CostBreakdown{}
|
||||
}
|
||||
resolution = NormalizeVideoBillingResolutionOrDefault(resolution)
|
||||
|
||||
unitPrice := s.getVideoUnitPrice(model, resolution, groupConfig)
|
||||
totalCost := unitPrice * float64(videoCount)
|
||||
|
||||
if rateMultiplier < 0 {
|
||||
rateMultiplier = 0
|
||||
}
|
||||
actualCost := totalCost * rateMultiplier
|
||||
|
||||
return &CostBreakdown{
|
||||
TotalCost: totalCost,
|
||||
ActualCost: actualCost,
|
||||
BillingMode: string(BillingModeVideo),
|
||||
}
|
||||
}
|
||||
|
||||
// getImageUnitPrice 获取图片单价
|
||||
func (s *BillingService) getImageUnitPrice(model string, imageSize string, groupConfig *ImagePriceConfig) float64 {
|
||||
// 优先使用分组配置的价格
|
||||
@@ -1286,8 +1335,33 @@ func (s *BillingService) getImageUnitPrice(model string, imageSize string, group
|
||||
return s.getDefaultImagePrice(model, imageSize)
|
||||
}
|
||||
|
||||
func (s *BillingService) getVideoUnitPrice(model string, resolution string, groupConfig *VideoPriceConfig) float64 {
|
||||
if groupConfig != nil {
|
||||
switch resolution {
|
||||
case VideoBillingResolution480P:
|
||||
if groupConfig.Price480P != nil {
|
||||
return *groupConfig.Price480P
|
||||
}
|
||||
case VideoBillingResolution720P:
|
||||
if groupConfig.Price720P != nil {
|
||||
return *groupConfig.Price720P
|
||||
}
|
||||
case VideoBillingResolution1080P:
|
||||
if groupConfig.Price1080P != nil {
|
||||
return *groupConfig.Price1080P
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s.getDefaultVideoPrice(model, resolution)
|
||||
}
|
||||
|
||||
// getDefaultImagePrice 获取 LiteLLM 默认图片价格
|
||||
func (s *BillingService) getDefaultImagePrice(model string, imageSize string) float64 {
|
||||
if price, ok := getDefaultGrokImagineImagePrice(model, imageSize); ok {
|
||||
return price
|
||||
}
|
||||
|
||||
basePrice := 0.0
|
||||
|
||||
// 从 PricingService 获取 output_cost_per_image
|
||||
@@ -1300,7 +1374,7 @@ func (s *BillingService) getDefaultImagePrice(model string, imageSize string) fl
|
||||
|
||||
// 如果没有找到价格,使用硬编码默认值($0.134,来自 gemini-3-pro-image-preview)
|
||||
if basePrice <= 0 {
|
||||
basePrice = 0.134
|
||||
basePrice = defaultImageGenerationPrice
|
||||
}
|
||||
|
||||
// 2K 尺寸 1.5 倍,4K 尺寸翻倍
|
||||
@@ -1313,3 +1387,73 @@ func (s *BillingService) getDefaultImagePrice(model string, imageSize string) fl
|
||||
|
||||
return basePrice
|
||||
}
|
||||
|
||||
func (s *BillingService) getDefaultVideoPrice(model string, resolution string) float64 {
|
||||
if price, ok := getDefaultGrokImagineVideoPrice(model, resolution); ok {
|
||||
return price
|
||||
}
|
||||
|
||||
// The bundled LiteLLM schema does not expose an output video generation price.
|
||||
// Keep the historical model default as the fallback, while letting group-level
|
||||
// video prices override it independently from image prices.
|
||||
return s.getDefaultImagePrice(model, ImageBillingSize2K)
|
||||
}
|
||||
|
||||
func getDefaultGrokImagineImagePrice(model string, imageSize string) (float64, bool) {
|
||||
model = strings.ToLower(strings.TrimSpace(model))
|
||||
switch model {
|
||||
case "grok-imagine-image-quality":
|
||||
return getGrokImagineImageTierPrice(
|
||||
imageSize,
|
||||
defaultGrokImagineImageQualityPrice1K,
|
||||
defaultGrokImagineImageQualityPrice2K,
|
||||
), true
|
||||
case "grok-imagine", "grok-imagine-image", "grok-imagine-edit":
|
||||
return getGrokImagineImageTierPrice(
|
||||
imageSize,
|
||||
defaultGrokImagineImagePrice1K,
|
||||
defaultGrokImagineImagePrice2K,
|
||||
), true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func getGrokImagineImageTierPrice(imageSize string, price1K float64, price2K float64) float64 {
|
||||
switch NormalizeImageBillingTierOrDefault(imageSize) {
|
||||
case ImageBillingSize1K:
|
||||
return price1K
|
||||
case ImageBillingSize2K, ImageBillingSize4K:
|
||||
return price2K
|
||||
default:
|
||||
return price2K
|
||||
}
|
||||
}
|
||||
|
||||
func getDefaultGrokImagineVideoPrice(model string, resolution string) (float64, bool) {
|
||||
model = strings.ToLower(strings.TrimSpace(model))
|
||||
switch {
|
||||
case strings.HasPrefix(model, "grok-imagine-video-1.5"):
|
||||
switch NormalizeVideoBillingResolutionOrDefault(resolution) {
|
||||
case VideoBillingResolution480P:
|
||||
return defaultGrokImagineVideo15Price480P, true
|
||||
case VideoBillingResolution720P:
|
||||
return defaultGrokImagineVideo15Price720P, true
|
||||
case VideoBillingResolution1080P:
|
||||
return defaultGrokImagineVideo15Price1080P, true
|
||||
default:
|
||||
return defaultGrokImagineVideo15Price480P, true
|
||||
}
|
||||
case strings.HasPrefix(model, "grok-imagine-video"):
|
||||
switch NormalizeVideoBillingResolutionOrDefault(resolution) {
|
||||
case VideoBillingResolution480P:
|
||||
return defaultGrokImagineVideoPrice480P, true
|
||||
case VideoBillingResolution720P, VideoBillingResolution1080P:
|
||||
return defaultGrokImagineVideoPrice720P, true
|
||||
default:
|
||||
return defaultGrokImagineVideoPrice480P, true
|
||||
}
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,6 +872,50 @@ func TestCalculateImageCost(t *testing.T) {
|
||||
require.InDelta(t, 0.134*3, cost.ActualCost, 1e-10)
|
||||
}
|
||||
|
||||
func TestCalculateVideoCostUsesSeparateConfig(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
|
||||
imagePrice := 0.4
|
||||
videoPrice := 0.08
|
||||
imageCost := svc.CalculateImageCost("grok-imagine-video", "2K", 1, &ImagePriceConfig{Price2K: &imagePrice}, 1.0)
|
||||
videoCost := svc.CalculateVideoCost("grok-imagine-video", "480p", 1, &VideoPriceConfig{Price480P: &videoPrice}, 0.5)
|
||||
|
||||
require.InDelta(t, 0.4, imageCost.TotalCost, 1e-10)
|
||||
require.InDelta(t, 0.08, videoCost.TotalCost, 1e-10)
|
||||
require.InDelta(t, 0.04, videoCost.ActualCost, 1e-10)
|
||||
require.Equal(t, string(BillingModeVideo), videoCost.BillingMode)
|
||||
}
|
||||
|
||||
func TestCalculateGrokImagineImageCostUsesDefaultRateCard(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
|
||||
standard1K := svc.CalculateImageCost("grok-imagine-image", "1K", 1, nil, 1.0)
|
||||
standard2K := svc.CalculateImageCost("grok-imagine-image", "2K", 1, nil, 1.0)
|
||||
quality1K := svc.CalculateImageCost("grok-imagine-image-quality", "1K", 1, nil, 1.0)
|
||||
quality2K := svc.CalculateImageCost("grok-imagine-image-quality", "2K", 1, nil, 1.0)
|
||||
|
||||
require.InDelta(t, 0.02, standard1K.TotalCost, 1e-10)
|
||||
require.InDelta(t, 0.02, standard2K.TotalCost, 1e-10)
|
||||
require.InDelta(t, 0.05, quality1K.TotalCost, 1e-10)
|
||||
require.InDelta(t, 0.07, quality2K.TotalCost, 1e-10)
|
||||
}
|
||||
|
||||
func TestCalculateGrokImagineVideoCostUsesDefaultRateCard(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
|
||||
standard480P := svc.CalculateVideoCost("grok-imagine-video", "480p", 1, nil, 1.0)
|
||||
standard720P := svc.CalculateVideoCost("grok-imagine-video", "720p", 1, nil, 1.0)
|
||||
video15_480P := svc.CalculateVideoCost("grok-imagine-video-1.5", "480p", 1, nil, 1.0)
|
||||
video15_720P := svc.CalculateVideoCost("grok-imagine-video-1.5", "720p", 1, nil, 1.0)
|
||||
video15_1080P := svc.CalculateVideoCost("grok-imagine-video-1.5", "1080p", 1, nil, 1.0)
|
||||
|
||||
require.InDelta(t, 0.05, standard480P.TotalCost, 1e-10)
|
||||
require.InDelta(t, 0.07, standard720P.TotalCost, 1e-10)
|
||||
require.InDelta(t, 0.08, video15_480P.TotalCost, 1e-10)
|
||||
require.InDelta(t, 0.14, video15_720P.TotalCost, 1e-10)
|
||||
require.InDelta(t, 0.25, video15_1080P.TotalCost, 1e-10)
|
||||
}
|
||||
|
||||
func TestIsModelSupported(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ const (
|
||||
BillingModeToken BillingMode = "token" // 按 token 区间计费
|
||||
BillingModePerRequest BillingMode = "per_request" // 按次计费(支持上下文窗口分层)
|
||||
BillingModeImage BillingMode = "image" // 图片计费(当前按次,预留 token 计费)
|
||||
BillingModeVideo BillingMode = "video" // 视频生成计费(按视频生成次数)
|
||||
)
|
||||
|
||||
// IsValid 检查 BillingMode 是否为合法值
|
||||
@@ -25,6 +26,15 @@ func (m BillingMode) IsValid() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsValidUsageFilter 检查 BillingMode 是否可用于使用记录筛选。
|
||||
func (m BillingMode) IsValidUsageFilter() bool {
|
||||
switch m {
|
||||
case BillingModeToken, BillingModePerRequest, BillingModeImage, BillingModeVideo, "":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
BillingModelSourceRequested = "requested"
|
||||
BillingModelSourceUpstream = "upstream"
|
||||
|
||||
@@ -796,6 +796,10 @@ func (s *GatewayService) calculateImageCost(
|
||||
multiplier float64,
|
||||
) *CostBreakdown {
|
||||
sizeTier := NormalizeImageBillingTierOrDefault(result.ImageSize)
|
||||
groupConfig := imagePriceConfigFromAPIKey(apiKey)
|
||||
if apiKeyHasConfiguredImagePrice(apiKey, sizeTier) {
|
||||
return s.billingService.CalculateImageCost(billingModel, sizeTier, result.ImageCount, groupConfig, multiplier)
|
||||
}
|
||||
if resolved := s.resolveChannelPricing(ctx, billingModel, apiKey); resolved != nil {
|
||||
tokens := UsageTokens{
|
||||
InputTokens: result.Usage.InputTokens,
|
||||
@@ -821,14 +825,6 @@ func (s *GatewayService) calculateImageCost(
|
||||
return cost
|
||||
}
|
||||
|
||||
var groupConfig *ImagePriceConfig
|
||||
if apiKey.Group != nil {
|
||||
groupConfig = &ImagePriceConfig{
|
||||
Price1K: apiKey.Group.ImagePrice1K,
|
||||
Price2K: apiKey.Group.ImagePrice2K,
|
||||
Price4K: apiKey.Group.ImagePrice4K,
|
||||
}
|
||||
}
|
||||
return s.billingService.CalculateImageCost(billingModel, sizeTier, result.ImageCount, groupConfig, multiplier)
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ type GrokMediaRequestInfo struct {
|
||||
N int
|
||||
Size string
|
||||
SizeTier string
|
||||
Resolution string
|
||||
InputImageURLs []string
|
||||
MaskImageURL string
|
||||
Uploads []OpenAIImagesUpload
|
||||
@@ -114,6 +115,7 @@ func ParseGrokMediaRequest(contentType string, body []byte) GrokMediaRequestInfo
|
||||
info.Prompt = strings.TrimSpace(info.Prompt)
|
||||
info.Size = strings.TrimSpace(info.Size)
|
||||
info.SizeTier = NormalizeImageBillingTierOrDefault(info.Size)
|
||||
info.Resolution = NormalizeVideoBillingResolutionOrDefault(info.Resolution)
|
||||
if info.N <= 0 {
|
||||
info.N = 1
|
||||
}
|
||||
@@ -127,6 +129,7 @@ func parseGrokMediaJSONRequest(body []byte, info *GrokMediaRequestInfo) {
|
||||
info.Model = strings.TrimSpace(gjson.GetBytes(body, "model").String())
|
||||
info.Prompt = strings.TrimSpace(gjson.GetBytes(body, "prompt").String())
|
||||
info.Size = strings.TrimSpace(gjson.GetBytes(body, "size").String())
|
||||
info.Resolution = strings.TrimSpace(gjson.GetBytes(body, "resolution").String())
|
||||
if n := gjson.GetBytes(body, "n"); n.Exists() && n.Type == gjson.Number {
|
||||
info.N = int(n.Int())
|
||||
}
|
||||
@@ -226,6 +229,8 @@ func parseGrokMediaMultipartRequest(contentType string, body []byte, info *GrokM
|
||||
info.Prompt = value
|
||||
case "size":
|
||||
info.Size = value
|
||||
case "resolution":
|
||||
info.Resolution = value
|
||||
case "n":
|
||||
if n, err := strconv.Atoi(value); err == nil {
|
||||
info.N = n
|
||||
@@ -363,6 +368,8 @@ func (s *OpenAIGatewayService) ForwardGrokMedia(
|
||||
ImageSize: usage.ImageSize,
|
||||
ImageInputSize: usage.ImageInputSize,
|
||||
ImageOutputSizes: usage.ImageOutputSizes,
|
||||
VideoCount: usage.VideoCount,
|
||||
VideoResolution: usage.VideoResolution,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -471,6 +478,8 @@ type grokMediaUsageMetadata struct {
|
||||
ImageSize string
|
||||
ImageInputSize string
|
||||
ImageOutputSizes []string
|
||||
VideoCount int
|
||||
VideoResolution string
|
||||
}
|
||||
|
||||
func grokMediaUsageFromResponse(endpoint GrokMediaEndpoint, requestInfo GrokMediaRequestInfo, responseBody []byte) grokMediaUsageMetadata {
|
||||
@@ -491,10 +500,10 @@ func grokMediaUsageFromResponse(endpoint GrokMediaEndpoint, requestInfo GrokMedi
|
||||
meta.ImageOutputSizes = collectOpenAIResponseImageOutputSizesFromJSONBytes(responseBody)
|
||||
case GrokMediaEndpointVideosGenerations:
|
||||
meta.ResponseID = extractGrokMediaVideoRequestID(responseBody)
|
||||
// Video generation is one billable media unit; the legacy usage schema stores it in ImageCount.
|
||||
meta.VideoCount = 1
|
||||
meta.VideoResolution = requestInfo.Resolution
|
||||
// Keep the legacy media-unit counter populated for existing usage displays.
|
||||
meta.ImageCount = 1
|
||||
meta.ImageSize = requestInfo.SizeTier
|
||||
meta.ImageInputSize = requestInfo.Size
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
@@ -45,6 +45,11 @@ type Group struct {
|
||||
ImagePrice4K *float64
|
||||
BatchImageDiscountMultiplier float64
|
||||
BatchImageHoldMultiplier float64
|
||||
VideoRateIndependent bool
|
||||
VideoRateMultiplier float64
|
||||
VideoPrice480P *float64
|
||||
VideoPrice720P *float64
|
||||
VideoPrice1080P *float64
|
||||
|
||||
// Claude Code 客户端限制
|
||||
ClaudeCodeOnly bool
|
||||
@@ -125,6 +130,21 @@ func (g *Group) GetImagePrice(imageSize string) *float64 {
|
||||
}
|
||||
}
|
||||
|
||||
// GetVideoPrice 根据 resolution 返回对应的视频生成价格。
|
||||
// 如果分组未配置价格,返回 nil(调用方应使用默认值)。
|
||||
func (g *Group) GetVideoPrice(resolution string) *float64 {
|
||||
switch NormalizeVideoBillingResolutionOrDefault(resolution) {
|
||||
case VideoBillingResolution480P:
|
||||
return g.VideoPrice480P
|
||||
case VideoBillingResolution720P:
|
||||
return g.VideoPrice720P
|
||||
case VideoBillingResolution1080P:
|
||||
return g.VideoPrice1080P
|
||||
default:
|
||||
return g.VideoPrice480P
|
||||
}
|
||||
}
|
||||
|
||||
// IsGroupContextValid reports whether a group from context has the fields required for routing decisions.
|
||||
func IsGroupContextValid(group *Group) bool {
|
||||
if group == nil {
|
||||
|
||||
@@ -9,3 +9,13 @@ func resolveImageRateMultiplier(apiKey *APIKey, effectiveGroupMultiplier float64
|
||||
}
|
||||
return effectiveGroupMultiplier
|
||||
}
|
||||
|
||||
func resolveVideoRateMultiplier(apiKey *APIKey, effectiveGroupMultiplier float64) float64 {
|
||||
if apiKey != nil && apiKey.Group != nil && apiKey.Group.VideoRateIndependent {
|
||||
if apiKey.Group.VideoRateMultiplier < 0 {
|
||||
return 0
|
||||
}
|
||||
return apiKey.Group.VideoRateMultiplier
|
||||
}
|
||||
return effectiveGroupMultiplier
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package service
|
||||
|
||||
func imagePriceConfigFromAPIKey(apiKey *APIKey) *ImagePriceConfig {
|
||||
if apiKey == nil || apiKey.Group == nil {
|
||||
return nil
|
||||
}
|
||||
return &ImagePriceConfig{
|
||||
Price1K: apiKey.Group.ImagePrice1K,
|
||||
Price2K: apiKey.Group.ImagePrice2K,
|
||||
Price4K: apiKey.Group.ImagePrice4K,
|
||||
}
|
||||
}
|
||||
|
||||
func apiKeyHasConfiguredImagePrice(apiKey *APIKey, imageSize string) bool {
|
||||
return apiKey != nil && apiKey.Group != nil && apiKey.Group.GetImagePrice(imageSize) != nil
|
||||
}
|
||||
|
||||
func videoPriceConfigFromAPIKey(apiKey *APIKey) *VideoPriceConfig {
|
||||
if apiKey == nil || apiKey.Group == nil {
|
||||
return nil
|
||||
}
|
||||
return &VideoPriceConfig{
|
||||
Price480P: apiKey.Group.VideoPrice480P,
|
||||
Price720P: apiKey.Group.VideoPrice720P,
|
||||
Price1080P: apiKey.Group.VideoPrice1080P,
|
||||
}
|
||||
}
|
||||
|
||||
func apiKeyHasConfiguredVideoPrice(apiKey *APIKey, resolution string) bool {
|
||||
return apiKey != nil && apiKey.Group != nil && apiKey.Group.GetVideoPrice(resolution) != nil
|
||||
}
|
||||
@@ -201,6 +201,13 @@ func TestParseGrokMediaRequestBuildsMultipartModerationBody(t *testing.T) {
|
||||
require.True(t, strings.HasPrefix(gjson.GetBytes(moderationBody, "images.0.image_url").String(), "data:image/"))
|
||||
}
|
||||
|
||||
func TestParseGrokMediaVideoRequestResolution(t *testing.T) {
|
||||
info := ParseGrokMediaRequest("application/json", []byte(`{"model":"grok-imagine-video","prompt":"waves","resolution":"720p"}`))
|
||||
|
||||
require.Equal(t, "grok-imagine-video", info.Model)
|
||||
require.Equal(t, "720p", info.Resolution)
|
||||
}
|
||||
|
||||
func TestNormalizeGrokMediaModelForEndpoint(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -330,7 +337,7 @@ func TestForwardGrokMediaVideoGenerationReturnsUsageAndResponseID(t *testing.T)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(recorder)
|
||||
body := []byte(`{"model":"grok-imagine-video-1.5","prompt":"waves"}`)
|
||||
body := []byte(`{"model":"grok-imagine-video-1.5","prompt":"waves","resolution":"720p"}`)
|
||||
c.Request = httptest.NewRequest(http.MethodPost, "/v1/videos/generations", bytes.NewReader(body))
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
@@ -358,12 +365,15 @@ func TestForwardGrokMediaVideoGenerationReturnsUsageAndResponseID(t *testing.T)
|
||||
result, err := svc.ForwardGrokMedia(context.Background(), c, account, GrokMediaEndpointVideosGenerations, "", body, "application/json")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "https://xai.test/v1/videos/generations", upstream.lastReq.URL.String())
|
||||
require.JSONEq(t, `{"model":"grok-imagine-video","prompt":"waves"}`, string(upstream.lastBody))
|
||||
require.JSONEq(t, `{"model":"grok-imagine-video","prompt":"waves","resolution":"720p"}`, string(upstream.lastBody))
|
||||
require.Equal(t, "video-request-123", result.ResponseID)
|
||||
require.Equal(t, "grok-imagine-video", result.BillingModel)
|
||||
require.Equal(t, 3, result.Usage.InputTokens)
|
||||
require.Equal(t, 4, result.Usage.OutputTokens)
|
||||
require.Equal(t, 1, result.ImageCount)
|
||||
require.Empty(t, result.ImageSize)
|
||||
require.Equal(t, 1, result.VideoCount)
|
||||
require.Equal(t, VideoBillingResolution720P, result.VideoResolution)
|
||||
}
|
||||
|
||||
func TestForwardGrokMediaVideoGenerationPreservesImageToVideoModel(t *testing.T) {
|
||||
|
||||
@@ -1803,8 +1803,9 @@ func TestOpenAIGatewayServiceRecordUsage_ImageIndependentMultiplierUsesImageRate
|
||||
require.Equal(t, string(BillingModeImage), *usageRepo.lastLog.BillingMode)
|
||||
}
|
||||
|
||||
func TestGrokVideoMediaBillingUsesImageRateMultiplier(t *testing.T) {
|
||||
mediaPrice2K := 0.4
|
||||
func TestGrokVideoBillingUsesSeparateVideoRateMultiplier(t *testing.T) {
|
||||
imagePrice2K := 0.4
|
||||
videoPrice480P := 0.08
|
||||
groupID := int64(126)
|
||||
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
@@ -1812,14 +1813,14 @@ func TestGrokVideoMediaBillingUsesImageRateMultiplier(t *testing.T) {
|
||||
|
||||
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
|
||||
Result: &OpenAIForwardResult{
|
||||
RequestID: "video-request-123",
|
||||
ResponseID: "video-request-123",
|
||||
Model: "grok-imagine-video-1.5",
|
||||
BillingModel: "grok-imagine-video-1.5",
|
||||
// The usage schema has no separate video count; video generation is billed as one media unit.
|
||||
ImageCount: 1,
|
||||
ImageSize: ImageBillingSize2K,
|
||||
Duration: time.Second,
|
||||
RequestID: "video-request-123",
|
||||
ResponseID: "video-request-123",
|
||||
Model: "grok-imagine-video-1.5",
|
||||
BillingModel: "grok-imagine-video-1.5",
|
||||
ImageCount: 1,
|
||||
VideoCount: 1,
|
||||
VideoResolution: VideoBillingResolution480P,
|
||||
Duration: time.Second,
|
||||
},
|
||||
APIKey: &APIKey{
|
||||
ID: 10126,
|
||||
@@ -1830,7 +1831,10 @@ func TestGrokVideoMediaBillingUsesImageRateMultiplier(t *testing.T) {
|
||||
RateMultiplier: 0.15,
|
||||
ImageRateIndependent: true,
|
||||
ImageRateMultiplier: 0.5,
|
||||
ImagePrice2K: &mediaPrice2K,
|
||||
ImagePrice2K: &imagePrice2K,
|
||||
VideoRateIndependent: true,
|
||||
VideoRateMultiplier: 0.25,
|
||||
VideoPrice480P: &videoPrice480P,
|
||||
},
|
||||
},
|
||||
User: &User{ID: 20126},
|
||||
@@ -1841,14 +1845,236 @@ func TestGrokVideoMediaBillingUsesImageRateMultiplier(t *testing.T) {
|
||||
require.NotNil(t, usageRepo.lastLog)
|
||||
require.Equal(t, "grok-imagine-video-1.5", usageRepo.lastLog.Model)
|
||||
require.Equal(t, 1, usageRepo.lastLog.ImageCount)
|
||||
require.Nil(t, usageRepo.lastLog.ImageSize)
|
||||
require.InDelta(t, 0.08, usageRepo.lastLog.TotalCost, 1e-12)
|
||||
require.InDelta(t, 0.02, usageRepo.lastLog.ActualCost, 1e-12)
|
||||
require.InDelta(t, 0.25, usageRepo.lastLog.RateMultiplier, 1e-12)
|
||||
require.NotNil(t, usageRepo.lastLog.BillingMode)
|
||||
require.Equal(t, string(BillingModeVideo), *usageRepo.lastLog.BillingMode)
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayServiceRecordUsage_GrokVideoUsesDefaultRateCard(t *testing.T) {
|
||||
groupID := int64(1261)
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
svc := newOpenAIRecordUsageServiceForTest(usageRepo, &openAIRecordUsageUserRepoStub{}, &openAIRecordUsageSubRepoStub{}, nil)
|
||||
|
||||
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
|
||||
Result: &OpenAIForwardResult{
|
||||
RequestID: "video-default-rate-card",
|
||||
ResponseID: "video-default-rate-card",
|
||||
Model: "grok-imagine-video-1.5",
|
||||
BillingModel: "grok-imagine-video-1.5",
|
||||
ImageCount: 1,
|
||||
VideoCount: 1,
|
||||
VideoResolution: VideoBillingResolution720P,
|
||||
Duration: time.Second,
|
||||
},
|
||||
APIKey: &APIKey{
|
||||
ID: 101261,
|
||||
GroupID: i64p(groupID),
|
||||
Group: &Group{
|
||||
ID: groupID,
|
||||
Platform: PlatformGrok,
|
||||
RateMultiplier: 1,
|
||||
},
|
||||
},
|
||||
User: &User{ID: 201261},
|
||||
Account: &Account{ID: 301261, Platform: PlatformGrok},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, usageRepo.lastLog)
|
||||
require.Nil(t, usageRepo.lastLog.ImageSize)
|
||||
require.InDelta(t, 0.14, usageRepo.lastLog.TotalCost, 1e-12)
|
||||
require.InDelta(t, 0.14, usageRepo.lastLog.ActualCost, 1e-12)
|
||||
require.Equal(t, 1, usageRepo.lastLog.ImageCount)
|
||||
require.NotNil(t, usageRepo.lastLog.BillingMode)
|
||||
require.Equal(t, string(BillingModeVideo), *usageRepo.lastLog.BillingMode)
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayServiceRecordUsage_GroupImagePriceOverridesChannelImagePrice(t *testing.T) {
|
||||
groupID := int64(127)
|
||||
channelPrice := 0.201
|
||||
groupImagePrice2K := 0.021
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
svc := newOpenAIRecordUsageServiceForTest(usageRepo, &openAIRecordUsageUserRepoStub{}, &openAIRecordUsageSubRepoStub{}, nil)
|
||||
svc.resolver = newOpenAIImageChannelPricingResolverForTest(t, groupID, "grok-imagine-image-quality", channelPrice)
|
||||
|
||||
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
|
||||
Result: &OpenAIForwardResult{
|
||||
RequestID: "resp_grok_image_group_price",
|
||||
Model: "grok-imagine-image-quality",
|
||||
BillingModel: "grok-imagine-image-quality",
|
||||
ImageCount: 1,
|
||||
ImageSize: ImageBillingSize2K,
|
||||
Duration: time.Second,
|
||||
},
|
||||
APIKey: &APIKey{
|
||||
ID: 10127,
|
||||
GroupID: i64p(groupID),
|
||||
Group: &Group{
|
||||
ID: groupID,
|
||||
Platform: PlatformGrok,
|
||||
RateMultiplier: 1,
|
||||
ImageRateIndependent: true,
|
||||
ImageRateMultiplier: 1,
|
||||
ImagePrice2K: &groupImagePrice2K,
|
||||
},
|
||||
},
|
||||
User: &User{ID: 20127},
|
||||
Account: &Account{ID: 30127, Platform: PlatformGrok},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, usageRepo.lastLog)
|
||||
require.Equal(t, 1, usageRepo.lastLog.ImageCount)
|
||||
require.Equal(t, ImageBillingSize2K, *usageRepo.lastLog.ImageSize)
|
||||
require.InDelta(t, 0.4, usageRepo.lastLog.TotalCost, 1e-12)
|
||||
require.InDelta(t, 0.2, usageRepo.lastLog.ActualCost, 1e-12)
|
||||
require.InDelta(t, 0.5, usageRepo.lastLog.RateMultiplier, 1e-12)
|
||||
require.InDelta(t, 0.021, usageRepo.lastLog.TotalCost, 1e-12)
|
||||
require.InDelta(t, 0.021, usageRepo.lastLog.ActualCost, 1e-12)
|
||||
require.NotNil(t, usageRepo.lastLog.BillingMode)
|
||||
require.Equal(t, string(BillingModeImage), *usageRepo.lastLog.BillingMode)
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayServiceRecordUsage_GroupVideoPriceOverridesChannelImagePrice(t *testing.T) {
|
||||
groupID := int64(128)
|
||||
channelPrice := 0.201
|
||||
groupVideoPrice720P := 0.037
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
svc := newOpenAIRecordUsageServiceForTest(usageRepo, &openAIRecordUsageUserRepoStub{}, &openAIRecordUsageSubRepoStub{}, nil)
|
||||
svc.resolver = newOpenAIImageChannelPricingResolverForTest(t, groupID, "grok-imagine-video", channelPrice)
|
||||
|
||||
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
|
||||
Result: &OpenAIForwardResult{
|
||||
RequestID: "resp_grok_video_group_price",
|
||||
Model: "grok-imagine-video",
|
||||
BillingModel: "grok-imagine-video",
|
||||
ImageCount: 1,
|
||||
VideoCount: 1,
|
||||
VideoResolution: VideoBillingResolution720P,
|
||||
Duration: time.Second,
|
||||
},
|
||||
APIKey: &APIKey{
|
||||
ID: 10128,
|
||||
GroupID: i64p(groupID),
|
||||
Group: &Group{
|
||||
ID: groupID,
|
||||
Platform: PlatformGrok,
|
||||
RateMultiplier: 1,
|
||||
VideoRateIndependent: true,
|
||||
VideoRateMultiplier: 1,
|
||||
VideoPrice720P: &groupVideoPrice720P,
|
||||
},
|
||||
},
|
||||
User: &User{ID: 20128},
|
||||
Account: &Account{ID: 30128, Platform: PlatformGrok},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, usageRepo.lastLog)
|
||||
require.Equal(t, 1, usageRepo.lastLog.ImageCount)
|
||||
require.Nil(t, usageRepo.lastLog.ImageSize)
|
||||
require.InDelta(t, 0.037, usageRepo.lastLog.TotalCost, 1e-12)
|
||||
require.InDelta(t, 0.037, usageRepo.lastLog.ActualCost, 1e-12)
|
||||
require.NotNil(t, usageRepo.lastLog.BillingMode)
|
||||
require.Equal(t, string(BillingModeVideo), *usageRepo.lastLog.BillingMode)
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayServiceRecordUsage_HydratesGroupImagePriceWhenAuthSnapshotOmitsIt(t *testing.T) {
|
||||
groupID := int64(130)
|
||||
groupImagePrice2K := 0.021
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
svc := newOpenAIRecordUsageServiceForTest(usageRepo, &openAIRecordUsageUserRepoStub{}, &openAIRecordUsageSubRepoStub{}, nil)
|
||||
channelService := &ChannelService{groupRepo: &openAIMediaPriceGroupRepoStub{group: &Group{
|
||||
ID: groupID,
|
||||
Platform: PlatformGrok,
|
||||
RateMultiplier: 1,
|
||||
ImagePrice2K: &groupImagePrice2K,
|
||||
}}}
|
||||
channelCache := newEmptyChannelCache()
|
||||
channelCache.loadedAt = time.Now()
|
||||
channelService.cache.Store(channelCache)
|
||||
svc.channelService = channelService
|
||||
refreshed := svc.apiKeyWithFreshGroupMediaPricing(context.Background(), &APIKey{GroupID: i64p(groupID), Group: &Group{ID: groupID}})
|
||||
require.NotNil(t, refreshed.Group.ImagePrice2K)
|
||||
|
||||
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
|
||||
Result: &OpenAIForwardResult{
|
||||
RequestID: "resp_grok_image_hydrated_price",
|
||||
Model: "grok-imagine-image-quality",
|
||||
BillingModel: "grok-imagine-image-quality",
|
||||
ImageCount: 1,
|
||||
ImageSize: ImageBillingSize2K,
|
||||
Duration: time.Second,
|
||||
},
|
||||
APIKey: &APIKey{
|
||||
ID: 10130,
|
||||
GroupID: i64p(groupID),
|
||||
Group: &Group{
|
||||
ID: groupID,
|
||||
Platform: PlatformGrok,
|
||||
RateMultiplier: 1,
|
||||
},
|
||||
},
|
||||
User: &User{ID: 20130},
|
||||
Account: &Account{ID: 30130, Platform: PlatformGrok},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, usageRepo.lastLog)
|
||||
require.InDelta(t, 0.021, usageRepo.lastLog.TotalCost, 1e-12)
|
||||
require.InDelta(t, 0.021, usageRepo.lastLog.ActualCost, 1e-12)
|
||||
require.Equal(t, string(BillingModeImage), *usageRepo.lastLog.BillingMode)
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayServiceRecordUsage_HydratesGroupVideoPriceWhenAuthSnapshotOmitsIt(t *testing.T) {
|
||||
groupID := int64(131)
|
||||
groupVideoPrice720P := 0.037
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
svc := newOpenAIRecordUsageServiceForTest(usageRepo, &openAIRecordUsageUserRepoStub{}, &openAIRecordUsageSubRepoStub{}, nil)
|
||||
channelService := &ChannelService{groupRepo: &openAIMediaPriceGroupRepoStub{group: &Group{
|
||||
ID: groupID,
|
||||
Platform: PlatformGrok,
|
||||
RateMultiplier: 1,
|
||||
VideoPrice720P: &groupVideoPrice720P,
|
||||
}}}
|
||||
channelCache := newEmptyChannelCache()
|
||||
channelCache.loadedAt = time.Now()
|
||||
channelService.cache.Store(channelCache)
|
||||
svc.channelService = channelService
|
||||
refreshed := svc.apiKeyWithFreshGroupMediaPricing(context.Background(), &APIKey{GroupID: i64p(groupID), Group: &Group{ID: groupID}})
|
||||
require.NotNil(t, refreshed.Group.VideoPrice720P)
|
||||
|
||||
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
|
||||
Result: &OpenAIForwardResult{
|
||||
RequestID: "resp_grok_video_hydrated_price",
|
||||
Model: "grok-imagine-video",
|
||||
BillingModel: "grok-imagine-video",
|
||||
ImageCount: 1,
|
||||
VideoCount: 1,
|
||||
VideoResolution: VideoBillingResolution720P,
|
||||
Duration: time.Second,
|
||||
},
|
||||
APIKey: &APIKey{
|
||||
ID: 10131,
|
||||
GroupID: i64p(groupID),
|
||||
Group: &Group{
|
||||
ID: groupID,
|
||||
Platform: PlatformGrok,
|
||||
RateMultiplier: 1,
|
||||
},
|
||||
},
|
||||
User: &User{ID: 20131},
|
||||
Account: &Account{ID: 30131, Platform: PlatformGrok},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, usageRepo.lastLog)
|
||||
require.Nil(t, usageRepo.lastLog.ImageSize)
|
||||
require.InDelta(t, 0.037, usageRepo.lastLog.TotalCost, 1e-12)
|
||||
require.InDelta(t, 0.037, usageRepo.lastLog.ActualCost, 1e-12)
|
||||
require.Equal(t, string(BillingModeVideo), *usageRepo.lastLog.BillingMode)
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayServiceRecordUsage_ChannelImageBillingUsesImageCountAndSharedMultiplier(t *testing.T) {
|
||||
groupID := int64(123)
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
@@ -1960,6 +2186,19 @@ func newOpenAITokenImageChannelPricingResolverForTest(t *testing.T, groupID int6
|
||||
return NewModelPricingResolver(cs, NewBillingService(&config.Config{}, nil))
|
||||
}
|
||||
|
||||
type openAIMediaPriceGroupRepoStub struct {
|
||||
GroupRepository
|
||||
group *Group
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *openAIMediaPriceGroupRepoStub) GetByIDLite(context.Context, int64) (*Group, error) {
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
return s.group, nil
|
||||
}
|
||||
|
||||
func TestGatewayServiceCalculateRecordUsageCost_ChannelImageBillingUsesImageCount(t *testing.T) {
|
||||
groupID := int64(126)
|
||||
billingService := NewBillingService(&config.Config{}, nil)
|
||||
@@ -2023,6 +2262,38 @@ func TestGatewayServiceCalculateRecordUsageCost_ChannelImageBillingUsesSizeTier(
|
||||
require.InDelta(t, 0.80, cost.ActualCost, 1e-12)
|
||||
}
|
||||
|
||||
func TestGatewayServiceCalculateRecordUsageCost_GroupImagePriceOverridesChannelImagePrice(t *testing.T) {
|
||||
groupID := int64(129)
|
||||
channelPrice := 0.25
|
||||
groupImagePrice2K := 0.021
|
||||
|
||||
svc := &GatewayService{
|
||||
billingService: NewBillingService(&config.Config{}, nil),
|
||||
resolver: newOpenAIImageChannelPricingResolverForTest(t, groupID, "gemini-image", channelPrice),
|
||||
}
|
||||
|
||||
cost := svc.calculateRecordUsageCost(
|
||||
context.Background(),
|
||||
&ForwardResult{Model: "gemini-image", ImageCount: 2, ImageSize: ImageBillingSize2K},
|
||||
&APIKey{
|
||||
GroupID: i64p(groupID),
|
||||
Group: &Group{
|
||||
ID: groupID,
|
||||
ImagePrice2K: &groupImagePrice2K,
|
||||
},
|
||||
},
|
||||
"gemini-image",
|
||||
1.0,
|
||||
1.0,
|
||||
nil,
|
||||
)
|
||||
|
||||
require.NotNil(t, cost)
|
||||
require.Equal(t, string(BillingModeImage), cost.BillingMode)
|
||||
require.InDelta(t, 0.042, cost.TotalCost, 1e-12)
|
||||
require.InDelta(t, 0.042, cost.ActualCost, 1e-12)
|
||||
}
|
||||
|
||||
func TestRecordUsageMarksCyberRequestType(t *testing.T) {
|
||||
logStub := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
userStub := &openAIRecordUsageUserRepoStub{}
|
||||
|
||||
@@ -242,6 +242,8 @@ type OpenAIForwardResult struct {
|
||||
ImageOutputSizes []string
|
||||
ImageSizeSource string
|
||||
ImageSizeBreakdown map[string]int
|
||||
VideoCount int
|
||||
VideoResolution string
|
||||
|
||||
wsReplayInput []json.RawMessage
|
||||
wsReplayInputExists bool
|
||||
|
||||
@@ -115,7 +115,9 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
|
||||
user := input.User
|
||||
account := input.Account
|
||||
subscription := input.Subscription
|
||||
ApplyOpenAIImageBillingResolution(result)
|
||||
if !isGrokVideoUsageResult(result, nil) {
|
||||
ApplyOpenAIImageBillingResolution(result)
|
||||
}
|
||||
|
||||
// 计算实际的新输入token(减去缓存读取的token)
|
||||
// 因为 input_tokens 包含了 cache_read_tokens,而缓存读取的token不应按输入价格计费
|
||||
@@ -148,7 +150,9 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
|
||||
}
|
||||
// token 倍率叠加高峰因子(token 计费含图片 token,图片按次倍率不受影响)。高峰因子按请求时刻现算,
|
||||
// 不并入上面的 Resolve,以免污染 user:group 倍率缓存。
|
||||
multiplier, imageMultiplier := computePeakAwareMultipliers(apiKey, multiplier, timezone.Now())
|
||||
baseMultiplier := multiplier
|
||||
multiplier, imageMultiplier := computePeakAwareMultipliers(apiKey, baseMultiplier, timezone.Now())
|
||||
videoMultiplier := resolveVideoRateMultiplier(apiKey, baseMultiplier)
|
||||
|
||||
var cost *CostBreakdown
|
||||
var err error
|
||||
@@ -174,7 +178,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
|
||||
if result.ServiceTier != nil {
|
||||
serviceTier = strings.TrimSpace(*result.ServiceTier)
|
||||
}
|
||||
cost, err = s.calculateOpenAIRecordUsageCost(ctx, result, apiKey, billingModels, multiplier, imageMultiplier, tokens, serviceTier)
|
||||
cost, err = s.calculateOpenAIRecordUsageCost(ctx, result, apiKey, billingModels, multiplier, imageMultiplier, videoMultiplier, tokens, serviceTier)
|
||||
if err != nil {
|
||||
if !isUsagePricingUnavailableError(err) {
|
||||
return err
|
||||
@@ -238,6 +242,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
|
||||
ImageSizeSource: optionalTrimmedStringPtr(result.ImageSizeSource),
|
||||
ImageSizeBreakdown: result.ImageSizeBreakdown,
|
||||
}
|
||||
isVideoUsage := isGrokVideoUsageResult(result, billingModels)
|
||||
if cost != nil {
|
||||
usageLog.InputCost = cost.InputCost
|
||||
usageLog.OutputCost = cost.OutputCost
|
||||
@@ -247,7 +252,9 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
|
||||
usageLog.TotalCost = cost.TotalCost
|
||||
usageLog.ActualCost = cost.ActualCost
|
||||
}
|
||||
if result.ImageCount > 0 && (cost == nil || cost.BillingMode != string(BillingModeToken)) {
|
||||
if isVideoUsage && (cost == nil || cost.BillingMode != string(BillingModeToken)) {
|
||||
usageLog.RateMultiplier = videoMultiplier
|
||||
} else if result.ImageCount > 0 && (cost == nil || cost.BillingMode != string(BillingModeToken)) {
|
||||
usageLog.RateMultiplier = imageMultiplier
|
||||
} else {
|
||||
usageLog.RateMultiplier = multiplier
|
||||
@@ -269,6 +276,9 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
|
||||
if cost != nil && cost.BillingMode != "" {
|
||||
billingMode := cost.BillingMode
|
||||
usageLog.BillingMode = &billingMode
|
||||
} else if isVideoUsage {
|
||||
billingMode := string(BillingModeVideo)
|
||||
usageLog.BillingMode = &billingMode
|
||||
} else if result.ImageCount > 0 {
|
||||
billingMode := string(BillingModeImage)
|
||||
usageLog.BillingMode = &billingMode
|
||||
@@ -346,10 +356,16 @@ func (s *OpenAIGatewayService) calculateOpenAIRecordUsageCost(
|
||||
billingModels []string,
|
||||
multiplier float64,
|
||||
imageMultiplier float64,
|
||||
videoMultiplier float64,
|
||||
tokens UsageTokens,
|
||||
serviceTier string,
|
||||
) (*CostBreakdown, error) {
|
||||
billingModel := firstUsageBillingModel(billingModels)
|
||||
if isGrokVideoUsageResult(result, billingModels) {
|
||||
if resolved := s.resolveOpenAIChannelPricing(ctx, billingModel, apiKey); resolved == nil || resolved.Mode != BillingModeToken {
|
||||
return s.calculateOpenAIVideoCost(ctx, billingModel, apiKey, result, videoMultiplier), nil
|
||||
}
|
||||
}
|
||||
if result != nil && result.ImageCount > 0 {
|
||||
// 渠道定价为 token 计费时走 token 路径,否则走图片计费
|
||||
if resolved := s.resolveOpenAIChannelPricing(ctx, billingModel, apiKey); resolved == nil || resolved.Mode != BillingModeToken {
|
||||
@@ -377,6 +393,24 @@ func (s *OpenAIGatewayService) calculateOpenAIRecordUsageCost(
|
||||
return nil, fmt.Errorf("calculate OpenAI usage cost failed for billing models %s: %w", strings.Join(billingModels, ","), lastErr)
|
||||
}
|
||||
|
||||
func isGrokVideoBillingModel(model string) bool {
|
||||
return strings.HasPrefix(strings.ToLower(strings.TrimSpace(model)), "grok-imagine-video")
|
||||
}
|
||||
|
||||
func isGrokVideoUsageResult(result *OpenAIForwardResult, billingModels []string) bool {
|
||||
if result == nil || result.VideoCount <= 0 {
|
||||
return false
|
||||
}
|
||||
candidates := append([]string{}, billingModels...)
|
||||
candidates = append(candidates, result.BillingModel, result.Model, result.UpstreamModel)
|
||||
for _, candidate := range candidates {
|
||||
if isGrokVideoBillingModel(candidate) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isUsagePricingUnavailableError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
@@ -420,6 +454,17 @@ func (s *OpenAIGatewayService) calculateOpenAIImageCost(
|
||||
multiplier float64,
|
||||
) *CostBreakdown {
|
||||
sizeTier := NormalizeImageBillingTierOrDefault(result.ImageSize)
|
||||
groupConfig := imagePriceConfigFromAPIKey(apiKey)
|
||||
if apiKeyHasConfiguredImagePrice(apiKey, sizeTier) {
|
||||
return s.billingService.CalculateImageCost(billingModel, sizeTier, result.ImageCount, groupConfig, multiplier)
|
||||
}
|
||||
if refreshed := s.apiKeyWithFreshGroupMediaPricing(ctx, apiKey); refreshed != apiKey {
|
||||
apiKey = refreshed
|
||||
groupConfig = imagePriceConfigFromAPIKey(apiKey)
|
||||
if apiKeyHasConfiguredImagePrice(apiKey, sizeTier) {
|
||||
return s.billingService.CalculateImageCost(billingModel, sizeTier, result.ImageCount, groupConfig, multiplier)
|
||||
}
|
||||
}
|
||||
if resolved := s.resolveOpenAIChannelPricing(ctx, billingModel, apiKey); resolved != nil &&
|
||||
(resolved.Mode == BillingModePerRequest || resolved.Mode == BillingModeImage) {
|
||||
gid := apiKey.Group.ID
|
||||
@@ -439,15 +484,69 @@ func (s *OpenAIGatewayService) calculateOpenAIImageCost(
|
||||
logger.LegacyPrintf("service.openai_gateway", "Calculate image channel cost failed: %v", err)
|
||||
}
|
||||
|
||||
var groupConfig *ImagePriceConfig
|
||||
if apiKey != nil && apiKey.Group != nil {
|
||||
groupConfig = &ImagePriceConfig{
|
||||
Price1K: apiKey.Group.ImagePrice1K,
|
||||
Price2K: apiKey.Group.ImagePrice2K,
|
||||
Price4K: apiKey.Group.ImagePrice4K,
|
||||
return s.billingService.CalculateImageCost(billingModel, sizeTier, result.ImageCount, groupConfig, multiplier)
|
||||
}
|
||||
|
||||
func (s *OpenAIGatewayService) calculateOpenAIVideoCost(
|
||||
ctx context.Context,
|
||||
billingModel string,
|
||||
apiKey *APIKey,
|
||||
result *OpenAIForwardResult,
|
||||
multiplier float64,
|
||||
) *CostBreakdown {
|
||||
videoCount := result.VideoCount
|
||||
if videoCount <= 0 {
|
||||
videoCount = 1
|
||||
}
|
||||
resolution := NormalizeVideoBillingResolutionOrDefault(result.VideoResolution)
|
||||
groupConfig := videoPriceConfigFromAPIKey(apiKey)
|
||||
if apiKeyHasConfiguredVideoPrice(apiKey, resolution) {
|
||||
return s.billingService.CalculateVideoCost(billingModel, resolution, videoCount, groupConfig, multiplier)
|
||||
}
|
||||
if refreshed := s.apiKeyWithFreshGroupMediaPricing(ctx, apiKey); refreshed != apiKey {
|
||||
apiKey = refreshed
|
||||
groupConfig = videoPriceConfigFromAPIKey(apiKey)
|
||||
if apiKeyHasConfiguredVideoPrice(apiKey, resolution) {
|
||||
return s.billingService.CalculateVideoCost(billingModel, resolution, videoCount, groupConfig, multiplier)
|
||||
}
|
||||
}
|
||||
return s.billingService.CalculateImageCost(billingModel, sizeTier, result.ImageCount, groupConfig, multiplier)
|
||||
if resolved := s.resolveOpenAIChannelPricing(ctx, billingModel, apiKey); resolved != nil &&
|
||||
(resolved.Mode == BillingModePerRequest || resolved.Mode == BillingModeImage) {
|
||||
gid := apiKey.Group.ID
|
||||
cost, err := s.billingService.CalculateCostUnified(CostInput{
|
||||
Ctx: ctx,
|
||||
Model: billingModel,
|
||||
GroupID: &gid,
|
||||
RequestCount: videoCount,
|
||||
SizeTier: resolution,
|
||||
RateMultiplier: multiplier,
|
||||
Resolver: s.resolver,
|
||||
Resolved: resolved,
|
||||
})
|
||||
if err == nil {
|
||||
cost.BillingMode = string(BillingModeVideo)
|
||||
return cost
|
||||
}
|
||||
logger.LegacyPrintf("service.openai_gateway", "Calculate video channel cost failed: %v", err)
|
||||
}
|
||||
|
||||
return s.billingService.CalculateVideoCost(billingModel, resolution, videoCount, groupConfig, multiplier)
|
||||
}
|
||||
|
||||
func (s *OpenAIGatewayService) apiKeyWithFreshGroupMediaPricing(ctx context.Context, apiKey *APIKey) *APIKey {
|
||||
if apiKey == nil || apiKey.GroupID == nil || *apiKey.GroupID <= 0 {
|
||||
return apiKey
|
||||
}
|
||||
if s == nil || s.channelService == nil || s.channelService.groupRepo == nil {
|
||||
return apiKey
|
||||
}
|
||||
group, err := s.channelService.groupRepo.GetByIDLite(ctx, *apiKey.GroupID)
|
||||
if err != nil || group == nil {
|
||||
return apiKey
|
||||
}
|
||||
clone := *apiKey
|
||||
clone.Group = group
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (s *OpenAIGatewayService) resolveOpenAIChannelPricing(ctx context.Context, billingModel string, apiKey *APIKey) *ResolvedPricing {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package service
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
VideoBillingResolution480P = "480p"
|
||||
VideoBillingResolution720P = "720p"
|
||||
VideoBillingResolution1080P = "1080p"
|
||||
)
|
||||
|
||||
func NormalizeVideoBillingResolutionOrDefault(resolution string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(resolution)) {
|
||||
case "480", "480p", "sd":
|
||||
return VideoBillingResolution480P
|
||||
case "720", "720p", "hd":
|
||||
return VideoBillingResolution720P
|
||||
case "1080", "1080p", "full_hd", "full-hd", "fhd":
|
||||
return VideoBillingResolution1080P
|
||||
default:
|
||||
return VideoBillingResolution480P
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
-- Add independent group pricing controls for Grok video generation.
|
||||
-- Video prices intentionally do not backfill from image prices: image and video
|
||||
-- generation must be priced separately.
|
||||
|
||||
ALTER TABLE groups
|
||||
ADD COLUMN IF NOT EXISTS video_rate_independent BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS video_rate_multiplier DECIMAL(10,4) NOT NULL DEFAULT 1.0,
|
||||
ADD COLUMN IF NOT EXISTS video_price_480p DECIMAL(20,8),
|
||||
ADD COLUMN IF NOT EXISTS video_price_720p DECIMAL(20,8),
|
||||
ADD COLUMN IF NOT EXISTS video_price_1080p DECIMAL(20,8);
|
||||
|
||||
COMMENT ON COLUMN groups.video_rate_independent IS '视频生成是否使用独立倍率;false 表示共享分组有效倍率';
|
||||
COMMENT ON COLUMN groups.video_rate_multiplier IS '视频生成独立倍率,仅 video_rate_independent=true 时生效';
|
||||
COMMENT ON COLUMN groups.video_price_480p IS '480p 视频生成单价 (USD),Grok 平台使用';
|
||||
COMMENT ON COLUMN groups.video_price_720p IS '720p 视频生成单价 (USD),Grok 平台使用';
|
||||
COMMENT ON COLUMN groups.video_price_1080p IS '1080p 视频生成单价 (USD),Grok 平台使用';
|
||||
@@ -0,0 +1,17 @@
|
||||
-- Grok video generation stores billing_mode='video' and keeps image_count=1
|
||||
-- only as a legacy media-unit counter. It must not be forced to carry an
|
||||
-- image_size, because video pricing uses video_resolution/request metadata.
|
||||
|
||||
ALTER TABLE usage_logs
|
||||
DROP CONSTRAINT IF EXISTS usage_logs_image_billing_size_check;
|
||||
|
||||
ALTER TABLE usage_logs
|
||||
ADD CONSTRAINT usage_logs_image_billing_size_check
|
||||
CHECK (
|
||||
image_count <= 0
|
||||
OR billing_mode = 'video'
|
||||
OR (
|
||||
image_size IS NOT NULL
|
||||
AND image_size IN ('1K', '2K', '4K', 'mixed')
|
||||
)
|
||||
) NOT VALID;
|
||||
@@ -294,7 +294,8 @@ const billingModeOptions = ref<SelectOption[]>([
|
||||
{ value: null, label: t('admin.usage.allBillingModes') },
|
||||
{ value: 'token', label: t('admin.usage.billingModeToken') },
|
||||
{ value: 'per_request', label: t('admin.usage.billingModePerRequest') },
|
||||
{ value: 'image', label: t('admin.usage.billingModeImage') }
|
||||
{ value: 'image', label: t('admin.usage.billingModeImage') },
|
||||
{ value: 'video', label: t('admin.usage.billingModeVideo') }
|
||||
])
|
||||
|
||||
const emitChange = () => emit('change')
|
||||
|
||||
@@ -26,6 +26,7 @@ export default {
|
||||
billingModeToken: 'Per Token',
|
||||
billingModePerRequest: 'Per Request',
|
||||
billingModeImage: 'Per Image',
|
||||
billingModeVideo: 'Per Video',
|
||||
inputPrice: 'Input',
|
||||
outputPrice: 'Output',
|
||||
cacheWritePrice: 'Cache Write',
|
||||
|
||||
@@ -841,6 +841,28 @@ export default {
|
||||
finalPricePreview: 'Final per-image price preview',
|
||||
notConfigured: 'Not configured'
|
||||
},
|
||||
mediaPricing: {
|
||||
title: 'Image / Video Generation Pricing',
|
||||
description:
|
||||
'Configure Grok image and video generation access plus base media prices. Leave empty to use default prices.',
|
||||
allowImageGeneration: 'Allow image and video generation for this group',
|
||||
independentMultiplier: 'Use independent media multiplier',
|
||||
imageMultiplier: 'Media multiplier',
|
||||
modeHint:
|
||||
'By default, Grok media billing uses media price × current effective group multiplier. Independent mode uses media price × media multiplier. One video generation is billed as one media unit.',
|
||||
finalPricePreview: 'Final per-media-unit price preview',
|
||||
notConfigured: 'Not configured'
|
||||
},
|
||||
videoPricing: {
|
||||
title: 'Video Generation Pricing',
|
||||
description: 'Configure Grok video generation base prices. Leave empty to use default video prices.',
|
||||
independentMultiplier: 'Use independent video multiplier',
|
||||
videoMultiplier: 'Video multiplier',
|
||||
modeHint:
|
||||
'By default, video billing uses video price × current effective group multiplier. Independent mode uses video price × video multiplier.',
|
||||
finalPricePreview: 'Final per-video price preview',
|
||||
notConfigured: 'Not configured'
|
||||
},
|
||||
peakRate: {
|
||||
enable: 'Enable peak rate multiplier',
|
||||
peakStart: 'Peak start',
|
||||
|
||||
@@ -477,6 +477,7 @@ export default {
|
||||
billingModeToken: 'Token',
|
||||
billingModePerRequest: 'Per Request',
|
||||
billingModeImage: 'Image',
|
||||
billingModeVideo: 'Video',
|
||||
allBillingModes: 'All Billing Modes',
|
||||
ipAddress: 'IP',
|
||||
clickToViewBalance: 'Click to view balance history',
|
||||
|
||||
@@ -478,6 +478,7 @@ export default {
|
||||
billingModeToken: 'Per Token',
|
||||
billingModePerRequest: 'Per Request',
|
||||
billingModeImage: 'Per Image',
|
||||
billingModeVideo: 'Per Video',
|
||||
inputPrice: 'Input',
|
||||
outputPrice: 'Output',
|
||||
cacheWritePrice: 'Cache Write',
|
||||
|
||||
@@ -26,6 +26,7 @@ export default {
|
||||
billingModeToken: '按 Token',
|
||||
billingModePerRequest: '按次',
|
||||
billingModeImage: '按图片',
|
||||
billingModeVideo: '按视频',
|
||||
inputPrice: '输入',
|
||||
outputPrice: '输出',
|
||||
cacheWritePrice: '缓存写入',
|
||||
|
||||
@@ -919,6 +919,27 @@ export default {
|
||||
finalPricePreview: '最终单张价格预览',
|
||||
notConfigured: '未配置'
|
||||
},
|
||||
mediaPricing: {
|
||||
title: '图片/视频生成计费',
|
||||
description: '配置 Grok 图片和视频生成能力及媒体基础单价,留空则使用默认价格',
|
||||
allowImageGeneration: '允许当前分组生图和视频生成',
|
||||
independentMultiplier: '媒体倍率独立',
|
||||
imageMultiplier: '媒体独立倍率',
|
||||
modeHint:
|
||||
'默认关闭独立倍率时,Grok 媒体费用 = 媒体价格 × 当前分组有效倍率;开启独立倍率后,Grok 媒体费用 = 媒体价格 × 媒体独立倍率。一次视频生成按 1 个媒体单位计费。',
|
||||
finalPricePreview: '最终单次媒体价格预览',
|
||||
notConfigured: '未配置'
|
||||
},
|
||||
videoPricing: {
|
||||
title: '视频生成计费',
|
||||
description: '配置 Grok 视频生成基础单价,留空则使用默认视频价格',
|
||||
independentMultiplier: '视频倍率独立',
|
||||
videoMultiplier: '视频独立倍率',
|
||||
modeHint:
|
||||
'默认关闭独立倍率时,视频费用 = 视频价格 × 当前分组有效倍率;开启独立倍率后,视频费用 = 视频价格 × 视频独立倍率。',
|
||||
finalPricePreview: '最终单次视频价格预览',
|
||||
notConfigured: '未配置'
|
||||
},
|
||||
peakRate: {
|
||||
enable: '启用高峰倍率',
|
||||
peakStart: '高峰开始',
|
||||
|
||||
@@ -538,6 +538,7 @@ export default {
|
||||
billingModeToken: '按量',
|
||||
billingModePerRequest: '按次',
|
||||
billingModeImage: '按次(图片)',
|
||||
billingModeVideo: '按次(视频)',
|
||||
allBillingModes: '全部计费模式',
|
||||
ipAddress: 'IP',
|
||||
clickToViewBalance: '点击查看充值记录',
|
||||
|
||||
@@ -483,6 +483,7 @@ export default {
|
||||
billingModeToken: '按 Token',
|
||||
billingModePerRequest: '按次',
|
||||
billingModeImage: '按图片',
|
||||
billingModeVideo: '按视频',
|
||||
inputPrice: '输入',
|
||||
outputPrice: '输出',
|
||||
cacheWritePrice: '缓存写入',
|
||||
|
||||
@@ -526,6 +526,11 @@ export interface Group {
|
||||
image_price_1k: number | null
|
||||
image_price_2k: number | null
|
||||
image_price_4k: number | null
|
||||
video_rate_independent: boolean
|
||||
video_rate_multiplier: number
|
||||
video_price_480p: number | null
|
||||
video_price_720p: number | null
|
||||
video_price_1080p: number | null
|
||||
// 高峰时段倍率配置
|
||||
peak_rate_enabled: boolean
|
||||
peak_start: string
|
||||
@@ -654,6 +659,11 @@ export interface CreateGroupRequest {
|
||||
image_price_1k?: number | null
|
||||
image_price_2k?: number | null
|
||||
image_price_4k?: number | null
|
||||
video_rate_independent?: boolean
|
||||
video_rate_multiplier?: number
|
||||
video_price_480p?: number | null
|
||||
video_price_720p?: number | null
|
||||
video_price_1080p?: number | null
|
||||
peak_rate_enabled?: boolean
|
||||
peak_start?: string
|
||||
peak_end?: string
|
||||
@@ -696,6 +706,11 @@ export interface UpdateGroupRequest {
|
||||
image_price_1k?: number | null
|
||||
image_price_2k?: number | null
|
||||
image_price_4k?: number | null
|
||||
video_rate_independent?: boolean
|
||||
video_rate_multiplier?: number
|
||||
video_price_480p?: number | null
|
||||
video_price_720p?: number | null
|
||||
video_price_1080p?: number | null
|
||||
peak_rate_enabled?: boolean
|
||||
peak_start?: string
|
||||
peak_end?: string
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
export const BILLING_MODE_TOKEN = 'token'
|
||||
export const BILLING_MODE_PER_REQUEST = 'per_request'
|
||||
export const BILLING_MODE_IMAGE = 'image'
|
||||
export const BILLING_MODE_VIDEO = 'video'
|
||||
|
||||
export function getBillingModeLabel(mode: string | null | undefined, t: (key: string) => string): string {
|
||||
switch (mode) {
|
||||
case BILLING_MODE_PER_REQUEST: return t('admin.usage.billingModePerRequest')
|
||||
case BILLING_MODE_IMAGE: return t('admin.usage.billingModeImage')
|
||||
case BILLING_MODE_VIDEO: return t('admin.usage.billingModeVideo')
|
||||
default: return t('admin.usage.billingModeToken')
|
||||
}
|
||||
}
|
||||
@@ -14,6 +16,7 @@ export function getBillingModeBadgeClass(mode: string | null | undefined): strin
|
||||
switch (mode) {
|
||||
case BILLING_MODE_PER_REQUEST: return 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300'
|
||||
case BILLING_MODE_IMAGE: return 'bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-300'
|
||||
case BILLING_MODE_VIDEO: return 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300'
|
||||
default: return 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300'
|
||||
}
|
||||
}
|
||||
@@ -25,7 +28,7 @@ interface ImageBillingRow {
|
||||
}
|
||||
|
||||
export function isImageUsage(row: Pick<ImageBillingRow, 'image_count' | 'billing_mode'> | null | undefined): boolean {
|
||||
return (row?.image_count ?? 0) > 0 && row?.billing_mode !== BILLING_MODE_TOKEN
|
||||
return (row?.image_count ?? 0) > 0 && row?.billing_mode !== BILLING_MODE_TOKEN && row?.billing_mode !== BILLING_MODE_VIDEO
|
||||
}
|
||||
|
||||
export function getDisplayBillingMode(row: Pick<ImageBillingRow, 'billing_mode' | 'image_count'> | null | undefined): string | null | undefined {
|
||||
|
||||
@@ -825,10 +825,10 @@
|
||||
<label
|
||||
class="block mb-2 font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ t("admin.groups.imagePricing.title") }}
|
||||
{{ t(imagePricingI18nKey(createForm.platform, "title")) }}
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
||||
{{ t("admin.groups.imagePricing.description") }}
|
||||
{{ t(imagePricingI18nKey(createForm.platform, "description")) }}
|
||||
</p>
|
||||
<div class="mb-4 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
@@ -837,7 +837,7 @@
|
||||
type="checkbox"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
{{ t("admin.groups.imagePricing.allowImageGeneration") }}
|
||||
{{ t(imagePricingI18nKey(createForm.platform, "allowImageGeneration")) }}
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
@@ -845,7 +845,7 @@
|
||||
type="checkbox"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
{{ t("admin.groups.imagePricing.independentMultiplier") }}
|
||||
{{ t(imagePricingI18nKey(createForm.platform, "independentMultiplier")) }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@@ -853,7 +853,7 @@
|
||||
class="mb-4"
|
||||
>
|
||||
<label class="input-label">{{
|
||||
t("admin.groups.imagePricing.imageMultiplier")
|
||||
t(imagePricingI18nKey(createForm.platform, "imageMultiplier"))
|
||||
}}</label>
|
||||
<input
|
||||
v-model.number="createForm.image_rate_multiplier"
|
||||
@@ -873,7 +873,7 @@
|
||||
step="0.001"
|
||||
min="0"
|
||||
class="input"
|
||||
placeholder="0.134"
|
||||
:placeholder="getImagePricePlaceholder(createForm.platform, 'image_price_1k')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -884,7 +884,7 @@
|
||||
step="0.001"
|
||||
min="0"
|
||||
class="input"
|
||||
placeholder="0.201"
|
||||
:placeholder="getImagePricePlaceholder(createForm.platform, 'image_price_2k')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -895,16 +895,16 @@
|
||||
step="0.001"
|
||||
min="0"
|
||||
class="input"
|
||||
placeholder="0.268"
|
||||
:placeholder="getImagePricePlaceholder(createForm.platform, 'image_price_4k')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.groups.imagePricing.modeHint") }}
|
||||
{{ t(imagePricingI18nKey(createForm.platform, "modeHint")) }}
|
||||
</p>
|
||||
<div class="mt-2 rounded-lg bg-gray-50 p-3 text-xs text-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
||||
<div class="mb-1 font-medium">
|
||||
{{ t("admin.groups.imagePricing.finalPricePreview") }}
|
||||
{{ t(imagePricingI18nKey(createForm.platform, "finalPricePreview")) }}
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div
|
||||
@@ -969,6 +969,98 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 视频生成计费配置(仅 Grok 平台) -->
|
||||
<div
|
||||
v-if="supportsVideoPricingPlatform(createForm.platform)"
|
||||
class="border-t pt-4"
|
||||
>
|
||||
<label
|
||||
class="block mb-2 font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ t(videoPricingI18nKey("title")) }}
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
||||
{{ t(videoPricingI18nKey("description")) }}
|
||||
</p>
|
||||
<div class="mb-4">
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
v-model="createForm.video_rate_independent"
|
||||
type="checkbox"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
{{ t(videoPricingI18nKey("independentMultiplier")) }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="createForm.video_rate_independent"
|
||||
class="mb-4"
|
||||
>
|
||||
<label class="input-label">{{
|
||||
t(videoPricingI18nKey("videoMultiplier"))
|
||||
}}</label>
|
||||
<input
|
||||
v-model.number="createForm.video_rate_multiplier"
|
||||
type="number"
|
||||
step="0.0001"
|
||||
min="0"
|
||||
class="input"
|
||||
placeholder="1"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="input-label">480p ($)</label>
|
||||
<input
|
||||
v-model.number="createForm.video_price_480p"
|
||||
type="number"
|
||||
step="0.001"
|
||||
min="0"
|
||||
class="input"
|
||||
:placeholder="getVideoPricePlaceholder(createForm.platform, 'video_price_480p')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">720p ($)</label>
|
||||
<input
|
||||
v-model.number="createForm.video_price_720p"
|
||||
type="number"
|
||||
step="0.001"
|
||||
min="0"
|
||||
class="input"
|
||||
:placeholder="getVideoPricePlaceholder(createForm.platform, 'video_price_720p')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">1080p ($)</label>
|
||||
<input
|
||||
v-model.number="createForm.video_price_1080p"
|
||||
type="number"
|
||||
step="0.001"
|
||||
min="0"
|
||||
class="input"
|
||||
:placeholder="getVideoPricePlaceholder(createForm.platform, 'video_price_1080p')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t(videoPricingI18nKey("modeHint")) }}
|
||||
</p>
|
||||
<div class="mt-2 rounded-lg bg-gray-50 p-3 text-xs text-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
||||
<div class="mb-1 font-medium">
|
||||
{{ t(videoPricingI18nKey("finalPricePreview")) }}
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div
|
||||
v-for="item in createVideoFinalPricePreview"
|
||||
:key="item.label"
|
||||
>
|
||||
{{ item.label }}: {{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 高峰时段倍率配置(仅订阅类型分组) -->
|
||||
<div v-if="createForm.subscription_type === 'subscription'" class="border-t pt-4">
|
||||
<div class="mb-4 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
@@ -2212,10 +2304,10 @@
|
||||
<label
|
||||
class="block mb-2 font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ t("admin.groups.imagePricing.title") }}
|
||||
{{ t(imagePricingI18nKey(editForm.platform, "title")) }}
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
||||
{{ t("admin.groups.imagePricing.description") }}
|
||||
{{ t(imagePricingI18nKey(editForm.platform, "description")) }}
|
||||
</p>
|
||||
<div class="mb-4 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
@@ -2224,7 +2316,7 @@
|
||||
type="checkbox"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
{{ t("admin.groups.imagePricing.allowImageGeneration") }}
|
||||
{{ t(imagePricingI18nKey(editForm.platform, "allowImageGeneration")) }}
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
@@ -2232,7 +2324,7 @@
|
||||
type="checkbox"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
{{ t("admin.groups.imagePricing.independentMultiplier") }}
|
||||
{{ t(imagePricingI18nKey(editForm.platform, "independentMultiplier")) }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
@@ -2240,7 +2332,7 @@
|
||||
class="mb-4"
|
||||
>
|
||||
<label class="input-label">{{
|
||||
t("admin.groups.imagePricing.imageMultiplier")
|
||||
t(imagePricingI18nKey(editForm.platform, "imageMultiplier"))
|
||||
}}</label>
|
||||
<input
|
||||
v-model.number="editForm.image_rate_multiplier"
|
||||
@@ -2260,7 +2352,7 @@
|
||||
step="0.001"
|
||||
min="0"
|
||||
class="input"
|
||||
placeholder="0.134"
|
||||
:placeholder="getImagePricePlaceholder(editForm.platform, 'image_price_1k')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -2271,7 +2363,7 @@
|
||||
step="0.001"
|
||||
min="0"
|
||||
class="input"
|
||||
placeholder="0.201"
|
||||
:placeholder="getImagePricePlaceholder(editForm.platform, 'image_price_2k')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -2282,16 +2374,16 @@
|
||||
step="0.001"
|
||||
min="0"
|
||||
class="input"
|
||||
placeholder="0.268"
|
||||
:placeholder="getImagePricePlaceholder(editForm.platform, 'image_price_4k')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.groups.imagePricing.modeHint") }}
|
||||
{{ t(imagePricingI18nKey(editForm.platform, "modeHint")) }}
|
||||
</p>
|
||||
<div class="mt-2 rounded-lg bg-gray-50 p-3 text-xs text-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
||||
<div class="mb-1 font-medium">
|
||||
{{ t("admin.groups.imagePricing.finalPricePreview") }}
|
||||
{{ t(imagePricingI18nKey(editForm.platform, "finalPricePreview")) }}
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div
|
||||
@@ -2356,6 +2448,98 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 视频生成计费配置(仅 Grok 平台) -->
|
||||
<div
|
||||
v-if="supportsVideoPricingPlatform(editForm.platform)"
|
||||
class="border-t pt-4"
|
||||
>
|
||||
<label
|
||||
class="block mb-2 font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ t(videoPricingI18nKey("title")) }}
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
||||
{{ t(videoPricingI18nKey("description")) }}
|
||||
</p>
|
||||
<div class="mb-4">
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
v-model="editForm.video_rate_independent"
|
||||
type="checkbox"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
{{ t(videoPricingI18nKey("independentMultiplier")) }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="editForm.video_rate_independent"
|
||||
class="mb-4"
|
||||
>
|
||||
<label class="input-label">{{
|
||||
t(videoPricingI18nKey("videoMultiplier"))
|
||||
}}</label>
|
||||
<input
|
||||
v-model.number="editForm.video_rate_multiplier"
|
||||
type="number"
|
||||
step="0.0001"
|
||||
min="0"
|
||||
class="input"
|
||||
placeholder="1"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="input-label">480p ($)</label>
|
||||
<input
|
||||
v-model.number="editForm.video_price_480p"
|
||||
type="number"
|
||||
step="0.001"
|
||||
min="0"
|
||||
class="input"
|
||||
:placeholder="getVideoPricePlaceholder(editForm.platform, 'video_price_480p')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">720p ($)</label>
|
||||
<input
|
||||
v-model.number="editForm.video_price_720p"
|
||||
type="number"
|
||||
step="0.001"
|
||||
min="0"
|
||||
class="input"
|
||||
:placeholder="getVideoPricePlaceholder(editForm.platform, 'video_price_720p')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">1080p ($)</label>
|
||||
<input
|
||||
v-model.number="editForm.video_price_1080p"
|
||||
type="number"
|
||||
step="0.001"
|
||||
min="0"
|
||||
class="input"
|
||||
:placeholder="getVideoPricePlaceholder(editForm.platform, 'video_price_1080p')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t(videoPricingI18nKey("modeHint")) }}
|
||||
</p>
|
||||
<div class="mt-2 rounded-lg bg-gray-50 p-3 text-xs text-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
||||
<div class="mb-1 font-medium">
|
||||
{{ t(videoPricingI18nKey("finalPricePreview")) }}
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div
|
||||
v-for="item in editVideoFinalPricePreview"
|
||||
:key="item.label"
|
||||
>
|
||||
{{ item.label }}: {{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 高峰时段倍率配置(仅订阅类型分组) -->
|
||||
<div v-if="editForm.subscription_type === 'subscription'" class="border-t pt-4">
|
||||
<div class="mb-4 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
@@ -3342,7 +3526,16 @@ import {
|
||||
} from "./groupsModelsList";
|
||||
import { createModelsListCandidatesTracker } from "./groupsModelsListCandidates";
|
||||
import { normalizeSupportedModelScopesForPlatform } from "./groupsSupportedModelScopes";
|
||||
import { supportsImagePricingPlatform } from "./groupsImagePricing";
|
||||
import {
|
||||
getDefaultImagePreviewPrice,
|
||||
getDefaultVideoPreviewPrice,
|
||||
getImagePricePlaceholder,
|
||||
getVideoPricePlaceholder,
|
||||
imagePricingI18nKey,
|
||||
supportsImagePricingPlatform,
|
||||
supportsVideoPricingPlatform,
|
||||
videoPricingI18nKey,
|
||||
} from "./groupsImagePricing";
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
@@ -3689,6 +3882,12 @@ const createForm = reactive({
|
||||
image_price_1k: null as number | null,
|
||||
image_price_2k: null as number | null,
|
||||
image_price_4k: null as number | null,
|
||||
// 视频生成计费配置(仅 Grok 平台)
|
||||
video_rate_independent: false,
|
||||
video_rate_multiplier: 1,
|
||||
video_price_480p: null as number | null,
|
||||
video_price_720p: null as number | null,
|
||||
video_price_1080p: null as number | null,
|
||||
// 高峰时段倍率配置
|
||||
peak_rate_enabled: false,
|
||||
peak_start: "",
|
||||
@@ -4028,6 +4227,12 @@ const editForm = reactive({
|
||||
image_price_1k: null as number | null,
|
||||
image_price_2k: null as number | null,
|
||||
image_price_4k: null as number | null,
|
||||
// 视频生成计费配置(仅 Grok 平台)
|
||||
video_rate_independent: false,
|
||||
video_rate_multiplier: 1,
|
||||
video_price_480p: null as number | null,
|
||||
video_price_720p: null as number | null,
|
||||
video_price_1080p: null as number | null,
|
||||
// 高峰时段倍率配置
|
||||
peak_rate_enabled: false,
|
||||
peak_start: "",
|
||||
@@ -4077,12 +4282,28 @@ type ImagePricingFormState = {
|
||||
peak_rate_multiplier: number;
|
||||
};
|
||||
|
||||
type VideoPricingFormState = {
|
||||
platform: GroupPlatform;
|
||||
rate_multiplier: number;
|
||||
video_rate_independent: boolean;
|
||||
video_rate_multiplier: number;
|
||||
video_price_480p: number | string | null;
|
||||
video_price_720p: number | string | null;
|
||||
video_price_1080p: number | string | null;
|
||||
};
|
||||
|
||||
const imagePricingTiers = [
|
||||
{ key: "image_price_1k", label: "1K" },
|
||||
{ key: "image_price_2k", label: "2K" },
|
||||
{ key: "image_price_4k", label: "4K" },
|
||||
] as const;
|
||||
|
||||
const videoPricingTiers = [
|
||||
{ key: "video_price_480p", label: "480p" },
|
||||
{ key: "video_price_720p", label: "720p" },
|
||||
{ key: "video_price_1080p", label: "1080p" },
|
||||
] as const;
|
||||
|
||||
const normalizePreviewNumber = (value: number | string | null | undefined, fallback = 0) => {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return fallback;
|
||||
@@ -4091,6 +4312,14 @@ const normalizePreviewNumber = (value: number | string | null | undefined, fallb
|
||||
return Number.isFinite(parsed) ? parsed : fallback;
|
||||
};
|
||||
|
||||
const parsePreviewPrice = (value: number | string | null | undefined) => {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return null;
|
||||
}
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
||||
};
|
||||
|
||||
const formatImagePricePreview = (value: number | string | null | undefined) => {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return t("admin.groups.imagePricing.notConfigured");
|
||||
@@ -4102,28 +4331,64 @@ const formatImagePricePreview = (value: number | string | null | undefined) => {
|
||||
return `$${price.toFixed(6).replace(/0+$/, "").replace(/\.$/, "")}`;
|
||||
};
|
||||
|
||||
const formatVideoPricePreview = (value: number | string | null | undefined) => {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return t("admin.groups.videoPricing.notConfigured");
|
||||
}
|
||||
const price = Number(value);
|
||||
if (!Number.isFinite(price) || price < 0) {
|
||||
return t("admin.groups.videoPricing.notConfigured");
|
||||
}
|
||||
return `$${price.toFixed(6).replace(/0+$/, "").replace(/\.$/, "")}`;
|
||||
};
|
||||
|
||||
const buildImageFinalPricePreview = (form: ImagePricingFormState) => {
|
||||
const imageMultiplier = form.image_rate_independent
|
||||
? normalizePreviewNumber(form.image_rate_multiplier, 1)
|
||||
: normalizePreviewNumber(form.rate_multiplier, 1);
|
||||
const multiplier = imageMultiplier;
|
||||
return imagePricingTiers.map((tier) => {
|
||||
const basePrice = normalizePreviewNumber(form[tier.key]);
|
||||
const basePrice =
|
||||
parsePreviewPrice(form[tier.key]) ??
|
||||
getDefaultImagePreviewPrice(form.platform, tier.key);
|
||||
return {
|
||||
label: tier.label,
|
||||
value: basePrice > 0
|
||||
value: basePrice !== null
|
||||
? formatImagePricePreview(basePrice * multiplier)
|
||||
: t("admin.groups.imagePricing.notConfigured"),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const buildVideoFinalPricePreview = (form: VideoPricingFormState) => {
|
||||
const multiplier = form.video_rate_independent
|
||||
? normalizePreviewNumber(form.video_rate_multiplier, 1)
|
||||
: normalizePreviewNumber(form.rate_multiplier, 1);
|
||||
return videoPricingTiers.map((tier) => {
|
||||
const basePrice =
|
||||
parsePreviewPrice(form[tier.key]) ??
|
||||
getDefaultVideoPreviewPrice(form.platform, tier.key);
|
||||
return {
|
||||
label: tier.label,
|
||||
value: basePrice !== null
|
||||
? formatVideoPricePreview(basePrice * multiplier)
|
||||
: t("admin.groups.videoPricing.notConfigured"),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const createImageFinalPricePreview = computed(() =>
|
||||
buildImageFinalPricePreview(createForm),
|
||||
);
|
||||
const editImageFinalPricePreview = computed(() =>
|
||||
buildImageFinalPricePreview(editForm),
|
||||
);
|
||||
const createVideoFinalPricePreview = computed(() =>
|
||||
buildVideoFinalPricePreview(createForm),
|
||||
);
|
||||
const editVideoFinalPricePreview = computed(() =>
|
||||
buildVideoFinalPricePreview(editForm),
|
||||
);
|
||||
|
||||
const resetDisabledBatchImagePricing = (
|
||||
form: Pick<
|
||||
@@ -4345,6 +4610,11 @@ const closeCreateModal = () => {
|
||||
createForm.image_price_1k = null;
|
||||
createForm.image_price_2k = null;
|
||||
createForm.image_price_4k = null;
|
||||
createForm.video_rate_independent = false;
|
||||
createForm.video_rate_multiplier = 1;
|
||||
createForm.video_price_480p = null;
|
||||
createForm.video_price_720p = null;
|
||||
createForm.video_price_1080p = null;
|
||||
createForm.peak_rate_enabled = false;
|
||||
createForm.peak_start = "";
|
||||
createForm.peak_end = "";
|
||||
@@ -4445,6 +4715,9 @@ const handleCreateGroup = async () => {
|
||||
requestData.batch_image_hold_multiplier = normalizeRateMultiplier(
|
||||
requestData.batch_image_hold_multiplier,
|
||||
);
|
||||
requestData.video_rate_multiplier = normalizeRateMultiplier(
|
||||
requestData.video_rate_multiplier,
|
||||
);
|
||||
requestData.peak_rate_enabled = createForm.peak_rate_enabled;
|
||||
requestData.peak_start = createForm.peak_start;
|
||||
requestData.peak_end = createForm.peak_end;
|
||||
@@ -4493,6 +4766,11 @@ const handleEdit = async (group: AdminGroup) => {
|
||||
editForm.image_price_1k = group.image_price_1k;
|
||||
editForm.image_price_2k = group.image_price_2k;
|
||||
editForm.image_price_4k = group.image_price_4k;
|
||||
editForm.video_rate_independent = group.video_rate_independent ?? false;
|
||||
editForm.video_rate_multiplier = group.video_rate_multiplier ?? 1;
|
||||
editForm.video_price_480p = group.video_price_480p;
|
||||
editForm.video_price_720p = group.video_price_720p;
|
||||
editForm.video_price_1080p = group.video_price_1080p;
|
||||
editForm.peak_rate_enabled = group.peak_rate_enabled ?? false;
|
||||
editForm.peak_start = group.peak_start ?? "";
|
||||
editForm.peak_end = group.peak_end ?? "";
|
||||
@@ -4545,6 +4823,11 @@ const closeEditModal = () => {
|
||||
editForm.peak_start = "";
|
||||
editForm.peak_end = "";
|
||||
editForm.peak_rate_multiplier = 1.0;
|
||||
editForm.video_rate_independent = false;
|
||||
editForm.video_rate_multiplier = 1;
|
||||
editForm.video_price_480p = null;
|
||||
editForm.video_price_720p = null;
|
||||
editForm.video_price_1080p = null;
|
||||
resetMessagesDispatchFormState(editForm);
|
||||
resetModelsListState(editModelsListState);
|
||||
};
|
||||
@@ -4610,6 +4893,9 @@ const handleUpdateGroup = async () => {
|
||||
payload.batch_image_hold_multiplier = normalizeRateMultiplier(
|
||||
payload.batch_image_hold_multiplier,
|
||||
);
|
||||
payload.video_rate_multiplier = normalizeRateMultiplier(
|
||||
payload.video_rate_multiplier,
|
||||
);
|
||||
payload.peak_rate_enabled = editForm.peak_rate_enabled;
|
||||
payload.peak_start = editForm.peak_start;
|
||||
payload.peak_end = editForm.peak_end;
|
||||
|
||||
@@ -1,17 +1,50 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
getDefaultImagePreviewPrice,
|
||||
getDefaultVideoPreviewPrice,
|
||||
getImagePricePlaceholder,
|
||||
getVideoPricePlaceholder,
|
||||
imagePricingPlatforms,
|
||||
imagePricingI18nKey,
|
||||
supportsImagePricingPlatform,
|
||||
supportsVideoPricingPlatform,
|
||||
videoPricingI18nKey,
|
||||
} from "../groupsImagePricing";
|
||||
|
||||
describe("groups image pricing platform support", () => {
|
||||
it("includes Grok media groups", () => {
|
||||
it("includes Grok image groups", () => {
|
||||
expect(supportsImagePricingPlatform("grok")).toBe(true);
|
||||
expect(imagePricingPlatforms.has("grok")).toBe(true);
|
||||
});
|
||||
|
||||
it("enables video pricing controls for Grok only", () => {
|
||||
expect(supportsVideoPricingPlatform("grok")).toBe(true);
|
||||
expect(supportsVideoPricingPlatform("openai")).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps non-media group platforms out of the image pricing controls", () => {
|
||||
expect(supportsImagePricingPlatform("anthropic")).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps image and video pricing copy separate", () => {
|
||||
expect(imagePricingI18nKey("grok", "title")).toBe(
|
||||
"admin.groups.imagePricing.title",
|
||||
);
|
||||
expect(videoPricingI18nKey("title")).toBe("admin.groups.videoPricing.title");
|
||||
});
|
||||
|
||||
it("uses Grok media defaults instead of generic image fallback placeholders", () => {
|
||||
expect(getImagePricePlaceholder("grok", "image_price_1k")).toBe("0.02");
|
||||
expect(getImagePricePlaceholder("grok", "image_price_2k")).toBe("0.02");
|
||||
expect(getVideoPricePlaceholder("grok", "video_price_480p")).toBe("0.08");
|
||||
expect(getVideoPricePlaceholder("grok", "video_price_720p")).toBe("0.14");
|
||||
expect(getVideoPricePlaceholder("grok", "video_price_1080p")).toBe("0.25");
|
||||
});
|
||||
|
||||
it("keeps non-Grok image placeholders on the generic image card", () => {
|
||||
expect(getImagePricePlaceholder("openai", "image_price_1k")).toBe("0.134");
|
||||
expect(getDefaultImagePreviewPrice("openai", "image_price_2k")).toBe(0.201);
|
||||
expect(getDefaultVideoPreviewPrice("openai", "video_price_480p")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,3 +7,85 @@ export const imagePricingPlatforms = new Set([
|
||||
|
||||
export const supportsImagePricingPlatform = (platform: string): boolean =>
|
||||
imagePricingPlatforms.has(platform);
|
||||
|
||||
export const supportsVideoPricingPlatform = (platform: string): boolean =>
|
||||
platform === "grok";
|
||||
|
||||
export const imagePricingI18nKey = (_platform: string, key: string): string =>
|
||||
`admin.groups.imagePricing.${key}`;
|
||||
|
||||
export const videoPricingI18nKey = (key: string): string =>
|
||||
`admin.groups.videoPricing.${key}`;
|
||||
|
||||
type ImagePricingTierKey = "image_price_1k" | "image_price_2k" | "image_price_4k";
|
||||
type VideoPricingTierKey =
|
||||
| "video_price_480p"
|
||||
| "video_price_720p"
|
||||
| "video_price_1080p";
|
||||
|
||||
const defaultImagePricePlaceholders: Record<
|
||||
string,
|
||||
Record<ImagePricingTierKey, string>
|
||||
> = {
|
||||
default: {
|
||||
image_price_1k: "0.134",
|
||||
image_price_2k: "0.201",
|
||||
image_price_4k: "0.268",
|
||||
},
|
||||
grok: {
|
||||
image_price_1k: "0.02",
|
||||
image_price_2k: "0.02",
|
||||
image_price_4k: "0.02",
|
||||
},
|
||||
};
|
||||
|
||||
const defaultVideoPricePlaceholders: Record<
|
||||
string,
|
||||
Record<VideoPricingTierKey, string>
|
||||
> = {
|
||||
grok: {
|
||||
video_price_480p: "0.08",
|
||||
video_price_720p: "0.14",
|
||||
video_price_1080p: "0.25",
|
||||
},
|
||||
};
|
||||
|
||||
export const getImagePricePlaceholder = (
|
||||
platform: string,
|
||||
tier: ImagePricingTierKey,
|
||||
): string => {
|
||||
const card = defaultImagePricePlaceholders[platform] ?? defaultImagePricePlaceholders.default;
|
||||
return card[tier];
|
||||
};
|
||||
|
||||
export const getVideoPricePlaceholder = (
|
||||
platform: string,
|
||||
tier: VideoPricingTierKey,
|
||||
): string => {
|
||||
const card = defaultVideoPricePlaceholders[platform];
|
||||
return card?.[tier] ?? "";
|
||||
};
|
||||
|
||||
export const getDefaultImagePreviewPrice = (
|
||||
platform: string,
|
||||
tier: ImagePricingTierKey,
|
||||
): number | null => {
|
||||
const placeholder = getImagePricePlaceholder(platform, tier);
|
||||
if (placeholder === "") {
|
||||
return null;
|
||||
}
|
||||
const value = Number(placeholder);
|
||||
return Number.isFinite(value) ? value : null;
|
||||
};
|
||||
|
||||
export const getDefaultVideoPreviewPrice = (
|
||||
platform: string,
|
||||
tier: VideoPricingTierKey,
|
||||
): number | null => {
|
||||
const placeholder = getVideoPricePlaceholder(platform, tier);
|
||||
if (placeholder === "") {
|
||||
return null;
|
||||
}
|
||||
const value = Number(placeholder);
|
||||
return Number.isFinite(value) ? value : null;
|
||||
};
|
||||
|
||||
@@ -389,6 +389,7 @@ const billingModeOptions = computed<SelectOption[]>(() => [
|
||||
{ value: 'token', label: t('admin.usage.billingModeToken') },
|
||||
{ value: 'per_request', label: t('admin.usage.billingModePerRequest') },
|
||||
{ value: 'image', label: t('admin.usage.billingModeImage') },
|
||||
{ value: 'video', label: t('admin.usage.billingModeVideo') },
|
||||
])
|
||||
|
||||
const apiKeys = ref<ApiKey[]>([])
|
||||
|
||||
Reference in New Issue
Block a user