mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 03:52:26 +08:00
Merge pull request #6444 from Ritel-T/pr/group-free-openai-fast
Add group-level Standard billing for OpenAI Fast requests
This commit is contained in:
+12
-1
@@ -124,6 +124,8 @@ type Group struct {
|
||||
AllowLive bool `json:"allow_live,omitempty"`
|
||||
// 是否强制此 OpenAI/Composite 分组请求使用 service_tier=priority
|
||||
ForceOpenaiFast bool `json:"force_openai_fast,omitempty"`
|
||||
// 是否让此 OpenAI/Composite 分组的 Fast 请求按 Standard 价格计费
|
||||
FreeOpenaiFast bool `json:"free_openai_fast,omitempty"`
|
||||
// 仅允许非 apikey 类型账号关联到此分组
|
||||
RequireOauthOnly bool `json:"require_oauth_only,omitempty"`
|
||||
// 调度时仅允许 privacy 已成功设置的账号
|
||||
@@ -256,7 +258,7 @@ func (*Group) scanValues(columns []string) ([]any, error) {
|
||||
switch columns[i] {
|
||||
case group.FieldVideoModelPrices, group.FieldModelPricing, group.FieldModelRouting, group.FieldSupportedModelScopes, group.FieldMessagesDispatchModelConfig, group.FieldModelsListConfig, group.FieldReasoningEffortMappings:
|
||||
values[i] = new([]byte)
|
||||
case group.FieldPeakRateEnabled, group.FieldIsExclusive, group.FieldAllowImageGeneration, group.FieldAllowBatchImageGeneration, group.FieldImageRateIndependent, group.FieldVideoRateIndependent, group.FieldLongContextPricingEnabled, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject, group.FieldAllowMessagesDispatch, group.FieldAllowLive, group.FieldForceOpenaiFast, group.FieldRequireOauthOnly, group.FieldRequirePrivacySet, group.FieldProfitControlEnabled:
|
||||
case group.FieldPeakRateEnabled, group.FieldIsExclusive, group.FieldAllowImageGeneration, group.FieldAllowBatchImageGeneration, group.FieldImageRateIndependent, group.FieldVideoRateIndependent, group.FieldLongContextPricingEnabled, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject, group.FieldAllowMessagesDispatch, group.FieldAllowLive, group.FieldForceOpenaiFast, group.FieldFreeOpenaiFast, group.FieldRequireOauthOnly, group.FieldRequirePrivacySet, group.FieldProfitControlEnabled:
|
||||
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, group.FieldVideoRateMultiplier, group.FieldVideoPrice480p, group.FieldVideoPrice720p, group.FieldVideoPrice1080p, group.FieldWebSearchPricePerCall, group.FieldSearchPricePer1k, group.FieldAudioRealtimePricePerMin, group.FieldAudioTtsPricePerMillionChars, group.FieldAudioSttPricePerHour, group.FieldProfitMinMargin, group.FieldProfitSafetyBuffer:
|
||||
values[i] = new(sql.NullFloat64)
|
||||
@@ -626,6 +628,12 @@ func (_m *Group) assignValues(columns []string, values []any) error {
|
||||
} else if value.Valid {
|
||||
_m.ForceOpenaiFast = value.Bool
|
||||
}
|
||||
case group.FieldFreeOpenaiFast:
|
||||
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field free_openai_fast", values[i])
|
||||
} else if value.Valid {
|
||||
_m.FreeOpenaiFast = value.Bool
|
||||
}
|
||||
case group.FieldRequireOauthOnly:
|
||||
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field require_oauth_only", values[i])
|
||||
@@ -974,6 +982,9 @@ func (_m *Group) String() string {
|
||||
builder.WriteString("force_openai_fast=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.ForceOpenaiFast))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("free_openai_fast=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.FreeOpenaiFast))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("require_oauth_only=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.RequireOauthOnly))
|
||||
builder.WriteString(", ")
|
||||
|
||||
@@ -120,6 +120,8 @@ const (
|
||||
FieldAllowLive = "allow_live"
|
||||
// FieldForceOpenaiFast holds the string denoting the force_openai_fast field in the database.
|
||||
FieldForceOpenaiFast = "force_openai_fast"
|
||||
// FieldFreeOpenaiFast holds the string denoting the free_openai_fast field in the database.
|
||||
FieldFreeOpenaiFast = "free_openai_fast"
|
||||
// FieldRequireOauthOnly holds the string denoting the require_oauth_only field in the database.
|
||||
FieldRequireOauthOnly = "require_oauth_only"
|
||||
// FieldRequirePrivacySet holds the string denoting the require_privacy_set field in the database.
|
||||
@@ -271,6 +273,7 @@ var Columns = []string{
|
||||
FieldAllowMessagesDispatch,
|
||||
FieldAllowLive,
|
||||
FieldForceOpenaiFast,
|
||||
FieldFreeOpenaiFast,
|
||||
FieldRequireOauthOnly,
|
||||
FieldRequirePrivacySet,
|
||||
FieldDefaultMappedModel,
|
||||
@@ -394,6 +397,8 @@ var (
|
||||
DefaultAllowLive bool
|
||||
// DefaultForceOpenaiFast holds the default value on creation for the "force_openai_fast" field.
|
||||
DefaultForceOpenaiFast bool
|
||||
// DefaultFreeOpenaiFast holds the default value on creation for the "free_openai_fast" field.
|
||||
DefaultFreeOpenaiFast bool
|
||||
// DefaultRequireOauthOnly holds the default value on creation for the "require_oauth_only" field.
|
||||
DefaultRequireOauthOnly bool
|
||||
// DefaultRequirePrivacySet holds the default value on creation for the "require_privacy_set" field.
|
||||
@@ -674,6 +679,11 @@ func ByForceOpenaiFast(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldForceOpenaiFast, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByFreeOpenaiFast orders the results by the free_openai_fast field.
|
||||
func ByFreeOpenaiFast(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldFreeOpenaiFast, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByRequireOauthOnly orders the results by the require_oauth_only field.
|
||||
func ByRequireOauthOnly(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldRequireOauthOnly, opts...).ToFunc()
|
||||
|
||||
@@ -295,6 +295,11 @@ func ForceOpenaiFast(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldForceOpenaiFast, v))
|
||||
}
|
||||
|
||||
// FreeOpenaiFast applies equality check predicate on the "free_openai_fast" field. It's identical to FreeOpenaiFastEQ.
|
||||
func FreeOpenaiFast(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldFreeOpenaiFast, v))
|
||||
}
|
||||
|
||||
// RequireOauthOnly applies equality check predicate on the "require_oauth_only" field. It's identical to RequireOauthOnlyEQ.
|
||||
func RequireOauthOnly(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldRequireOauthOnly, v))
|
||||
@@ -2290,6 +2295,16 @@ func ForceOpenaiFastNEQ(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldNEQ(FieldForceOpenaiFast, v))
|
||||
}
|
||||
|
||||
// FreeOpenaiFastEQ applies the EQ predicate on the "free_openai_fast" field.
|
||||
func FreeOpenaiFastEQ(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldFreeOpenaiFast, v))
|
||||
}
|
||||
|
||||
// FreeOpenaiFastNEQ applies the NEQ predicate on the "free_openai_fast" field.
|
||||
func FreeOpenaiFastNEQ(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldNEQ(FieldFreeOpenaiFast, v))
|
||||
}
|
||||
|
||||
// RequireOauthOnlyEQ applies the EQ predicate on the "require_oauth_only" field.
|
||||
func RequireOauthOnlyEQ(v bool) predicate.Group {
|
||||
return predicate.Group(sql.FieldEQ(FieldRequireOauthOnly, v))
|
||||
|
||||
@@ -718,6 +718,20 @@ func (_c *GroupCreate) SetNillableForceOpenaiFast(v *bool) *GroupCreate {
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetFreeOpenaiFast sets the "free_openai_fast" field.
|
||||
func (_c *GroupCreate) SetFreeOpenaiFast(v bool) *GroupCreate {
|
||||
_c.mutation.SetFreeOpenaiFast(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableFreeOpenaiFast sets the "free_openai_fast" field if the given value is not nil.
|
||||
func (_c *GroupCreate) SetNillableFreeOpenaiFast(v *bool) *GroupCreate {
|
||||
if v != nil {
|
||||
_c.SetFreeOpenaiFast(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetRequireOauthOnly sets the "require_oauth_only" field.
|
||||
func (_c *GroupCreate) SetRequireOauthOnly(v bool) *GroupCreate {
|
||||
_c.mutation.SetRequireOauthOnly(v)
|
||||
@@ -1127,6 +1141,10 @@ func (_c *GroupCreate) defaults() error {
|
||||
v := group.DefaultForceOpenaiFast
|
||||
_c.mutation.SetForceOpenaiFast(v)
|
||||
}
|
||||
if _, ok := _c.mutation.FreeOpenaiFast(); !ok {
|
||||
v := group.DefaultFreeOpenaiFast
|
||||
_c.mutation.SetFreeOpenaiFast(v)
|
||||
}
|
||||
if _, ok := _c.mutation.RequireOauthOnly(); !ok {
|
||||
v := group.DefaultRequireOauthOnly
|
||||
_c.mutation.SetRequireOauthOnly(v)
|
||||
@@ -1325,6 +1343,9 @@ func (_c *GroupCreate) check() error {
|
||||
if _, ok := _c.mutation.ForceOpenaiFast(); !ok {
|
||||
return &ValidationError{Name: "force_openai_fast", err: errors.New(`ent: missing required field "Group.force_openai_fast"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.FreeOpenaiFast(); !ok {
|
||||
return &ValidationError{Name: "free_openai_fast", err: errors.New(`ent: missing required field "Group.free_openai_fast"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.RequireOauthOnly(); !ok {
|
||||
return &ValidationError{Name: "require_oauth_only", err: errors.New(`ent: missing required field "Group.require_oauth_only"`)}
|
||||
}
|
||||
@@ -1611,6 +1632,10 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
|
||||
_spec.SetField(group.FieldForceOpenaiFast, field.TypeBool, value)
|
||||
_node.ForceOpenaiFast = value
|
||||
}
|
||||
if value, ok := _c.mutation.FreeOpenaiFast(); ok {
|
||||
_spec.SetField(group.FieldFreeOpenaiFast, field.TypeBool, value)
|
||||
_node.FreeOpenaiFast = value
|
||||
}
|
||||
if value, ok := _c.mutation.RequireOauthOnly(); ok {
|
||||
_spec.SetField(group.FieldRequireOauthOnly, field.TypeBool, value)
|
||||
_node.RequireOauthOnly = value
|
||||
@@ -2685,6 +2710,18 @@ func (u *GroupUpsert) UpdateForceOpenaiFast() *GroupUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetFreeOpenaiFast sets the "free_openai_fast" field.
|
||||
func (u *GroupUpsert) SetFreeOpenaiFast(v bool) *GroupUpsert {
|
||||
u.Set(group.FieldFreeOpenaiFast, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateFreeOpenaiFast sets the "free_openai_fast" field to the value that was provided on create.
|
||||
func (u *GroupUpsert) UpdateFreeOpenaiFast() *GroupUpsert {
|
||||
u.SetExcluded(group.FieldFreeOpenaiFast)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetRequireOauthOnly sets the "require_oauth_only" field.
|
||||
func (u *GroupUpsert) SetRequireOauthOnly(v bool) *GroupUpsert {
|
||||
u.Set(group.FieldRequireOauthOnly, v)
|
||||
@@ -3910,6 +3947,20 @@ func (u *GroupUpsertOne) UpdateForceOpenaiFast() *GroupUpsertOne {
|
||||
})
|
||||
}
|
||||
|
||||
// SetFreeOpenaiFast sets the "free_openai_fast" field.
|
||||
func (u *GroupUpsertOne) SetFreeOpenaiFast(v bool) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetFreeOpenaiFast(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateFreeOpenaiFast sets the "free_openai_fast" field to the value that was provided on create.
|
||||
func (u *GroupUpsertOne) UpdateFreeOpenaiFast() *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateFreeOpenaiFast()
|
||||
})
|
||||
}
|
||||
|
||||
// SetRequireOauthOnly sets the "require_oauth_only" field.
|
||||
func (u *GroupUpsertOne) SetRequireOauthOnly(v bool) *GroupUpsertOne {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
@@ -5328,6 +5379,20 @@ func (u *GroupUpsertBulk) UpdateForceOpenaiFast() *GroupUpsertBulk {
|
||||
})
|
||||
}
|
||||
|
||||
// SetFreeOpenaiFast sets the "free_openai_fast" field.
|
||||
func (u *GroupUpsertBulk) SetFreeOpenaiFast(v bool) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.SetFreeOpenaiFast(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateFreeOpenaiFast sets the "free_openai_fast" field to the value that was provided on create.
|
||||
func (u *GroupUpsertBulk) UpdateFreeOpenaiFast() *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
s.UpdateFreeOpenaiFast()
|
||||
})
|
||||
}
|
||||
|
||||
// SetRequireOauthOnly sets the "require_oauth_only" field.
|
||||
func (u *GroupUpsertBulk) SetRequireOauthOnly(v bool) *GroupUpsertBulk {
|
||||
return u.Update(func(s *GroupUpsert) {
|
||||
|
||||
@@ -1003,6 +1003,20 @@ func (_u *GroupUpdate) SetNillableForceOpenaiFast(v *bool) *GroupUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetFreeOpenaiFast sets the "free_openai_fast" field.
|
||||
func (_u *GroupUpdate) SetFreeOpenaiFast(v bool) *GroupUpdate {
|
||||
_u.mutation.SetFreeOpenaiFast(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableFreeOpenaiFast sets the "free_openai_fast" field if the given value is not nil.
|
||||
func (_u *GroupUpdate) SetNillableFreeOpenaiFast(v *bool) *GroupUpdate {
|
||||
if v != nil {
|
||||
_u.SetFreeOpenaiFast(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetRequireOauthOnly sets the "require_oauth_only" field.
|
||||
func (_u *GroupUpdate) SetRequireOauthOnly(v bool) *GroupUpdate {
|
||||
_u.mutation.SetRequireOauthOnly(v)
|
||||
@@ -1833,6 +1847,9 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if value, ok := _u.mutation.ForceOpenaiFast(); ok {
|
||||
_spec.SetField(group.FieldForceOpenaiFast, field.TypeBool, value)
|
||||
}
|
||||
if value, ok := _u.mutation.FreeOpenaiFast(); ok {
|
||||
_spec.SetField(group.FieldFreeOpenaiFast, field.TypeBool, value)
|
||||
}
|
||||
if value, ok := _u.mutation.RequireOauthOnly(); ok {
|
||||
_spec.SetField(group.FieldRequireOauthOnly, field.TypeBool, value)
|
||||
}
|
||||
@@ -3163,6 +3180,20 @@ func (_u *GroupUpdateOne) SetNillableForceOpenaiFast(v *bool) *GroupUpdateOne {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetFreeOpenaiFast sets the "free_openai_fast" field.
|
||||
func (_u *GroupUpdateOne) SetFreeOpenaiFast(v bool) *GroupUpdateOne {
|
||||
_u.mutation.SetFreeOpenaiFast(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableFreeOpenaiFast sets the "free_openai_fast" field if the given value is not nil.
|
||||
func (_u *GroupUpdateOne) SetNillableFreeOpenaiFast(v *bool) *GroupUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetFreeOpenaiFast(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetRequireOauthOnly sets the "require_oauth_only" field.
|
||||
func (_u *GroupUpdateOne) SetRequireOauthOnly(v bool) *GroupUpdateOne {
|
||||
_u.mutation.SetRequireOauthOnly(v)
|
||||
@@ -4023,6 +4054,9 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
|
||||
if value, ok := _u.mutation.ForceOpenaiFast(); ok {
|
||||
_spec.SetField(group.FieldForceOpenaiFast, field.TypeBool, value)
|
||||
}
|
||||
if value, ok := _u.mutation.FreeOpenaiFast(); ok {
|
||||
_spec.SetField(group.FieldFreeOpenaiFast, field.TypeBool, value)
|
||||
}
|
||||
if value, ok := _u.mutation.RequireOauthOnly(); ok {
|
||||
_spec.SetField(group.FieldRequireOauthOnly, field.TypeBool, value)
|
||||
}
|
||||
|
||||
@@ -955,6 +955,7 @@ var (
|
||||
{Name: "allow_messages_dispatch", Type: field.TypeBool, Default: false},
|
||||
{Name: "allow_live", Type: field.TypeBool, Default: false},
|
||||
{Name: "force_openai_fast", Type: field.TypeBool, Default: false},
|
||||
{Name: "free_openai_fast", Type: field.TypeBool, Default: false},
|
||||
{Name: "require_oauth_only", Type: field.TypeBool, Default: false},
|
||||
{Name: "require_privacy_set", Type: field.TypeBool, Default: false},
|
||||
{Name: "default_mapped_model", Type: field.TypeString, Size: 100, Default: ""},
|
||||
|
||||
+55
-1
@@ -22159,6 +22159,7 @@ type GroupMutation struct {
|
||||
allow_messages_dispatch *bool
|
||||
allow_live *bool
|
||||
force_openai_fast *bool
|
||||
free_openai_fast *bool
|
||||
require_oauth_only *bool
|
||||
require_privacy_set *bool
|
||||
default_mapped_model *string
|
||||
@@ -24982,6 +24983,42 @@ func (m *GroupMutation) ResetForceOpenaiFast() {
|
||||
m.force_openai_fast = nil
|
||||
}
|
||||
|
||||
// SetFreeOpenaiFast sets the "free_openai_fast" field.
|
||||
func (m *GroupMutation) SetFreeOpenaiFast(b bool) {
|
||||
m.free_openai_fast = &b
|
||||
}
|
||||
|
||||
// FreeOpenaiFast returns the value of the "free_openai_fast" field in the mutation.
|
||||
func (m *GroupMutation) FreeOpenaiFast() (r bool, exists bool) {
|
||||
v := m.free_openai_fast
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldFreeOpenaiFast returns the old "free_openai_fast" 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) OldFreeOpenaiFast(ctx context.Context) (v bool, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldFreeOpenaiFast is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldFreeOpenaiFast requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldFreeOpenaiFast: %w", err)
|
||||
}
|
||||
return oldValue.FreeOpenaiFast, nil
|
||||
}
|
||||
|
||||
// ResetFreeOpenaiFast resets all changes to the "free_openai_fast" field.
|
||||
func (m *GroupMutation) ResetFreeOpenaiFast() {
|
||||
m.free_openai_fast = nil
|
||||
}
|
||||
|
||||
// SetRequireOauthOnly sets the "require_oauth_only" field.
|
||||
func (m *GroupMutation) SetRequireOauthOnly(b bool) {
|
||||
m.require_oauth_only = &b
|
||||
@@ -25847,7 +25884,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, 63)
|
||||
fields := make([]string, 0, 64)
|
||||
if m.created_at != nil {
|
||||
fields = append(fields, group.FieldCreatedAt)
|
||||
}
|
||||
@@ -26004,6 +26041,9 @@ func (m *GroupMutation) Fields() []string {
|
||||
if m.force_openai_fast != nil {
|
||||
fields = append(fields, group.FieldForceOpenaiFast)
|
||||
}
|
||||
if m.free_openai_fast != nil {
|
||||
fields = append(fields, group.FieldFreeOpenaiFast)
|
||||
}
|
||||
if m.require_oauth_only != nil {
|
||||
fields = append(fields, group.FieldRequireOauthOnly)
|
||||
}
|
||||
@@ -26152,6 +26192,8 @@ func (m *GroupMutation) Field(name string) (ent.Value, bool) {
|
||||
return m.AllowLive()
|
||||
case group.FieldForceOpenaiFast:
|
||||
return m.ForceOpenaiFast()
|
||||
case group.FieldFreeOpenaiFast:
|
||||
return m.FreeOpenaiFast()
|
||||
case group.FieldRequireOauthOnly:
|
||||
return m.RequireOauthOnly()
|
||||
case group.FieldRequirePrivacySet:
|
||||
@@ -26289,6 +26331,8 @@ func (m *GroupMutation) OldField(ctx context.Context, name string) (ent.Value, e
|
||||
return m.OldAllowLive(ctx)
|
||||
case group.FieldForceOpenaiFast:
|
||||
return m.OldForceOpenaiFast(ctx)
|
||||
case group.FieldFreeOpenaiFast:
|
||||
return m.OldFreeOpenaiFast(ctx)
|
||||
case group.FieldRequireOauthOnly:
|
||||
return m.OldRequireOauthOnly(ctx)
|
||||
case group.FieldRequirePrivacySet:
|
||||
@@ -26686,6 +26730,13 @@ func (m *GroupMutation) SetField(name string, value ent.Value) error {
|
||||
}
|
||||
m.SetForceOpenaiFast(v)
|
||||
return nil
|
||||
case group.FieldFreeOpenaiFast:
|
||||
v, ok := value.(bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetFreeOpenaiFast(v)
|
||||
return nil
|
||||
case group.FieldRequireOauthOnly:
|
||||
v, ok := value.(bool)
|
||||
if !ok {
|
||||
@@ -27437,6 +27488,9 @@ func (m *GroupMutation) ResetField(name string) error {
|
||||
case group.FieldForceOpenaiFast:
|
||||
m.ResetForceOpenaiFast()
|
||||
return nil
|
||||
case group.FieldFreeOpenaiFast:
|
||||
m.ResetFreeOpenaiFast()
|
||||
return nil
|
||||
case group.FieldRequireOauthOnly:
|
||||
m.ResetRequireOauthOnly()
|
||||
return nil
|
||||
|
||||
@@ -1161,34 +1161,38 @@ func init() {
|
||||
groupDescForceOpenaiFast := groupFields[48].Descriptor()
|
||||
// group.DefaultForceOpenaiFast holds the default value on creation for the force_openai_fast field.
|
||||
group.DefaultForceOpenaiFast = groupDescForceOpenaiFast.Default.(bool)
|
||||
// groupDescFreeOpenaiFast is the schema descriptor for free_openai_fast field.
|
||||
groupDescFreeOpenaiFast := groupFields[49].Descriptor()
|
||||
// group.DefaultFreeOpenaiFast holds the default value on creation for the free_openai_fast field.
|
||||
group.DefaultFreeOpenaiFast = groupDescFreeOpenaiFast.Default.(bool)
|
||||
// groupDescRequireOauthOnly is the schema descriptor for require_oauth_only field.
|
||||
groupDescRequireOauthOnly := groupFields[49].Descriptor()
|
||||
groupDescRequireOauthOnly := groupFields[50].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[50].Descriptor()
|
||||
groupDescRequirePrivacySet := groupFields[51].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[51].Descriptor()
|
||||
groupDescDefaultMappedModel := groupFields[52].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[52].Descriptor()
|
||||
groupDescMessagesDispatchModelConfig := groupFields[53].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[53].Descriptor()
|
||||
groupDescModelsListConfig := groupFields[54].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[54].Descriptor()
|
||||
groupDescRpmLimit := groupFields[55].Descriptor()
|
||||
// group.DefaultRpmLimit holds the default value on creation for the rpm_limit field.
|
||||
group.DefaultRpmLimit = groupDescRpmLimit.Default.(int)
|
||||
// groupDescMaxReasoningEffort is the schema descriptor for max_reasoning_effort field.
|
||||
groupDescMaxReasoningEffort := groupFields[55].Descriptor()
|
||||
groupDescMaxReasoningEffort := groupFields[56].Descriptor()
|
||||
// group.DefaultMaxReasoningEffort holds the default value on creation for the max_reasoning_effort field.
|
||||
group.DefaultMaxReasoningEffort = groupDescMaxReasoningEffort.Default.(string)
|
||||
// group.MaxReasoningEffortValidator is a validator for the "max_reasoning_effort" field. It is called by the builders before save.
|
||||
@@ -1200,19 +1204,19 @@ func init() {
|
||||
// group.MaxReasoningEffortOverLimitValidator is a validator for the "max_reasoning_effort_over_limit" field. It is called by the builders before save.
|
||||
group.MaxReasoningEffortOverLimitValidator = groupDescMaxReasoningEffortOverLimit.Validators[0].(func(string) error)
|
||||
// groupDescReasoningEffortMappings is the schema descriptor for reasoning_effort_mappings field.
|
||||
groupDescReasoningEffortMappings := groupFields[56].Descriptor()
|
||||
groupDescReasoningEffortMappings := groupFields[57].Descriptor()
|
||||
// group.DefaultReasoningEffortMappings holds the default value on creation for the reasoning_effort_mappings field.
|
||||
group.DefaultReasoningEffortMappings = groupDescReasoningEffortMappings.Default.([]domain.ReasoningEffortMapping)
|
||||
// groupDescProfitControlEnabled is the schema descriptor for profit_control_enabled field.
|
||||
groupDescProfitControlEnabled := groupFields[57].Descriptor()
|
||||
groupDescProfitControlEnabled := groupFields[58].Descriptor()
|
||||
// group.DefaultProfitControlEnabled holds the default value on creation for the profit_control_enabled field.
|
||||
group.DefaultProfitControlEnabled = groupDescProfitControlEnabled.Default.(bool)
|
||||
// groupDescProfitMinMargin is the schema descriptor for profit_min_margin field.
|
||||
groupDescProfitMinMargin := groupFields[58].Descriptor()
|
||||
groupDescProfitMinMargin := groupFields[59].Descriptor()
|
||||
// group.DefaultProfitMinMargin holds the default value on creation for the profit_min_margin field.
|
||||
group.DefaultProfitMinMargin = groupDescProfitMinMargin.Default.(float64)
|
||||
// groupDescProfitSafetyBuffer is the schema descriptor for profit_safety_buffer field.
|
||||
groupDescProfitSafetyBuffer := groupFields[59].Descriptor()
|
||||
groupDescProfitSafetyBuffer := groupFields[60].Descriptor()
|
||||
// group.DefaultProfitSafetyBuffer holds the default value on creation for the profit_safety_buffer field.
|
||||
group.DefaultProfitSafetyBuffer = groupDescProfitSafetyBuffer.Default.(float64)
|
||||
idempotencyrecordMixin := schema.IdempotencyRecord{}.Mixin()
|
||||
|
||||
@@ -245,6 +245,9 @@ func (Group) Fields() []ent.Field {
|
||||
field.Bool("force_openai_fast").
|
||||
Default(false).
|
||||
Comment("是否强制此 OpenAI/Composite 分组请求使用 service_tier=priority"),
|
||||
field.Bool("free_openai_fast").
|
||||
Default(false).
|
||||
Comment("是否让此 OpenAI/Composite 分组的 Fast 请求按 Standard 价格计费"),
|
||||
field.Bool("require_oauth_only").
|
||||
Default(false).
|
||||
Comment("仅允许非 apikey 类型账号关联到此分组"),
|
||||
|
||||
@@ -148,6 +148,7 @@ type CreateGroupRequest struct {
|
||||
AllowMessagesDispatch bool `json:"allow_messages_dispatch"`
|
||||
AllowLive bool `json:"allow_live"`
|
||||
ForceOpenAIFast bool `json:"force_openai_fast"`
|
||||
FreeOpenAIFast bool `json:"free_openai_fast"`
|
||||
RequireOAuthOnly bool `json:"require_oauth_only"`
|
||||
RequirePrivacySet bool `json:"require_privacy_set"`
|
||||
DefaultMappedModel string `json:"default_mapped_model"`
|
||||
@@ -220,6 +221,7 @@ type UpdateGroupRequest struct {
|
||||
AllowMessagesDispatch *bool `json:"allow_messages_dispatch"`
|
||||
AllowLive *bool `json:"allow_live"`
|
||||
ForceOpenAIFast *bool `json:"force_openai_fast"`
|
||||
FreeOpenAIFast *bool `json:"free_openai_fast"`
|
||||
RequireOAuthOnly *bool `json:"require_oauth_only"`
|
||||
RequirePrivacySet *bool `json:"require_privacy_set"`
|
||||
DefaultMappedModel *string `json:"default_mapped_model"`
|
||||
@@ -557,6 +559,7 @@ func (h *GroupHandler) Create(c *gin.Context) {
|
||||
AllowMessagesDispatch: req.AllowMessagesDispatch,
|
||||
AllowLive: req.AllowLive,
|
||||
ForceOpenAIFast: req.ForceOpenAIFast,
|
||||
FreeOpenAIFast: req.FreeOpenAIFast,
|
||||
RequireOAuthOnly: req.RequireOAuthOnly,
|
||||
RequirePrivacySet: req.RequirePrivacySet,
|
||||
DefaultMappedModel: req.DefaultMappedModel,
|
||||
@@ -688,6 +691,7 @@ func (h *GroupHandler) Update(c *gin.Context) {
|
||||
AllowMessagesDispatch: req.AllowMessagesDispatch,
|
||||
AllowLive: req.AllowLive,
|
||||
ForceOpenAIFast: req.ForceOpenAIFast,
|
||||
FreeOpenAIFast: req.FreeOpenAIFast,
|
||||
RequireOAuthOnly: req.RequireOAuthOnly,
|
||||
RequirePrivacySet: req.RequirePrivacySet,
|
||||
DefaultMappedModel: req.DefaultMappedModel,
|
||||
|
||||
@@ -9,15 +9,19 @@ import (
|
||||
|
||||
func TestGroupRequestsDecodeForceOpenAIFast(t *testing.T) {
|
||||
var createReq CreateGroupRequest
|
||||
require.NoError(t, json.Unmarshal([]byte(`{"name":"fast","force_openai_fast":true}`), &createReq))
|
||||
require.NoError(t, json.Unmarshal([]byte(`{"name":"fast","force_openai_fast":true,"free_openai_fast":true}`), &createReq))
|
||||
require.True(t, createReq.ForceOpenAIFast)
|
||||
require.True(t, createReq.FreeOpenAIFast)
|
||||
|
||||
var updateReq UpdateGroupRequest
|
||||
require.NoError(t, json.Unmarshal([]byte(`{"force_openai_fast":false}`), &updateReq))
|
||||
require.NoError(t, json.Unmarshal([]byte(`{"force_openai_fast":false,"free_openai_fast":false}`), &updateReq))
|
||||
require.NotNil(t, updateReq.ForceOpenAIFast)
|
||||
require.False(t, *updateReq.ForceOpenAIFast)
|
||||
require.NotNil(t, updateReq.FreeOpenAIFast)
|
||||
require.False(t, *updateReq.FreeOpenAIFast)
|
||||
|
||||
var omitted UpdateGroupRequest
|
||||
require.NoError(t, json.Unmarshal([]byte(`{}`), &omitted))
|
||||
require.Nil(t, omitted.ForceOpenAIFast)
|
||||
require.Nil(t, omitted.FreeOpenAIFast)
|
||||
}
|
||||
|
||||
@@ -10,14 +10,16 @@ import (
|
||||
|
||||
func TestGroupMapperExposesForceOpenAIFastOnlyToAdmins(t *testing.T) {
|
||||
group := &service.Group{
|
||||
ID: 7, Name: "fast", Platform: service.PlatformOpenAI, Status: service.StatusActive, ForceOpenAIFast: true,
|
||||
ID: 7, Name: "fast", Platform: service.PlatformOpenAI, Status: service.StatusActive, ForceOpenAIFast: true, FreeOpenAIFast: true,
|
||||
}
|
||||
|
||||
userJSON, err := json.Marshal(GroupFromService(group))
|
||||
require.NoError(t, err)
|
||||
require.NotContains(t, string(userJSON), "force_openai_fast")
|
||||
require.NotContains(t, string(userJSON), "free_openai_fast")
|
||||
|
||||
adminJSON, err := json.Marshal(GroupFromServiceAdmin(group))
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(adminJSON), `"force_openai_fast":true`)
|
||||
require.Contains(t, string(adminJSON), `"free_openai_fast":true`)
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ func GroupFromServiceAdmin(g *service.Group) *AdminGroup {
|
||||
out := &AdminGroup{
|
||||
Group: groupFromServiceBase(g),
|
||||
ForceOpenAIFast: g.ForceOpenAIFast,
|
||||
FreeOpenAIFast: g.FreeOpenAIFast,
|
||||
ProfitControlEnabled: g.ProfitControlEnabled,
|
||||
ProfitMinMargin: g.ProfitMinMargin,
|
||||
ProfitSafetyBuffer: g.ProfitSafetyBuffer,
|
||||
|
||||
@@ -168,6 +168,8 @@ type AdminGroup struct {
|
||||
Group
|
||||
// ForceOpenAIFast 是管理端请求策略,用户侧分组 DTO 无需暴露。
|
||||
ForceOpenAIFast bool `json:"force_openai_fast"`
|
||||
// FreeOpenAIFast 是管理端计费策略,用户侧分组 DTO 无需暴露。
|
||||
FreeOpenAIFast bool `json:"free_openai_fast"`
|
||||
|
||||
// 分组利润控制(五个 token 平台分组可启用;margin/buffer 为小数存储)。
|
||||
// 仅管理员可见:这三个字段与同响应中的 rate_multiplier 相乘即可反推出
|
||||
|
||||
@@ -211,6 +211,7 @@ func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*se
|
||||
group.FieldAllowMessagesDispatch,
|
||||
group.FieldAllowLive,
|
||||
group.FieldForceOpenaiFast,
|
||||
group.FieldFreeOpenaiFast,
|
||||
group.FieldDefaultMappedModel,
|
||||
group.FieldMessagesDispatchModelConfig,
|
||||
group.FieldModelsListConfig,
|
||||
@@ -1010,6 +1011,7 @@ func groupEntityToService(g *dbent.Group) *service.Group {
|
||||
AllowMessagesDispatch: g.AllowMessagesDispatch,
|
||||
AllowLive: g.AllowLive,
|
||||
ForceOpenAIFast: g.ForceOpenaiFast,
|
||||
FreeOpenAIFast: g.FreeOpenaiFast,
|
||||
RequireOAuthOnly: g.RequireOauthOnly,
|
||||
RequirePrivacySet: g.RequirePrivacySet,
|
||||
DefaultMappedModel: g.DefaultMappedModel,
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestGetByKeyForAuthCarriesGroupForceOpenAIFast(t *testing.T) {
|
||||
suffix := time.Now().UnixNano()
|
||||
group := mustCreateGroup(t, integrationEntClient, &service.Group{
|
||||
Name: fmt.Sprintf("fast-proj-group-%d", suffix), Platform: service.PlatformOpenAI,
|
||||
RateMultiplier: 1, ForceOpenAIFast: true,
|
||||
RateMultiplier: 1, ForceOpenAIFast: true, FreeOpenAIFast: true,
|
||||
})
|
||||
user := mustCreateUser(t, integrationEntClient, &service.User{
|
||||
Email: fmt.Sprintf("fast-proj-%d@example.com", suffix), Concurrency: 5,
|
||||
@@ -42,4 +42,5 @@ func TestGetByKeyForAuthCarriesGroupForceOpenAIFast(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got.Group)
|
||||
require.True(t, got.Group.ForceOpenAIFast)
|
||||
require.True(t, got.Group.FreeOpenAIFast)
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ func mustCreateGroup(t *testing.T, client *dbent.Client, g *service.Group) *serv
|
||||
SetRateMultiplier(g.RateMultiplier).
|
||||
SetIsExclusive(g.IsExclusive).
|
||||
SetForceOpenaiFast(g.ForceOpenAIFast).
|
||||
SetFreeOpenaiFast(g.FreeOpenAIFast).
|
||||
SetProfitControlEnabled(g.ProfitControlEnabled).
|
||||
SetProfitMinMargin(g.ProfitMinMargin).
|
||||
SetProfitSafetyBuffer(g.ProfitSafetyBuffer)
|
||||
|
||||
@@ -104,6 +104,7 @@ func createGroupRecord(ctx context.Context, client *dbent.Client, groupIn *servi
|
||||
SetAllowMessagesDispatch(groupIn.AllowMessagesDispatch).
|
||||
SetAllowLive(groupIn.AllowLive).
|
||||
SetForceOpenaiFast(groupIn.ForceOpenAIFast).
|
||||
SetFreeOpenaiFast(groupIn.FreeOpenAIFast).
|
||||
SetRequireOauthOnly(groupIn.RequireOAuthOnly).
|
||||
SetRequirePrivacySet(groupIn.RequirePrivacySet).
|
||||
SetDefaultMappedModel(groupIn.DefaultMappedModel).
|
||||
@@ -282,6 +283,7 @@ func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) er
|
||||
SetAllowMessagesDispatch(groupIn.AllowMessagesDispatch).
|
||||
SetAllowLive(groupIn.AllowLive).
|
||||
SetForceOpenaiFast(groupIn.ForceOpenAIFast).
|
||||
SetFreeOpenaiFast(groupIn.FreeOpenAIFast).
|
||||
SetRequireOauthOnly(groupIn.RequireOAuthOnly).
|
||||
SetRequirePrivacySet(groupIn.RequirePrivacySet).
|
||||
SetDefaultMappedModel(groupIn.DefaultMappedModel).
|
||||
|
||||
@@ -58,6 +58,7 @@ func TestMigrationsRunner_IsIdempotent_AndSchemaIsUpToDate(t *testing.T) {
|
||||
// groups: OpenAI Live 与 Fast 强制策略都默认关闭,管理员显式开启后才生效。
|
||||
requireColumn(t, tx, "groups", "allow_live", "boolean", 0, false)
|
||||
requireColumn(t, tx, "groups", "force_openai_fast", "boolean", 0, false)
|
||||
requireColumn(t, tx, "groups", "free_openai_fast", "boolean", 0, false)
|
||||
|
||||
// api_keys: key length should be 128
|
||||
requireColumn(t, tx, "api_keys", "key", "character varying", 128, false)
|
||||
|
||||
@@ -303,6 +303,7 @@ func sanitizeGroupOpenAIFast(group *Group) {
|
||||
if group == nil || !groupSupportsOpenAIFast(group.Platform) {
|
||||
if group != nil {
|
||||
group.ForceOpenAIFast = false
|
||||
group.FreeOpenAIFast = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -517,6 +518,7 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
|
||||
AllowMessagesDispatch: input.AllowMessagesDispatch,
|
||||
AllowLive: input.AllowLive,
|
||||
ForceOpenAIFast: input.ForceOpenAIFast,
|
||||
FreeOpenAIFast: input.FreeOpenAIFast,
|
||||
RequireOAuthOnly: input.RequireOAuthOnly,
|
||||
RequirePrivacySet: input.RequirePrivacySet,
|
||||
DefaultMappedModel: input.DefaultMappedModel,
|
||||
@@ -884,6 +886,9 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd
|
||||
if input.ForceOpenAIFast != nil {
|
||||
group.ForceOpenAIFast = *input.ForceOpenAIFast
|
||||
}
|
||||
if input.FreeOpenAIFast != nil {
|
||||
group.FreeOpenAIFast = *input.FreeOpenAIFast
|
||||
}
|
||||
if input.RequireOAuthOnly != nil {
|
||||
group.RequireOAuthOnly = *input.RequireOAuthOnly
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ func cloneGroupForDuplicate(source *Group, operationID string) *Group {
|
||||
AllowMessagesDispatch: source.AllowMessagesDispatch,
|
||||
AllowLive: source.AllowLive,
|
||||
ForceOpenAIFast: source.ForceOpenAIFast,
|
||||
FreeOpenAIFast: source.FreeOpenAIFast,
|
||||
RequireOAuthOnly: source.RequireOAuthOnly,
|
||||
RequirePrivacySet: source.RequirePrivacySet,
|
||||
DefaultMappedModel: source.DefaultMappedModel,
|
||||
|
||||
@@ -167,6 +167,7 @@ func TestDuplicateGroupCopiesConfigurationDeeplyAndResetsRuntimeState(t *testing
|
||||
AllowMessagesDispatch: true,
|
||||
AllowLive: true,
|
||||
ForceOpenAIFast: true,
|
||||
FreeOpenAIFast: true,
|
||||
RequireOAuthOnly: true,
|
||||
RequirePrivacySet: true,
|
||||
DefaultMappedModel: "gpt-5.4",
|
||||
@@ -215,6 +216,7 @@ func TestDuplicateGroupCopiesConfigurationDeeplyAndResetsRuntimeState(t *testing
|
||||
require.Equal(t, source.ModelRouting, duplicate.ModelRouting)
|
||||
require.Equal(t, source.MessagesDispatchModelConfig, duplicate.MessagesDispatchModelConfig)
|
||||
require.Equal(t, source.ForceOpenAIFast, duplicate.ForceOpenAIFast)
|
||||
require.Equal(t, source.FreeOpenAIFast, duplicate.FreeOpenAIFast)
|
||||
require.Equal(t, source.ModelsListConfig, duplicate.ModelsListConfig)
|
||||
require.Equal(t, source.RPMLimit, duplicate.RPMLimit)
|
||||
require.Equal(t, source.MaxReasoningEffort, duplicate.MaxReasoningEffort)
|
||||
|
||||
@@ -267,6 +267,7 @@ type CreateGroupInput struct {
|
||||
AllowMessagesDispatch bool
|
||||
AllowLive bool
|
||||
ForceOpenAIFast bool
|
||||
FreeOpenAIFast bool
|
||||
DefaultMappedModel string
|
||||
RequireOAuthOnly bool
|
||||
RequirePrivacySet bool
|
||||
@@ -345,6 +346,7 @@ type UpdateGroupInput struct {
|
||||
AllowMessagesDispatch *bool
|
||||
AllowLive *bool
|
||||
ForceOpenAIFast *bool
|
||||
FreeOpenAIFast *bool
|
||||
DefaultMappedModel *string
|
||||
RequireOAuthOnly *bool
|
||||
RequirePrivacySet *bool
|
||||
|
||||
@@ -1099,19 +1099,20 @@ func TestAdminService_CreateGroup_NormalizesForceOpenAIFastByPlatform(t *testing
|
||||
svc := &adminServiceImpl{groupRepo: repo}
|
||||
|
||||
group, err := svc.CreateGroup(context.Background(), &CreateGroupInput{
|
||||
Name: "fast-" + tt.name, Platform: tt.platform, RateMultiplier: 1, ForceOpenAIFast: true,
|
||||
Name: "fast-" + tt.name, Platform: tt.platform, RateMultiplier: 1, ForceOpenAIFast: true, FreeOpenAIFast: true,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, group)
|
||||
require.Equal(t, tt.want, repo.created.ForceOpenAIFast)
|
||||
require.Equal(t, tt.want, repo.created.FreeOpenAIFast)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_UpdateGroup_ClearsForceOpenAIFastWhenPlatformChanges(t *testing.T) {
|
||||
existingGroup := &Group{
|
||||
ID: 1, Name: "existing-fast", Platform: PlatformOpenAI, Status: StatusActive, ForceOpenAIFast: true,
|
||||
ID: 1, Name: "existing-fast", Platform: PlatformOpenAI, Status: StatusActive, ForceOpenAIFast: true, FreeOpenAIFast: true,
|
||||
}
|
||||
repo := &groupRepoStubForAdmin{getByID: existingGroup}
|
||||
svc := &adminServiceImpl{groupRepo: repo}
|
||||
@@ -1123,6 +1124,7 @@ func TestAdminService_UpdateGroup_ClearsForceOpenAIFastWhenPlatformChanges(t *te
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, group)
|
||||
require.False(t, repo.updated.ForceOpenAIFast)
|
||||
require.False(t, repo.updated.FreeOpenAIFast)
|
||||
}
|
||||
|
||||
func TestAdminService_UpdateGroup_ForceOpenAIFastInvalidatesAuthCache(t *testing.T) {
|
||||
@@ -1136,11 +1138,13 @@ func TestAdminService_UpdateGroup_ForceOpenAIFastInvalidatesAuthCache(t *testing
|
||||
|
||||
group, err := svc.UpdateGroup(context.Background(), existingGroup.ID, &UpdateGroupInput{
|
||||
ForceOpenAIFast: &enabled,
|
||||
FreeOpenAIFast: &enabled,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, group)
|
||||
require.True(t, repo.updated.ForceOpenAIFast)
|
||||
require.True(t, repo.updated.FreeOpenAIFast)
|
||||
require.Equal(t, []int64{existingGroup.ID}, invalidator.groupIDs)
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ type APIKeyAuthGroupSnapshot struct {
|
||||
AllowMessagesDispatch bool `json:"allow_messages_dispatch"`
|
||||
AllowLive bool `json:"allow_live"`
|
||||
ForceOpenAIFast bool `json:"force_openai_fast"`
|
||||
FreeOpenAIFast bool `json:"free_openai_fast"`
|
||||
DefaultMappedModel string `json:"default_mapped_model,omitempty"`
|
||||
MessagesDispatchModelConfig OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config,omitempty"`
|
||||
ModelsListConfig GroupModelsListConfig `json:"models_list_config,omitempty"`
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/dgraph-io/ristretto"
|
||||
)
|
||||
|
||||
const apiKeyAuthSnapshotVersion = 21 // v21: group force_openai_fast field
|
||||
const apiKeyAuthSnapshotVersion = 22 // v22: group free_openai_fast field
|
||||
|
||||
type apiKeyAuthCacheConfig struct {
|
||||
l1Size int
|
||||
@@ -419,6 +419,7 @@ func (s *APIKeyService) snapshotFromAPIKey(ctx context.Context, apiKey *APIKey)
|
||||
AllowMessagesDispatch: apiKey.Group.AllowMessagesDispatch,
|
||||
AllowLive: apiKey.Group.AllowLive,
|
||||
ForceOpenAIFast: apiKey.Group.ForceOpenAIFast,
|
||||
FreeOpenAIFast: apiKey.Group.FreeOpenAIFast,
|
||||
DefaultMappedModel: apiKey.Group.DefaultMappedModel,
|
||||
MessagesDispatchModelConfig: apiKey.Group.MessagesDispatchModelConfig,
|
||||
ModelsListConfig: apiKey.Group.ModelsListConfig,
|
||||
@@ -519,6 +520,7 @@ func (s *APIKeyService) snapshotToAPIKey(key string, snapshot *APIKeyAuthSnapsho
|
||||
AllowMessagesDispatch: snapshot.Group.AllowMessagesDispatch,
|
||||
AllowLive: snapshot.Group.AllowLive,
|
||||
ForceOpenAIFast: snapshot.Group.ForceOpenAIFast,
|
||||
FreeOpenAIFast: snapshot.Group.FreeOpenAIFast,
|
||||
DefaultMappedModel: snapshot.Group.DefaultMappedModel,
|
||||
MessagesDispatchModelConfig: snapshot.Group.MessagesDispatchModelConfig,
|
||||
ModelsListConfig: snapshot.Group.ModelsListConfig,
|
||||
|
||||
@@ -15,7 +15,7 @@ func TestAPIKeyAuthSnapshotGroupForceOpenAIFastRoundtrip(t *testing.T) {
|
||||
User: &User{ID: 40, Status: StatusActive},
|
||||
Group: &Group{
|
||||
ID: groupID, Name: "fast-roundtrip", Platform: PlatformOpenAI, Status: StatusActive,
|
||||
Hydrated: true, ForceOpenAIFast: true,
|
||||
Hydrated: true, ForceOpenAIFast: true, FreeOpenAIFast: true,
|
||||
},
|
||||
}
|
||||
svc := &APIKeyService{}
|
||||
@@ -31,5 +31,6 @@ func TestAPIKeyAuthSnapshotGroupForceOpenAIFastRoundtrip(t *testing.T) {
|
||||
require.NotNil(t, materialized.Group)
|
||||
require.True(t, materialized.Group.Hydrated)
|
||||
require.True(t, materialized.Group.ForceOpenAIFast)
|
||||
require.Equal(t, 21, cached.Snapshot.Version)
|
||||
require.True(t, materialized.Group.FreeOpenAIFast)
|
||||
require.Equal(t, 22, cached.Snapshot.Version)
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestAPIKeyAuthSnapshotProfitControlRoundtrip(t *testing.T) {
|
||||
snapshot := svc.snapshotFromAPIKey(context.Background(), apiKey)
|
||||
require.NotNil(t, snapshot)
|
||||
require.Equal(t, apiKeyAuthSnapshotVersion, snapshot.Version)
|
||||
require.Equal(t, 21, snapshot.Version, "v21 起认证快照携带分组 Fast 强制开关")
|
||||
require.Equal(t, 22, snapshot.Version, "v22 起认证快照携带分组免费 Fast 开关")
|
||||
|
||||
// 模拟 L2 缓存的完整 JSON 往返(与 apiKeyCache.SetAuthCache/GetAuthCache 同构)。
|
||||
payload, err := json.Marshal(&APIKeyAuthCacheEntry{Snapshot: snapshot})
|
||||
|
||||
@@ -101,6 +101,7 @@ type Group struct {
|
||||
AllowMessagesDispatch bool
|
||||
AllowLive bool
|
||||
ForceOpenAIFast bool // 强制 OpenAI 网关请求使用 service_tier=priority
|
||||
FreeOpenAIFast bool // OpenAI Fast 请求按 Standard 价格向用户计费
|
||||
RequireOAuthOnly bool // 仅允许非 apikey 类型账号关联(OpenAI/Antigravity/Anthropic/Gemini)
|
||||
RequirePrivacySet bool // 调度时仅允许 privacy 已成功设置的账号(OpenAI/Antigravity/Anthropic/Gemini)
|
||||
DefaultMappedModel string
|
||||
|
||||
@@ -3081,6 +3081,77 @@ func TestOpenAIGatewayServiceRecordUsage_ShadowUsesParentCredentialTierContract(
|
||||
require.InDelta(t, fastCost.TotalCost, usageRepo.lastLog.TotalCost, 1e-10)
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayServiceRecordUsage_FreeOpenAIFastChargesStandard(t *testing.T) {
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
svc := newOpenAIRecordUsageServiceForTest(
|
||||
usageRepo,
|
||||
&openAIRecordUsageUserRepoStub{},
|
||||
&openAIRecordUsageSubRepoStub{},
|
||||
nil,
|
||||
)
|
||||
svc.resolver = NewModelPricingResolver(nil, svc.billingService)
|
||||
groupID := int64(77)
|
||||
serviceTier := "priority"
|
||||
tokens := UsageTokens{InputTokens: 100, OutputTokens: 50}
|
||||
inputPrice := 0.001
|
||||
outputPrice := 0.002
|
||||
fastMultiplier := 3.0
|
||||
apiKey := &APIKey{
|
||||
ID: 1020,
|
||||
GroupID: &groupID,
|
||||
Group: &Group{
|
||||
ID: groupID, Platform: PlatformOpenAI, Status: StatusActive,
|
||||
Hydrated: true, RateMultiplier: 0.5, FreeOpenAIFast: true,
|
||||
ModelPricing: []ChannelModelPricing{{
|
||||
Models: []string{"gpt-5.6-sol"},
|
||||
BillingMode: BillingModeToken,
|
||||
InputPrice: &inputPrice,
|
||||
OutputPrice: &outputPrice,
|
||||
FastMultiplier: &fastMultiplier,
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
|
||||
Result: &OpenAIForwardResult{
|
||||
RequestID: "resp_free_fast",
|
||||
ServiceTier: &serviceTier,
|
||||
UpstreamResponseServiceTier: "default",
|
||||
Usage: OpenAIUsage{InputTokens: tokens.InputTokens, OutputTokens: tokens.OutputTokens},
|
||||
Model: "gpt-5.6-sol",
|
||||
Duration: time.Second,
|
||||
},
|
||||
APIKey: apiKey,
|
||||
User: &User{ID: 2020},
|
||||
Account: &Account{ID: 3020, Platform: PlatformOpenAI, Type: AccountTypeOAuth},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, usageRepo.lastLog)
|
||||
require.NotNil(t, usageRepo.lastLog.ServiceTier)
|
||||
require.Equal(t, "priority", *usageRepo.lastLog.ServiceTier)
|
||||
require.InDelta(t, 0.5, usageRepo.lastLog.RateMultiplier, 1e-12)
|
||||
|
||||
standardTotal := float64(tokens.InputTokens)*inputPrice + float64(tokens.OutputTokens)*outputPrice
|
||||
require.InDelta(t, standardTotal*fastMultiplier, usageRepo.lastLog.TotalCost, 1e-10)
|
||||
require.InDelta(t, standardTotal*0.5, usageRepo.lastLog.ActualCost, 1e-10)
|
||||
}
|
||||
|
||||
func TestGroupBillsOpenAIFastAtStandardRequiresOpenAIAccount(t *testing.T) {
|
||||
apiKey := &APIKey{Group: &Group{Platform: PlatformComposite, FreeOpenAIFast: true}}
|
||||
|
||||
require.True(t, groupBillsOpenAIFastAtStandard(
|
||||
apiKey,
|
||||
&Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth},
|
||||
"priority",
|
||||
))
|
||||
require.False(t, groupBillsOpenAIFastAtStandard(
|
||||
apiKey,
|
||||
&Account{Platform: PlatformGrok, Type: AccountTypeAPIKey},
|
||||
"priority",
|
||||
))
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayServiceRecordUsage_ServiceTierNeverRaisedByUpstreamResponse(t *testing.T) {
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
userRepo := &openAIRecordUsageUserRepoStub{}
|
||||
|
||||
@@ -132,6 +132,24 @@ func openAIUsagePricingAt(input *OpenAIRecordUsageInput) time.Time {
|
||||
return timezone.Now()
|
||||
}
|
||||
|
||||
func groupBillsOpenAIFastAtStandard(apiKey *APIKey, account *Account, serviceTier string) bool {
|
||||
if apiKey == nil || apiKey.Group == nil || !apiKey.Group.FreeOpenAIFast {
|
||||
return false
|
||||
}
|
||||
if account == nil || !account.IsOpenAI() {
|
||||
return false
|
||||
}
|
||||
if !groupSupportsOpenAIFast(apiKey.Group.Platform) {
|
||||
return false
|
||||
}
|
||||
switch normalizeBillingServiceTier(serviceTier) {
|
||||
case "priority", "fast":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// RecordUsage records usage and deducts balance
|
||||
func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRecordUsageInput) error {
|
||||
if input == nil {
|
||||
@@ -277,6 +295,32 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
|
||||
}
|
||||
}
|
||||
|
||||
// Free Fast changes only the customer charge. Keep priority TotalCost and
|
||||
// service_tier for upstream accounting, but evaluate ActualCost once more at
|
||||
// the Standard tier using the same channel, peak, and long-context policy.
|
||||
if groupBillsOpenAIFastAtStandard(apiKey, billingAccount, serviceTier) {
|
||||
standardCost, standardErr := s.calculateOpenAIRecordUsageCost(
|
||||
ctx,
|
||||
result,
|
||||
apiKey,
|
||||
billingModels,
|
||||
multiplier,
|
||||
imageMultiplier,
|
||||
videoMultiplier,
|
||||
baseMultiplier,
|
||||
tokens,
|
||||
"",
|
||||
longContextBillingGate,
|
||||
pricingAt,
|
||||
)
|
||||
if standardErr != nil {
|
||||
return standardErr
|
||||
}
|
||||
if cost != nil && standardCost != nil {
|
||||
cost.ActualCost = standardCost.ActualCost
|
||||
}
|
||||
}
|
||||
|
||||
// Determine billing type
|
||||
isSubscriptionBilling := subscription != nil && apiKey.Group != nil && apiKey.Group.IsSubscriptionType()
|
||||
billingType := BillingTypeBalance
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE groups
|
||||
ADD COLUMN IF NOT EXISTS free_openai_fast BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
COMMENT ON COLUMN groups.free_openai_fast IS
|
||||
'Whether Fast/priority requests in this OpenAI/Composite group are billed to users at Standard price';
|
||||
@@ -0,0 +1,18 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGroupFreeOpenAIFastMigration(t *testing.T) {
|
||||
content, err := FS.ReadFile("233_group_free_openai_fast.sql")
|
||||
require.NoError(t, err)
|
||||
|
||||
sql := strings.Join(strings.Fields(string(content)), " ")
|
||||
require.Contains(t, sql,
|
||||
"ADD COLUMN IF NOT EXISTS free_openai_fast BOOLEAN NOT NULL DEFAULT FALSE")
|
||||
require.Contains(t, sql, "COMMENT ON COLUMN groups.free_openai_fast")
|
||||
}
|
||||
@@ -1158,7 +1158,9 @@ export default {
|
||||
openaiFast: {
|
||||
title: 'OpenAI Fast mode',
|
||||
force: 'Force Fast (priority)',
|
||||
hint: 'Forces service_tier=priority on OpenAI requests in this group. The global Fast/Flex policy can still filter or block it. New requests update immediately after saving; existing WebSocket sessions must reconnect.'
|
||||
hint: 'Forces service_tier=priority on OpenAI requests in this group. The global Fast/Flex policy can still filter or block it. New requests update immediately after saving; existing WebSocket sessions must reconnect.',
|
||||
free: 'Free Fast',
|
||||
freeHint: 'Fast requests in this group still use the priority tier, but customers are charged the equivalent Standard price.'
|
||||
},
|
||||
invalidRequestFallback: {
|
||||
title: 'Invalid Request Fallback Group',
|
||||
|
||||
@@ -1156,7 +1156,9 @@ export default {
|
||||
openaiFast: {
|
||||
title: 'OpenAI Fast 模式',
|
||||
force: '强制使用 Fast(priority)',
|
||||
hint: '开启后,此分组的 OpenAI 请求会强制写入 service_tier=priority;全局 Fast/Flex 策略仍可过滤或拦截。保存后新请求立即生效,已建立的 WebSocket 会话需重连。'
|
||||
hint: '开启后,此分组的 OpenAI 请求会强制写入 service_tier=priority;全局 Fast/Flex 策略仍可过滤或拦截。保存后新请求立即生效,已建立的 WebSocket 会话需重连。',
|
||||
free: '免费 Fast',
|
||||
freeHint: '该分组的 Fast 请求仍使用 priority 档位,但客户实际费用按同一请求的 Standard 价格计算。'
|
||||
},
|
||||
invalidRequestFallback: {
|
||||
title: '无效请求兜底分组',
|
||||
|
||||
@@ -613,6 +613,7 @@ export interface Group {
|
||||
|
||||
export interface AdminGroup extends Group {
|
||||
force_openai_fast: boolean
|
||||
free_openai_fast: boolean
|
||||
model_pricing: import('@/api/admin/channels').ChannelModelPricing[]
|
||||
// 分组利润控制(openai/anthropic/gemini/grok/antigravity 分组可启用;margin/buffer 为小数存储)。
|
||||
// 仅管理员可见:与 rate_multiplier 相乘即可反推上游成本上限,不得下放到 Group。
|
||||
@@ -778,6 +779,7 @@ export interface CreateGroupRequest {
|
||||
monthly_limit_usd?: number | null
|
||||
long_context_pricing_enabled?: boolean
|
||||
force_openai_fast?: boolean
|
||||
free_openai_fast?: boolean
|
||||
model_pricing?: import('@/api/admin/channels').ChannelModelPricing[]
|
||||
allow_image_generation?: boolean
|
||||
allow_batch_image_generation?: boolean
|
||||
@@ -842,6 +844,7 @@ export interface UpdateGroupRequest {
|
||||
monthly_limit_usd?: number | null
|
||||
long_context_pricing_enabled?: boolean
|
||||
force_openai_fast?: boolean
|
||||
free_openai_fast?: boolean
|
||||
model_pricing?: import('@/api/admin/channels').ChannelModelPricing[]
|
||||
allow_image_generation?: boolean
|
||||
allow_batch_image_generation?: boolean
|
||||
|
||||
@@ -1619,6 +1619,35 @@
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{{ t("admin.groups.openaiFast.hint") }}
|
||||
</p>
|
||||
<div class="flex items-center justify-between gap-4 mt-4">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ t("admin.groups.openaiFast.free") }}
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
:aria-checked="createForm.free_openai_fast"
|
||||
:aria-label="t('admin.groups.openaiFast.free')"
|
||||
data-testid="create-free-openai-fast"
|
||||
@click="createForm.free_openai_fast = !createForm.free_openai_fast"
|
||||
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
|
||||
:class="
|
||||
createForm.free_openai_fast
|
||||
? 'bg-emerald-500'
|
||||
: 'bg-gray-300 dark:bg-dark-600'
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
|
||||
:class="
|
||||
createForm.free_openai_fast ? 'translate-x-6' : 'translate-x-1'
|
||||
"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{{ t("admin.groups.openaiFast.freeHint") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Codex Live 开关(OpenAI 与 Composite 平台) -->
|
||||
@@ -3387,6 +3416,35 @@
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{{ t("admin.groups.openaiFast.hint") }}
|
||||
</p>
|
||||
<div class="flex items-center justify-between gap-4 mt-4">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ t("admin.groups.openaiFast.free") }}
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
:aria-checked="editForm.free_openai_fast"
|
||||
:aria-label="t('admin.groups.openaiFast.free')"
|
||||
data-testid="edit-free-openai-fast"
|
||||
@click="editForm.free_openai_fast = !editForm.free_openai_fast"
|
||||
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
|
||||
:class="
|
||||
editForm.free_openai_fast
|
||||
? 'bg-emerald-500'
|
||||
: 'bg-gray-300 dark:bg-dark-600'
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
|
||||
:class="
|
||||
editForm.free_openai_fast ? 'translate-x-6' : 'translate-x-1'
|
||||
"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{{ t("admin.groups.openaiFast.freeHint") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Codex Live 开关(OpenAI 与 Composite 平台) -->
|
||||
@@ -5137,6 +5195,7 @@ const createForm = reactive({
|
||||
monthly_limit_usd: null as number | null,
|
||||
long_context_pricing_enabled: true,
|
||||
force_openai_fast: false,
|
||||
free_openai_fast: false,
|
||||
model_pricing: [] as PricingFormEntry[],
|
||||
// 图片生成计费配置
|
||||
allow_image_generation: false,
|
||||
@@ -5500,6 +5559,7 @@ const editForm = reactive({
|
||||
monthly_limit_usd: null as number | null,
|
||||
long_context_pricing_enabled: true,
|
||||
force_openai_fast: false,
|
||||
free_openai_fast: false,
|
||||
model_pricing: [] as PricingFormEntry[],
|
||||
// 图片生成计费配置
|
||||
allow_image_generation: false,
|
||||
@@ -5976,6 +6036,7 @@ const closeCreateModal = () => {
|
||||
createForm.video_model_prices = createVideoModelPricesForm();
|
||||
createForm.long_context_pricing_enabled = true;
|
||||
createForm.force_openai_fast = false;
|
||||
createForm.free_openai_fast = false;
|
||||
createForm.model_pricing = [];
|
||||
createForm.web_search_price_per_call = null;
|
||||
createForm.search_price_per_1k = null;
|
||||
@@ -6081,6 +6142,10 @@ const handleCreateGroup = async () => {
|
||||
createForm.platform,
|
||||
createForm.force_openai_fast,
|
||||
),
|
||||
free_openai_fast: normalizeGroupOpenAIFast(
|
||||
createForm.platform,
|
||||
createForm.free_openai_fast,
|
||||
),
|
||||
model_pricing: groupPricingToAPI(
|
||||
createForm.model_pricing,
|
||||
createForm.platform,
|
||||
@@ -6210,6 +6275,7 @@ const handleEdit = async (group: AdminGroup) => {
|
||||
editForm.long_context_pricing_enabled =
|
||||
group.long_context_pricing_enabled ?? true;
|
||||
editForm.force_openai_fast = group.force_openai_fast ?? false;
|
||||
editForm.free_openai_fast = group.free_openai_fast ?? false;
|
||||
editForm.model_pricing = groupPricingFromAPI(group.model_pricing);
|
||||
editForm.allow_image_generation = group.allow_image_generation ?? false;
|
||||
editForm.allow_batch_image_generation =
|
||||
@@ -6321,6 +6387,7 @@ const closeEditModal = () => {
|
||||
editForm.video_model_prices = createVideoModelPricesForm();
|
||||
editForm.long_context_pricing_enabled = true;
|
||||
editForm.force_openai_fast = false;
|
||||
editForm.free_openai_fast = false;
|
||||
editForm.model_pricing = [];
|
||||
editForm.web_search_price_per_call = null;
|
||||
editForm.search_price_per_1k = null;
|
||||
@@ -6358,6 +6425,10 @@ const handleUpdateGroup = async () => {
|
||||
editForm.platform,
|
||||
editForm.force_openai_fast,
|
||||
),
|
||||
free_openai_fast: normalizeGroupOpenAIFast(
|
||||
editForm.platform,
|
||||
editForm.free_openai_fast,
|
||||
),
|
||||
model_pricing: groupPricingToAPI(
|
||||
editForm.model_pricing,
|
||||
editForm.platform,
|
||||
|
||||
@@ -26,11 +26,15 @@ describe("groupsOpenAIFast", () => {
|
||||
title: expect.any(String),
|
||||
force: expect.any(String),
|
||||
hint: expect.stringContaining("service_tier=priority"),
|
||||
free: expect.any(String),
|
||||
freeHint: expect.stringContaining("Standard"),
|
||||
});
|
||||
expect(en.groups.openaiFast).toMatchObject({
|
||||
title: expect.any(String),
|
||||
force: expect.any(String),
|
||||
hint: expect.stringContaining("service_tier=priority"),
|
||||
free: expect.any(String),
|
||||
freeHint: expect.stringContaining("Standard"),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user