Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
beceb45d
Unverified
Commit
beceb45d
authored
Feb 18, 2026
by
Wesley Liddick
Committed by
GitHub
Feb 18, 2026
Browse files
Merge pull request #591 from miraserver/main
feat: add Cache TTL Override per account
parents
9450edf4
3d1f03c2
Changes
25
Show whitespace changes
Inline
Side-by-side
backend/cmd/server/VERSION
View file @
beceb45d
0.1.76
\ No newline at end of file
0.1.83
\ No newline at end of file
backend/ent/migrate/schema.go
View file @
beceb45d
...
...
@@ -650,6 +650,7 @@ var (
{
Name
:
"ip_address"
,
Type
:
field
.
TypeString
,
Nullable
:
true
,
Size
:
45
},
{
Name
:
"image_count"
,
Type
:
field
.
TypeInt
,
Default
:
0
},
{
Name
:
"image_size"
,
Type
:
field
.
TypeString
,
Nullable
:
true
,
Size
:
10
},
{
Name
:
"cache_ttl_overridden"
,
Type
:
field
.
TypeBool
,
Default
:
false
},
{
Name
:
"created_at"
,
Type
:
field
.
TypeTime
,
SchemaType
:
map
[
string
]
string
{
"postgres"
:
"timestamptz"
}},
{
Name
:
"api_key_id"
,
Type
:
field
.
TypeInt64
},
{
Name
:
"account_id"
,
Type
:
field
.
TypeInt64
},
...
...
@@ -665,31 +666,31 @@ var (
ForeignKeys
:
[]
*
schema
.
ForeignKey
{
{
Symbol
:
"usage_logs_api_keys_usage_logs"
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
6
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
7
]},
RefColumns
:
[]
*
schema
.
Column
{
APIKeysColumns
[
0
]},
OnDelete
:
schema
.
NoAction
,
},
{
Symbol
:
"usage_logs_accounts_usage_logs"
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
7
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
8
]},
RefColumns
:
[]
*
schema
.
Column
{
AccountsColumns
[
0
]},
OnDelete
:
schema
.
NoAction
,
},
{
Symbol
:
"usage_logs_groups_usage_logs"
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
8
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
9
]},
RefColumns
:
[]
*
schema
.
Column
{
GroupsColumns
[
0
]},
OnDelete
:
schema
.
SetNull
,
},
{
Symbol
:
"usage_logs_users_usage_logs"
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
29
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
30
]},
RefColumns
:
[]
*
schema
.
Column
{
UsersColumns
[
0
]},
OnDelete
:
schema
.
NoAction
,
},
{
Symbol
:
"usage_logs_user_subscriptions_usage_logs"
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
0
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
1
]},
RefColumns
:
[]
*
schema
.
Column
{
UserSubscriptionsColumns
[
0
]},
OnDelete
:
schema
.
SetNull
,
},
...
...
@@ -698,32 +699,32 @@ var (
{
Name
:
"usagelog_user_id"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
29
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
30
]},
},
{
Name
:
"usagelog_api_key_id"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
6
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
7
]},
},
{
Name
:
"usagelog_account_id"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
7
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
8
]},
},
{
Name
:
"usagelog_group_id"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
8
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
9
]},
},
{
Name
:
"usagelog_subscription_id"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
0
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
3
1
]},
},
{
Name
:
"usagelog_created_at"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
5
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
6
]},
},
{
Name
:
"usagelog_model"
,
...
...
@@ -738,12 +739,12 @@ var (
{
Name
:
"usagelog_user_id_created_at"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
29
],
UsageLogsColumns
[
2
5
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
30
],
UsageLogsColumns
[
2
6
]},
},
{
Name
:
"usagelog_api_key_id_created_at"
,
Unique
:
false
,
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
6
],
UsageLogsColumns
[
2
5
]},
Columns
:
[]
*
schema
.
Column
{
UsageLogsColumns
[
2
7
],
UsageLogsColumns
[
2
6
]},
},
},
}
...
...
backend/ent/mutation.go
View file @
beceb45d
...
...
@@ -15061,6 +15061,7 @@ type UsageLogMutation struct {
image_count *int
addimage_count *int
image_size *string
cache_ttl_overridden *bool
created_at *time.Time
clearedFields map[string]struct{}
user *int64
...
...
@@ -16687,6 +16688,42 @@ func (m *UsageLogMutation) ResetImageSize() {
delete(m.clearedFields, usagelog.FieldImageSize)
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func (m *UsageLogMutation) SetCacheTTLOverridden(b bool) {
m.cache_ttl_overridden = &b
}
// CacheTTLOverridden returns the value of the "cache_ttl_overridden" field in the mutation.
func (m *UsageLogMutation) CacheTTLOverridden() (r bool, exists bool) {
v := m.cache_ttl_overridden
if v == nil {
return
}
return *v, true
}
// OldCacheTTLOverridden returns the old "cache_ttl_overridden" field's value of the UsageLog entity.
// If the UsageLog 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 *UsageLogMutation) OldCacheTTLOverridden(ctx context.Context) (v bool, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldCacheTTLOverridden is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldCacheTTLOverridden requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldCacheTTLOverridden: %w", err)
}
return oldValue.CacheTTLOverridden, nil
}
// ResetCacheTTLOverridden resets all changes to the "cache_ttl_overridden" field.
func (m *UsageLogMutation) ResetCacheTTLOverridden() {
m.cache_ttl_overridden = nil
}
// SetCreatedAt sets the "created_at" field.
func (m *UsageLogMutation) SetCreatedAt(t time.Time) {
m.created_at = &t
...
...
@@ -16892,7 +16929,7 @@ func (m *UsageLogMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
func (m *UsageLogMutation) Fields() []string {
fields := make([]string, 0, 3
0
)
fields := make([]string, 0, 3
1
)
if m.user != nil {
fields = append(fields, usagelog.FieldUserID)
}
...
...
@@ -16980,6 +17017,9 @@ func (m *UsageLogMutation) Fields() []string {
if m.image_size != nil {
fields = append(fields, usagelog.FieldImageSize)
}
if m.cache_ttl_overridden != nil {
fields = append(fields, usagelog.FieldCacheTTLOverridden)
}
if m.created_at != nil {
fields = append(fields, usagelog.FieldCreatedAt)
}
...
...
@@ -17049,6 +17089,8 @@ func (m *UsageLogMutation) Field(name string) (ent.Value, bool) {
return m.ImageCount()
case usagelog.FieldImageSize:
return m.ImageSize()
case usagelog.FieldCacheTTLOverridden:
return m.CacheTTLOverridden()
case usagelog.FieldCreatedAt:
return m.CreatedAt()
}
...
...
@@ -17118,6 +17160,8 @@ func (m *UsageLogMutation) OldField(ctx context.Context, name string) (ent.Value
return m.OldImageCount(ctx)
case usagelog.FieldImageSize:
return m.OldImageSize(ctx)
case usagelog.FieldCacheTTLOverridden:
return m.OldCacheTTLOverridden(ctx)
case usagelog.FieldCreatedAt:
return m.OldCreatedAt(ctx)
}
...
...
@@ -17332,6 +17376,13 @@ func (m *UsageLogMutation) SetField(name string, value ent.Value) error {
}
m.SetImageSize(v)
return nil
case usagelog.FieldCacheTTLOverridden:
v, ok := value.(bool)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetCacheTTLOverridden(v)
return nil
case usagelog.FieldCreatedAt:
v, ok := value.(time.Time)
if !ok {
...
...
@@ -17745,6 +17796,9 @@ func (m *UsageLogMutation) ResetField(name string) error {
case usagelog.FieldImageSize:
m.ResetImageSize()
return nil
case usagelog.FieldCacheTTLOverridden:
m.ResetCacheTTLOverridden()
return nil
case usagelog.FieldCreatedAt:
m.ResetCreatedAt()
return nil
...
...
backend/ent/runtime/runtime.go
View file @
beceb45d
...
...
@@ -779,8 +779,12 @@ func init() {
usagelogDescImageSize
:=
usagelogFields
[
28
]
.
Descriptor
()
// usagelog.ImageSizeValidator is a validator for the "image_size" field. It is called by the builders before save.
usagelog
.
ImageSizeValidator
=
usagelogDescImageSize
.
Validators
[
0
]
.
(
func
(
string
)
error
)
// usagelogDescCacheTTLOverridden is the schema descriptor for cache_ttl_overridden field.
usagelogDescCacheTTLOverridden
:=
usagelogFields
[
29
]
.
Descriptor
()
// usagelog.DefaultCacheTTLOverridden holds the default value on creation for the cache_ttl_overridden field.
usagelog
.
DefaultCacheTTLOverridden
=
usagelogDescCacheTTLOverridden
.
Default
.
(
bool
)
// usagelogDescCreatedAt is the schema descriptor for created_at field.
usagelogDescCreatedAt
:=
usagelogFields
[
29
]
.
Descriptor
()
usagelogDescCreatedAt
:=
usagelogFields
[
30
]
.
Descriptor
()
// usagelog.DefaultCreatedAt holds the default value on creation for the created_at field.
usagelog
.
DefaultCreatedAt
=
usagelogDescCreatedAt
.
Default
.
(
func
()
time
.
Time
)
userMixin
:=
schema
.
User
{}
.
Mixin
()
...
...
backend/ent/schema/usage_log.go
View file @
beceb45d
...
...
@@ -119,6 +119,10 @@ func (UsageLog) Fields() []ent.Field {
Optional
()
.
Nillable
(),
// Cache TTL Override 标记(管理员强制替换了缓存 TTL 计费)
field
.
Bool
(
"cache_ttl_overridden"
)
.
Default
(
false
),
// 时间戳(只有 created_at,日志不可修改)
field
.
Time
(
"created_at"
)
.
Default
(
time
.
Now
)
.
...
...
backend/ent/usagelog.go
View file @
beceb45d
...
...
@@ -80,6 +80,8 @@ type UsageLog struct {
ImageCount
int
`json:"image_count,omitempty"`
// ImageSize holds the value of the "image_size" field.
ImageSize
*
string
`json:"image_size,omitempty"`
// CacheTTLOverridden holds the value of the "cache_ttl_overridden" field.
CacheTTLOverridden
bool
`json:"cache_ttl_overridden,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt
time
.
Time
`json:"created_at,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
...
...
@@ -165,7 +167,7 @@ func (*UsageLog) scanValues(columns []string) ([]any, error) {
values
:=
make
([]
any
,
len
(
columns
))
for
i
:=
range
columns
{
switch
columns
[
i
]
{
case
usagelog
.
FieldStream
:
case
usagelog
.
FieldStream
,
usagelog
.
FieldCacheTTLOverridden
:
values
[
i
]
=
new
(
sql
.
NullBool
)
case
usagelog
.
FieldInputCost
,
usagelog
.
FieldOutputCost
,
usagelog
.
FieldCacheCreationCost
,
usagelog
.
FieldCacheReadCost
,
usagelog
.
FieldTotalCost
,
usagelog
.
FieldActualCost
,
usagelog
.
FieldRateMultiplier
,
usagelog
.
FieldAccountRateMultiplier
:
values
[
i
]
=
new
(
sql
.
NullFloat64
)
...
...
@@ -378,6 +380,12 @@ func (_m *UsageLog) assignValues(columns []string, values []any) error {
_m
.
ImageSize
=
new
(
string
)
*
_m
.
ImageSize
=
value
.
String
}
case
usagelog
.
FieldCacheTTLOverridden
:
if
value
,
ok
:=
values
[
i
]
.
(
*
sql
.
NullBool
);
!
ok
{
return
fmt
.
Errorf
(
"unexpected type %T for field cache_ttl_overridden"
,
values
[
i
])
}
else
if
value
.
Valid
{
_m
.
CacheTTLOverridden
=
value
.
Bool
}
case
usagelog
.
FieldCreatedAt
:
if
value
,
ok
:=
values
[
i
]
.
(
*
sql
.
NullTime
);
!
ok
{
return
fmt
.
Errorf
(
"unexpected type %T for field created_at"
,
values
[
i
])
...
...
@@ -548,6 +556,9 @@ func (_m *UsageLog) String() string {
builder
.
WriteString
(
*
v
)
}
builder
.
WriteString
(
", "
)
builder
.
WriteString
(
"cache_ttl_overridden="
)
builder
.
WriteString
(
fmt
.
Sprintf
(
"%v"
,
_m
.
CacheTTLOverridden
))
builder
.
WriteString
(
", "
)
builder
.
WriteString
(
"created_at="
)
builder
.
WriteString
(
_m
.
CreatedAt
.
Format
(
time
.
ANSIC
))
builder
.
WriteByte
(
')'
)
...
...
backend/ent/usagelog/usagelog.go
View file @
beceb45d
...
...
@@ -72,6 +72,8 @@ const (
FieldImageCount
=
"image_count"
// FieldImageSize holds the string denoting the image_size field in the database.
FieldImageSize
=
"image_size"
// FieldCacheTTLOverridden holds the string denoting the cache_ttl_overridden field in the database.
FieldCacheTTLOverridden
=
"cache_ttl_overridden"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt
=
"created_at"
// EdgeUser holds the string denoting the user edge name in mutations.
...
...
@@ -155,6 +157,7 @@ var Columns = []string{
FieldIPAddress
,
FieldImageCount
,
FieldImageSize
,
FieldCacheTTLOverridden
,
FieldCreatedAt
,
}
...
...
@@ -211,6 +214,8 @@ var (
DefaultImageCount
int
// ImageSizeValidator is a validator for the "image_size" field. It is called by the builders before save.
ImageSizeValidator
func
(
string
)
error
// DefaultCacheTTLOverridden holds the default value on creation for the "cache_ttl_overridden" field.
DefaultCacheTTLOverridden
bool
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt
func
()
time
.
Time
)
...
...
@@ -368,6 +373,11 @@ func ByImageSize(opts ...sql.OrderTermOption) OrderOption {
return
sql
.
OrderByField
(
FieldImageSize
,
opts
...
)
.
ToFunc
()
}
// ByCacheTTLOverridden orders the results by the cache_ttl_overridden field.
func
ByCacheTTLOverridden
(
opts
...
sql
.
OrderTermOption
)
OrderOption
{
return
sql
.
OrderByField
(
FieldCacheTTLOverridden
,
opts
...
)
.
ToFunc
()
}
// ByCreatedAt orders the results by the created_at field.
func
ByCreatedAt
(
opts
...
sql
.
OrderTermOption
)
OrderOption
{
return
sql
.
OrderByField
(
FieldCreatedAt
,
opts
...
)
.
ToFunc
()
...
...
backend/ent/usagelog/where.go
View file @
beceb45d
...
...
@@ -200,6 +200,11 @@ func ImageSize(v string) predicate.UsageLog {
return
predicate
.
UsageLog
(
sql
.
FieldEQ
(
FieldImageSize
,
v
))
}
// CacheTTLOverridden applies equality check predicate on the "cache_ttl_overridden" field. It's identical to CacheTTLOverriddenEQ.
func
CacheTTLOverridden
(
v
bool
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldEQ
(
FieldCacheTTLOverridden
,
v
))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func
CreatedAt
(
v
time
.
Time
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldEQ
(
FieldCreatedAt
,
v
))
...
...
@@ -1440,6 +1445,16 @@ func ImageSizeContainsFold(v string) predicate.UsageLog {
return
predicate
.
UsageLog
(
sql
.
FieldContainsFold
(
FieldImageSize
,
v
))
}
// CacheTTLOverriddenEQ applies the EQ predicate on the "cache_ttl_overridden" field.
func
CacheTTLOverriddenEQ
(
v
bool
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldEQ
(
FieldCacheTTLOverridden
,
v
))
}
// CacheTTLOverriddenNEQ applies the NEQ predicate on the "cache_ttl_overridden" field.
func
CacheTTLOverriddenNEQ
(
v
bool
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldNEQ
(
FieldCacheTTLOverridden
,
v
))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func
CreatedAtEQ
(
v
time
.
Time
)
predicate
.
UsageLog
{
return
predicate
.
UsageLog
(
sql
.
FieldEQ
(
FieldCreatedAt
,
v
))
...
...
backend/ent/usagelog_create.go
View file @
beceb45d
...
...
@@ -393,6 +393,20 @@ func (_c *UsageLogCreate) SetNillableImageSize(v *string) *UsageLogCreate {
return
_c
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func
(
_c
*
UsageLogCreate
)
SetCacheTTLOverridden
(
v
bool
)
*
UsageLogCreate
{
_c
.
mutation
.
SetCacheTTLOverridden
(
v
)
return
_c
}
// SetNillableCacheTTLOverridden sets the "cache_ttl_overridden" field if the given value is not nil.
func
(
_c
*
UsageLogCreate
)
SetNillableCacheTTLOverridden
(
v
*
bool
)
*
UsageLogCreate
{
if
v
!=
nil
{
_c
.
SetCacheTTLOverridden
(
*
v
)
}
return
_c
}
// SetCreatedAt sets the "created_at" field.
func
(
_c
*
UsageLogCreate
)
SetCreatedAt
(
v
time
.
Time
)
*
UsageLogCreate
{
_c
.
mutation
.
SetCreatedAt
(
v
)
...
...
@@ -531,6 +545,10 @@ func (_c *UsageLogCreate) defaults() {
v
:=
usagelog
.
DefaultImageCount
_c
.
mutation
.
SetImageCount
(
v
)
}
if
_
,
ok
:=
_c
.
mutation
.
CacheTTLOverridden
();
!
ok
{
v
:=
usagelog
.
DefaultCacheTTLOverridden
_c
.
mutation
.
SetCacheTTLOverridden
(
v
)
}
if
_
,
ok
:=
_c
.
mutation
.
CreatedAt
();
!
ok
{
v
:=
usagelog
.
DefaultCreatedAt
()
_c
.
mutation
.
SetCreatedAt
(
v
)
...
...
@@ -627,6 +645,9 @@ func (_c *UsageLogCreate) check() error {
return
&
ValidationError
{
Name
:
"image_size"
,
err
:
fmt
.
Errorf
(
`ent: validator failed for field "UsageLog.image_size": %w`
,
err
)}
}
}
if
_
,
ok
:=
_c
.
mutation
.
CacheTTLOverridden
();
!
ok
{
return
&
ValidationError
{
Name
:
"cache_ttl_overridden"
,
err
:
errors
.
New
(
`ent: missing required field "UsageLog.cache_ttl_overridden"`
)}
}
if
_
,
ok
:=
_c
.
mutation
.
CreatedAt
();
!
ok
{
return
&
ValidationError
{
Name
:
"created_at"
,
err
:
errors
.
New
(
`ent: missing required field "UsageLog.created_at"`
)}
}
...
...
@@ -762,6 +783,10 @@ func (_c *UsageLogCreate) createSpec() (*UsageLog, *sqlgraph.CreateSpec) {
_spec
.
SetField
(
usagelog
.
FieldImageSize
,
field
.
TypeString
,
value
)
_node
.
ImageSize
=
&
value
}
if
value
,
ok
:=
_c
.
mutation
.
CacheTTLOverridden
();
ok
{
_spec
.
SetField
(
usagelog
.
FieldCacheTTLOverridden
,
field
.
TypeBool
,
value
)
_node
.
CacheTTLOverridden
=
value
}
if
value
,
ok
:=
_c
.
mutation
.
CreatedAt
();
ok
{
_spec
.
SetField
(
usagelog
.
FieldCreatedAt
,
field
.
TypeTime
,
value
)
_node
.
CreatedAt
=
value
...
...
@@ -1407,6 +1432,18 @@ func (u *UsageLogUpsert) ClearImageSize() *UsageLogUpsert {
return
u
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func
(
u
*
UsageLogUpsert
)
SetCacheTTLOverridden
(
v
bool
)
*
UsageLogUpsert
{
u
.
Set
(
usagelog
.
FieldCacheTTLOverridden
,
v
)
return
u
}
// UpdateCacheTTLOverridden sets the "cache_ttl_overridden" field to the value that was provided on create.
func
(
u
*
UsageLogUpsert
)
UpdateCacheTTLOverridden
()
*
UsageLogUpsert
{
u
.
SetExcluded
(
usagelog
.
FieldCacheTTLOverridden
)
return
u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
...
...
@@ -2040,6 +2077,20 @@ func (u *UsageLogUpsertOne) ClearImageSize() *UsageLogUpsertOne {
})
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func
(
u
*
UsageLogUpsertOne
)
SetCacheTTLOverridden
(
v
bool
)
*
UsageLogUpsertOne
{
return
u
.
Update
(
func
(
s
*
UsageLogUpsert
)
{
s
.
SetCacheTTLOverridden
(
v
)
})
}
// UpdateCacheTTLOverridden sets the "cache_ttl_overridden" field to the value that was provided on create.
func
(
u
*
UsageLogUpsertOne
)
UpdateCacheTTLOverridden
()
*
UsageLogUpsertOne
{
return
u
.
Update
(
func
(
s
*
UsageLogUpsert
)
{
s
.
UpdateCacheTTLOverridden
()
})
}
// Exec executes the query.
func
(
u
*
UsageLogUpsertOne
)
Exec
(
ctx
context
.
Context
)
error
{
if
len
(
u
.
create
.
conflict
)
==
0
{
...
...
@@ -2839,6 +2890,20 @@ func (u *UsageLogUpsertBulk) ClearImageSize() *UsageLogUpsertBulk {
})
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func
(
u
*
UsageLogUpsertBulk
)
SetCacheTTLOverridden
(
v
bool
)
*
UsageLogUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UsageLogUpsert
)
{
s
.
SetCacheTTLOverridden
(
v
)
})
}
// UpdateCacheTTLOverridden sets the "cache_ttl_overridden" field to the value that was provided on create.
func
(
u
*
UsageLogUpsertBulk
)
UpdateCacheTTLOverridden
()
*
UsageLogUpsertBulk
{
return
u
.
Update
(
func
(
s
*
UsageLogUpsert
)
{
s
.
UpdateCacheTTLOverridden
()
})
}
// Exec executes the query.
func
(
u
*
UsageLogUpsertBulk
)
Exec
(
ctx
context
.
Context
)
error
{
if
u
.
create
.
err
!=
nil
{
...
...
backend/ent/usagelog_update.go
View file @
beceb45d
...
...
@@ -612,6 +612,20 @@ func (_u *UsageLogUpdate) ClearImageSize() *UsageLogUpdate {
return
_u
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func
(
_u
*
UsageLogUpdate
)
SetCacheTTLOverridden
(
v
bool
)
*
UsageLogUpdate
{
_u
.
mutation
.
SetCacheTTLOverridden
(
v
)
return
_u
}
// SetNillableCacheTTLOverridden sets the "cache_ttl_overridden" field if the given value is not nil.
func
(
_u
*
UsageLogUpdate
)
SetNillableCacheTTLOverridden
(
v
*
bool
)
*
UsageLogUpdate
{
if
v
!=
nil
{
_u
.
SetCacheTTLOverridden
(
*
v
)
}
return
_u
}
// SetUser sets the "user" edge to the User entity.
func
(
_u
*
UsageLogUpdate
)
SetUser
(
v
*
User
)
*
UsageLogUpdate
{
return
_u
.
SetUserID
(
v
.
ID
)
...
...
@@ -894,6 +908,9 @@ func (_u *UsageLogUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if
_u
.
mutation
.
ImageSizeCleared
()
{
_spec
.
ClearField
(
usagelog
.
FieldImageSize
,
field
.
TypeString
)
}
if
value
,
ok
:=
_u
.
mutation
.
CacheTTLOverridden
();
ok
{
_spec
.
SetField
(
usagelog
.
FieldCacheTTLOverridden
,
field
.
TypeBool
,
value
)
}
if
_u
.
mutation
.
UserCleared
()
{
edge
:=
&
sqlgraph
.
EdgeSpec
{
Rel
:
sqlgraph
.
M2O
,
...
...
@@ -1639,6 +1656,20 @@ func (_u *UsageLogUpdateOne) ClearImageSize() *UsageLogUpdateOne {
return
_u
}
// SetCacheTTLOverridden sets the "cache_ttl_overridden" field.
func
(
_u
*
UsageLogUpdateOne
)
SetCacheTTLOverridden
(
v
bool
)
*
UsageLogUpdateOne
{
_u
.
mutation
.
SetCacheTTLOverridden
(
v
)
return
_u
}
// SetNillableCacheTTLOverridden sets the "cache_ttl_overridden" field if the given value is not nil.
func
(
_u
*
UsageLogUpdateOne
)
SetNillableCacheTTLOverridden
(
v
*
bool
)
*
UsageLogUpdateOne
{
if
v
!=
nil
{
_u
.
SetCacheTTLOverridden
(
*
v
)
}
return
_u
}
// SetUser sets the "user" edge to the User entity.
func
(
_u
*
UsageLogUpdateOne
)
SetUser
(
v
*
User
)
*
UsageLogUpdateOne
{
return
_u
.
SetUserID
(
v
.
ID
)
...
...
@@ -1951,6 +1982,9 @@ func (_u *UsageLogUpdateOne) sqlSave(ctx context.Context) (_node *UsageLog, err
if
_u
.
mutation
.
ImageSizeCleared
()
{
_spec
.
ClearField
(
usagelog
.
FieldImageSize
,
field
.
TypeString
)
}
if
value
,
ok
:=
_u
.
mutation
.
CacheTTLOverridden
();
ok
{
_spec
.
SetField
(
usagelog
.
FieldCacheTTLOverridden
,
field
.
TypeBool
,
value
)
}
if
_u
.
mutation
.
UserCleared
()
{
edge
:=
&
sqlgraph
.
EdgeSpec
{
Rel
:
sqlgraph
.
M2O
,
...
...
backend/internal/handler/dto/mappers.go
View file @
beceb45d
...
...
@@ -211,6 +211,13 @@ func AccountFromServiceShallow(a *service.Account) *Account {
enabled
:=
true
out
.
EnableSessionIDMasking
=
&
enabled
}
// 缓存 TTL 强制替换
if
a
.
IsCacheTTLOverrideEnabled
()
{
enabled
:=
true
out
.
CacheTTLOverrideEnabled
=
&
enabled
target
:=
a
.
GetCacheTTLOverrideTarget
()
out
.
CacheTTLOverrideTarget
=
&
target
}
}
return
out
...
...
@@ -398,6 +405,7 @@ func usageLogFromServiceUser(l *service.UsageLog) UsageLog {
ImageCount
:
l
.
ImageCount
,
ImageSize
:
l
.
ImageSize
,
UserAgent
:
l
.
UserAgent
,
CacheTTLOverridden
:
l
.
CacheTTLOverridden
,
CreatedAt
:
l
.
CreatedAt
,
User
:
UserFromServiceShallow
(
l
.
User
),
APIKey
:
APIKeyFromService
(
l
.
APIKey
),
...
...
backend/internal/handler/dto/types.go
View file @
beceb45d
...
...
@@ -150,6 +150,11 @@ type Account struct {
// 从 extra 字段提取,方便前端显示和编辑
EnableSessionIDMasking
*
bool
`json:"session_id_masking_enabled,omitempty"`
// 缓存 TTL 强制替换(仅 Anthropic OAuth/SetupToken 账号有效)
// 启用后将所有 cache creation tokens 归入指定的 TTL 类型计费
CacheTTLOverrideEnabled
*
bool
`json:"cache_ttl_override_enabled,omitempty"`
CacheTTLOverrideTarget
*
string
`json:"cache_ttl_override_target,omitempty"`
Proxy
*
Proxy
`json:"proxy,omitempty"`
AccountGroups
[]
AccountGroup
`json:"account_groups,omitempty"`
...
...
@@ -273,6 +278,9 @@ type UsageLog struct {
// User-Agent
UserAgent
*
string
`json:"user_agent"`
// Cache TTL Override 标记
CacheTTLOverridden
bool
`json:"cache_ttl_overridden"`
CreatedAt
time
.
Time
`json:"created_at"`
User
*
User
`json:"user,omitempty"`
...
...
backend/internal/repository/usage_log_repo.go
View file @
beceb45d
...
...
@@ -22,7 +22,7 @@ import (
"github.com/lib/pq"
)
const
usageLogSelectColumns
=
"id, user_id, api_key_id, account_id, request_id, model, group_id, subscription_id, input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens, cache_creation_5m_tokens, cache_creation_1h_tokens, input_cost, output_cost, cache_creation_cost, cache_read_cost, total_cost, actual_cost, rate_multiplier, account_rate_multiplier, billing_type, stream, duration_ms, first_token_ms, user_agent, ip_address, image_count, image_size, reasoning_effort, created_at"
const
usageLogSelectColumns
=
"id, user_id, api_key_id, account_id, request_id, model, group_id, subscription_id, input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens, cache_creation_5m_tokens, cache_creation_1h_tokens, input_cost, output_cost, cache_creation_cost, cache_read_cost, total_cost, actual_cost, rate_multiplier, account_rate_multiplier, billing_type, stream, duration_ms, first_token_ms, user_agent, ip_address, image_count, image_size, reasoning_effort,
cache_ttl_overridden,
created_at"
type
usageLogRepository
struct
{
client
*
dbent
.
Client
...
...
@@ -115,6 +115,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
image_count,
image_size,
reasoning_effort,
cache_ttl_overridden,
created_at
) VALUES (
$1, $2, $3, $4, $5,
...
...
@@ -122,7 +123,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
$8, $9, $10, $11,
$12, $13,
$14, $15, $16, $17, $18, $19,
$20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31
$20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31
, $32
)
ON CONFLICT (request_id, api_key_id) DO NOTHING
RETURNING id, created_at
...
...
@@ -173,6 +174,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
log
.
ImageCount
,
imageSize
,
reasoningEffort
,
log
.
CacheTTLOverridden
,
createdAt
,
}
if
err
:=
scanSingleRow
(
ctx
,
sqlq
,
query
,
args
,
&
log
.
ID
,
&
log
.
CreatedAt
);
err
!=
nil
{
...
...
@@ -2195,6 +2197,7 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
imageCount
int
imageSize
sql
.
NullString
reasoningEffort
sql
.
NullString
cacheTTLOverridden
bool
createdAt
time
.
Time
)
...
...
@@ -2230,6 +2233,7 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
&
imageCount
,
&
imageSize
,
&
reasoningEffort
,
&
cacheTTLOverridden
,
&
createdAt
,
);
err
!=
nil
{
return
nil
,
err
...
...
@@ -2258,6 +2262,7 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
BillingType
:
int8
(
billingType
),
Stream
:
stream
,
ImageCount
:
imageCount
,
CacheTTLOverridden
:
cacheTTLOverridden
,
CreatedAt
:
createdAt
,
}
...
...
backend/internal/server/api_contract_test.go
View file @
beceb45d
...
...
@@ -401,6 +401,7 @@ func TestAPIContracts(t *testing.T) {
"first_token_ms": 50,
"image_count": 0,
"image_size": null,
"cache_ttl_overridden": false,
"created_at": "2025-01-02T03:04:05Z",
"user_agent": null
}
...
...
backend/internal/service/account.go
View file @
beceb45d
...
...
@@ -752,6 +752,38 @@ func (a *Account) IsSessionIDMaskingEnabled() bool {
return
false
}
// IsCacheTTLOverrideEnabled 检查是否启用缓存 TTL 强制替换
// 仅适用于 Anthropic OAuth/SetupToken 类型账号
// 启用后将所有 cache creation tokens 归入指定的 TTL 类型(5m 或 1h)
func
(
a
*
Account
)
IsCacheTTLOverrideEnabled
()
bool
{
if
!
a
.
IsAnthropicOAuthOrSetupToken
()
{
return
false
}
if
a
.
Extra
==
nil
{
return
false
}
if
v
,
ok
:=
a
.
Extra
[
"cache_ttl_override_enabled"
];
ok
{
if
enabled
,
ok
:=
v
.
(
bool
);
ok
{
return
enabled
}
}
return
false
}
// GetCacheTTLOverrideTarget 获取缓存 TTL 强制替换的目标类型
// 返回 "5m" 或 "1h",默认 "5m"
func
(
a
*
Account
)
GetCacheTTLOverrideTarget
()
string
{
if
a
.
Extra
==
nil
{
return
"5m"
}
if
v
,
ok
:=
a
.
Extra
[
"cache_ttl_override_target"
];
ok
{
if
target
,
ok
:=
v
.
(
string
);
ok
&&
(
target
==
"5m"
||
target
==
"1h"
)
{
return
target
}
}
return
"5m"
}
// GetWindowCostLimit 获取 5h 窗口费用阈值(美元)
// 返回 0 表示未启用
func
(
a
*
Account
)
GetWindowCostLimit
()
float64
{
...
...
backend/internal/service/gateway_service.go
View file @
beceb45d
...
...
@@ -4276,6 +4276,23 @@ func (s *GatewayService) handleStreamingResponse(ctx context.Context, resp *http
}
}
// Cache TTL Override: 重写 SSE 事件中的 cache_creation 分类
if
account
.
IsCacheTTLOverrideEnabled
()
{
overrideTarget
:=
account
.
GetCacheTTLOverrideTarget
()
if
eventType
==
"message_start"
{
if
msg
,
ok
:=
event
[
"message"
]
.
(
map
[
string
]
any
);
ok
{
if
u
,
ok
:=
msg
[
"usage"
]
.
(
map
[
string
]
any
);
ok
{
rewriteCacheCreationJSON
(
u
,
overrideTarget
)
}
}
}
if
eventType
==
"message_delta"
{
if
u
,
ok
:=
event
[
"usage"
]
.
(
map
[
string
]
any
);
ok
{
rewriteCacheCreationJSON
(
u
,
overrideTarget
)
}
}
}
if
needModelReplace
{
if
msg
,
ok
:=
event
[
"message"
]
.
(
map
[
string
]
any
);
ok
{
if
model
,
ok
:=
msg
[
"model"
]
.
(
string
);
ok
&&
model
==
mappedModel
{
...
...
@@ -4450,6 +4467,58 @@ func (s *GatewayService) parseSSEUsage(data string, usage *ClaudeUsage) {
}
}
// applyCacheTTLOverride 将所有 cache creation tokens 归入指定的 TTL 类型。
// target 为 "5m" 或 "1h"。返回 true 表示发生了变更。
func
applyCacheTTLOverride
(
usage
*
ClaudeUsage
,
target
string
)
bool
{
// Fallback: 如果只有聚合字段但无 5m/1h 明细,将聚合字段归入 5m 默认类别
if
usage
.
CacheCreation5mTokens
==
0
&&
usage
.
CacheCreation1hTokens
==
0
&&
usage
.
CacheCreationInputTokens
>
0
{
usage
.
CacheCreation5mTokens
=
usage
.
CacheCreationInputTokens
}
total
:=
usage
.
CacheCreation5mTokens
+
usage
.
CacheCreation1hTokens
if
total
==
0
{
return
false
}
switch
target
{
case
"1h"
:
if
usage
.
CacheCreation1hTokens
==
total
{
return
false
// 已经全是 1h
}
usage
.
CacheCreation1hTokens
=
total
usage
.
CacheCreation5mTokens
=
0
default
:
// "5m"
if
usage
.
CacheCreation5mTokens
==
total
{
return
false
// 已经全是 5m
}
usage
.
CacheCreation5mTokens
=
total
usage
.
CacheCreation1hTokens
=
0
}
return
true
}
// rewriteCacheCreationJSON 在 JSON usage 对象中重写 cache_creation 嵌套对象的 TTL 分类。
// usageObj 是 usage JSON 对象(map[string]any)。
func
rewriteCacheCreationJSON
(
usageObj
map
[
string
]
any
,
target
string
)
{
ccObj
,
ok
:=
usageObj
[
"cache_creation"
]
.
(
map
[
string
]
any
)
if
!
ok
{
return
}
v5m
,
_
:=
ccObj
[
"ephemeral_5m_input_tokens"
]
.
(
float64
)
v1h
,
_
:=
ccObj
[
"ephemeral_1h_input_tokens"
]
.
(
float64
)
total
:=
v5m
+
v1h
if
total
==
0
{
return
}
switch
target
{
case
"1h"
:
ccObj
[
"ephemeral_1h_input_tokens"
]
=
total
ccObj
[
"ephemeral_5m_input_tokens"
]
=
float64
(
0
)
default
:
// "5m"
ccObj
[
"ephemeral_5m_input_tokens"
]
=
total
ccObj
[
"ephemeral_1h_input_tokens"
]
=
float64
(
0
)
}
}
func
(
s
*
GatewayService
)
handleNonStreamingResponse
(
ctx
context
.
Context
,
resp
*
http
.
Response
,
c
*
gin
.
Context
,
account
*
Account
,
originalModel
,
mappedModel
string
)
(
*
ClaudeUsage
,
error
)
{
// 更新5h窗口状态
s
.
rateLimitService
.
UpdateSessionWindow
(
ctx
,
account
,
resp
.
Header
)
...
...
@@ -4486,6 +4555,20 @@ func (s *GatewayService) handleNonStreamingResponse(ctx context.Context, resp *h
}
}
// Cache TTL Override: 重写 non-streaming 响应中的 cache_creation 分类
if
account
.
IsCacheTTLOverrideEnabled
()
{
overrideTarget
:=
account
.
GetCacheTTLOverrideTarget
()
if
applyCacheTTLOverride
(
&
response
.
Usage
,
overrideTarget
)
{
// 同步更新 body JSON 中的嵌套 cache_creation 对象
if
newBody
,
err
:=
sjson
.
SetBytes
(
body
,
"usage.cache_creation.ephemeral_5m_input_tokens"
,
response
.
Usage
.
CacheCreation5mTokens
);
err
==
nil
{
body
=
newBody
}
if
newBody
,
err
:=
sjson
.
SetBytes
(
body
,
"usage.cache_creation.ephemeral_1h_input_tokens"
,
response
.
Usage
.
CacheCreation1hTokens
);
err
==
nil
{
body
=
newBody
}
}
}
// 如果有模型映射,替换响应中的model字段
if
originalModel
!=
mappedModel
{
body
=
s
.
replaceModelInResponseBody
(
body
,
mappedModel
,
originalModel
)
...
...
@@ -4562,6 +4645,13 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
result
.
Usage
.
InputTokens
=
0
}
// Cache TTL Override: 确保计费时 token 分类与账号设置一致
cacheTTLOverridden
:=
false
if
account
.
IsCacheTTLOverrideEnabled
()
{
applyCacheTTLOverride
(
&
result
.
Usage
,
account
.
GetCacheTTLOverrideTarget
())
cacheTTLOverridden
=
(
result
.
Usage
.
CacheCreation5mTokens
+
result
.
Usage
.
CacheCreation1hTokens
)
>
0
}
// 获取费率倍数(优先级:用户专属 > 分组默认 > 系统默认)
multiplier
:=
s
.
cfg
.
Default
.
RateMultiplier
if
apiKey
.
GroupID
!=
nil
&&
apiKey
.
Group
!=
nil
{
...
...
@@ -4647,6 +4737,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
FirstTokenMs
:
result
.
FirstTokenMs
,
ImageCount
:
result
.
ImageCount
,
ImageSize
:
imageSize
,
CacheTTLOverridden
:
cacheTTLOverridden
,
CreatedAt
:
time
.
Now
(),
}
...
...
@@ -4747,6 +4838,13 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
result
.
Usage
.
InputTokens
=
0
}
// Cache TTL Override: 确保计费时 token 分类与账号设置一致
cacheTTLOverridden
:=
false
if
account
.
IsCacheTTLOverrideEnabled
()
{
applyCacheTTLOverride
(
&
result
.
Usage
,
account
.
GetCacheTTLOverrideTarget
())
cacheTTLOverridden
=
(
result
.
Usage
.
CacheCreation5mTokens
+
result
.
Usage
.
CacheCreation1hTokens
)
>
0
}
// 获取费率倍数(优先级:用户专属 > 分组默认 > 系统默认)
multiplier
:=
s
.
cfg
.
Default
.
RateMultiplier
if
apiKey
.
GroupID
!=
nil
&&
apiKey
.
Group
!=
nil
{
...
...
@@ -4832,6 +4930,7 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
FirstTokenMs
:
result
.
FirstTokenMs
,
ImageCount
:
result
.
ImageCount
,
ImageSize
:
imageSize
,
CacheTTLOverridden
:
cacheTTLOverridden
,
CreatedAt
:
time
.
Now
(),
}
...
...
backend/internal/service/usage_log.go
View file @
beceb45d
...
...
@@ -46,6 +46,9 @@ type UsageLog struct {
UserAgent
*
string
IPAddress
*
string
// Cache TTL Override 标记(管理员强制替换了缓存 TTL 计费)
CacheTTLOverridden
bool
// 图片生成字段
ImageCount
int
ImageSize
*
string
...
...
backend/migrations/055_add_cache_ttl_overridden.sql
0 → 100644
View file @
beceb45d
-- Add cache_ttl_overridden flag to usage_logs for tracking cache TTL override per account.
ALTER
TABLE
usage_logs
ADD
COLUMN
IF
NOT
EXISTS
cache_ttl_overridden
BOOLEAN
NOT
NULL
DEFAULT
FALSE
;
frontend/src/components/account/CreateAccountModal.vue
View file @
beceb45d
...
...
@@ -1527,6 +1527,46 @@
<
/button
>
<
/div
>
<
/div
>
<!--
Cache
TTL
Override
-->
<
div
class
=
"
rounded-lg border border-gray-200 p-4 dark:border-dark-600
"
>
<
div
class
=
"
flex items-center justify-between
"
>
<
div
>
<
label
class
=
"
input-label mb-0
"
>
{{
t
(
'
admin.accounts.quotaControl.cacheTTLOverride.label
'
)
}}
<
/label
>
<
p
class
=
"
mt-1 text-xs text-gray-500 dark:text-gray-400
"
>
{{
t
(
'
admin.accounts.quotaControl.cacheTTLOverride.hint
'
)
}}
<
/p
>
<
/div
>
<
button
type
=
"
button
"
@
click
=
"
cacheTTLOverrideEnabled = !cacheTTLOverrideEnabled
"
:
class
=
"
[
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
cacheTTLOverrideEnabled ? 'bg-primary-600' : 'bg-gray-200 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',
cacheTTLOverrideEnabled ? 'translate-x-5' : 'translate-x-0'
]
"
/>
<
/button
>
<
/div
>
<
div
v
-
if
=
"
cacheTTLOverrideEnabled
"
class
=
"
mt-3
"
>
<
label
class
=
"
input-label text-xs
"
>
{{
t
(
'
admin.accounts.quotaControl.cacheTTLOverride.target
'
)
}}
<
/label
>
<
select
v
-
model
=
"
cacheTTLOverrideTarget
"
class
=
"
mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 dark:border-dark-500 dark:bg-dark-700 dark:text-white
"
>
<
option
value
=
"
5m
"
>
5
m
<
/option
>
<
option
value
=
"
1h
"
>
1
h
<
/option
>
<
/select
>
<
p
class
=
"
mt-1 text-xs text-gray-500 dark:text-gray-400
"
>
{{
t
(
'
admin.accounts.quotaControl.cacheTTLOverride.targetHint
'
)
}}
<
/p
>
<
/div
>
<
/div
>
<
/div
>
<
div
>
...
...
@@ -2146,6 +2186,8 @@ const maxSessions = ref<number | null>(null)
const
sessionIdleTimeout
=
ref
<
number
|
null
>
(
null
)
const
tlsFingerprintEnabled
=
ref
(
false
)
const
sessionIdMaskingEnabled
=
ref
(
false
)
const
cacheTTLOverrideEnabled
=
ref
(
false
)
const
cacheTTLOverrideTarget
=
ref
<
string
>
(
'
5m
'
)
// Gemini tier selection (used as fallback when auto-detection is unavailable/fails)
const
geminiTierGoogleOne
=
ref
<
'
google_one_free
'
|
'
google_ai_pro
'
|
'
google_ai_ultra
'
>
(
'
google_one_free
'
)
...
...
@@ -2597,6 +2639,8 @@ const resetForm = () => {
sessionIdleTimeout
.
value
=
null
tlsFingerprintEnabled
.
value
=
false
sessionIdMaskingEnabled
.
value
=
false
cacheTTLOverrideEnabled
.
value
=
false
cacheTTLOverrideTarget
.
value
=
'
5m
'
antigravityAccountType
.
value
=
'
oauth
'
upstreamBaseUrl
.
value
=
''
upstreamApiKey
.
value
=
''
...
...
@@ -3174,6 +3218,12 @@ const handleAnthropicExchange = async (authCode: string) => {
extra
.
session_id_masking_enabled
=
true
}
// Add cache TTL override settings
if
(
cacheTTLOverrideEnabled
.
value
)
{
extra
.
cache_ttl_override_enabled
=
true
extra
.
cache_ttl_override_target
=
cacheTTLOverrideTarget
.
value
}
const
credentials
=
{
...
tokenInfo
,
...(
interceptWarmupRequests
.
value
?
{
intercept_warmup_requests
:
true
}
:
{
}
)
...
...
@@ -3267,6 +3317,12 @@ const handleCookieAuth = async (sessionKey: string) => {
extra
.
session_id_masking_enabled
=
true
}
// Add cache TTL override settings
if
(
cacheTTLOverrideEnabled
.
value
)
{
extra
.
cache_ttl_override_enabled
=
true
extra
.
cache_ttl_override_target
=
cacheTTLOverrideTarget
.
value
}
const
accountName
=
keys
.
length
>
1
?
`${form.name
}
#${i + 1
}
`
:
form
.
name
// Merge interceptWarmupRequests into credentials
...
...
frontend/src/components/account/EditAccountModal.vue
View file @
beceb45d
...
...
@@ -904,6 +904,46 @@
<
/button
>
<
/div
>
<
/div
>
<!--
Cache
TTL
Override
-->
<
div
class
=
"
rounded-lg border border-gray-200 p-4 dark:border-dark-600
"
>
<
div
class
=
"
flex items-center justify-between
"
>
<
div
>
<
label
class
=
"
input-label mb-0
"
>
{{
t
(
'
admin.accounts.quotaControl.cacheTTLOverride.label
'
)
}}
<
/label
>
<
p
class
=
"
mt-1 text-xs text-gray-500 dark:text-gray-400
"
>
{{
t
(
'
admin.accounts.quotaControl.cacheTTLOverride.hint
'
)
}}
<
/p
>
<
/div
>
<
button
type
=
"
button
"
@
click
=
"
cacheTTLOverrideEnabled = !cacheTTLOverrideEnabled
"
:
class
=
"
[
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
cacheTTLOverrideEnabled ? 'bg-primary-600' : 'bg-gray-200 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',
cacheTTLOverrideEnabled ? 'translate-x-5' : 'translate-x-0'
]
"
/>
<
/button
>
<
/div
>
<
div
v
-
if
=
"
cacheTTLOverrideEnabled
"
class
=
"
mt-3
"
>
<
label
class
=
"
input-label text-xs
"
>
{{
t
(
'
admin.accounts.quotaControl.cacheTTLOverride.target
'
)
}}
<
/label
>
<
select
v
-
model
=
"
cacheTTLOverrideTarget
"
class
=
"
mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 dark:border-dark-500 dark:bg-dark-700 dark:text-white
"
>
<
option
value
=
"
5m
"
>
5
m
<
/option
>
<
option
value
=
"
1h
"
>
1
h
<
/option
>
<
/select
>
<
p
class
=
"
mt-1 text-xs text-gray-500 dark:text-gray-400
"
>
{{
t
(
'
admin.accounts.quotaControl.cacheTTLOverride.targetHint
'
)
}}
<
/p
>
<
/div
>
<
/div
>
<
/div
>
<
div
class
=
"
border-t border-gray-200 pt-4 dark:border-dark-600
"
>
...
...
@@ -1102,6 +1142,8 @@ const maxSessions = ref<number | null>(null)
const
sessionIdleTimeout
=
ref
<
number
|
null
>
(
null
)
const
tlsFingerprintEnabled
=
ref
(
false
)
const
sessionIdMaskingEnabled
=
ref
(
false
)
const
cacheTTLOverrideEnabled
=
ref
(
false
)
const
cacheTTLOverrideTarget
=
ref
<
string
>
(
'
5m
'
)
// Computed: current preset mappings based on platform
const
presetMappings
=
computed
(()
=>
getPresetMappingsByPlatform
(
props
.
account
?.
platform
||
'
anthropic
'
))
...
...
@@ -1489,6 +1531,8 @@ function loadQuotaControlSettings(account: Account) {
sessionIdleTimeout
.
value
=
null
tlsFingerprintEnabled
.
value
=
false
sessionIdMaskingEnabled
.
value
=
false
cacheTTLOverrideEnabled
.
value
=
false
cacheTTLOverrideTarget
.
value
=
'
5m
'
// Only applies to Anthropic OAuth/SetupToken accounts
if
(
account
.
platform
!==
'
anthropic
'
||
(
account
.
type
!==
'
oauth
'
&&
account
.
type
!==
'
setup-token
'
))
{
...
...
@@ -1517,6 +1561,12 @@ function loadQuotaControlSettings(account: Account) {
if
(
account
.
session_id_masking_enabled
===
true
)
{
sessionIdMaskingEnabled
.
value
=
true
}
// Load cache TTL override setting
if
(
account
.
cache_ttl_override_enabled
===
true
)
{
cacheTTLOverrideEnabled
.
value
=
true
cacheTTLOverrideTarget
.
value
=
account
.
cache_ttl_override_target
||
'
5m
'
}
}
function
formatTempUnschedKeywords
(
value
:
unknown
)
{
...
...
@@ -1723,6 +1773,15 @@ const handleSubmit = async () => {
delete
newExtra
.
session_id_masking_enabled
}
// Cache TTL override setting
if
(
cacheTTLOverrideEnabled
.
value
)
{
newExtra
.
cache_ttl_override_enabled
=
true
newExtra
.
cache_ttl_override_target
=
cacheTTLOverrideTarget
.
value
}
else
{
delete
newExtra
.
cache_ttl_override_enabled
delete
newExtra
.
cache_ttl_override_target
}
updatePayload
.
extra
=
newExtra
}
...
...
Prev
1
2
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment