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
df4c0adf
Unverified
Commit
df4c0adf
authored
Feb 03, 2026
by
Wesley Liddick
Committed by
GitHub
Feb 03, 2026
Browse files
Merge pull request #463 from DuckyProject/feat/usage-records-codex-reasoning-effort
feat(usage): add reasoning effort column
parents
7cbe4afd
53ee6383
Changes
13
Hide whitespace changes
Inline
Side-by-side
backend/internal/handler/dto/mappers.go
View file @
df4c0adf
...
@@ -366,6 +366,7 @@ func usageLogFromServiceUser(l *service.UsageLog) UsageLog {
...
@@ -366,6 +366,7 @@ func usageLogFromServiceUser(l *service.UsageLog) UsageLog {
AccountID
:
l
.
AccountID
,
AccountID
:
l
.
AccountID
,
RequestID
:
l
.
RequestID
,
RequestID
:
l
.
RequestID
,
Model
:
l
.
Model
,
Model
:
l
.
Model
,
ReasoningEffort
:
l
.
ReasoningEffort
,
GroupID
:
l
.
GroupID
,
GroupID
:
l
.
GroupID
,
SubscriptionID
:
l
.
SubscriptionID
,
SubscriptionID
:
l
.
SubscriptionID
,
InputTokens
:
l
.
InputTokens
,
InputTokens
:
l
.
InputTokens
,
...
...
backend/internal/handler/dto/types.go
View file @
df4c0adf
...
@@ -222,6 +222,9 @@ type UsageLog struct {
...
@@ -222,6 +222,9 @@ type UsageLog struct {
AccountID
int64
`json:"account_id"`
AccountID
int64
`json:"account_id"`
RequestID
string
`json:"request_id"`
RequestID
string
`json:"request_id"`
Model
string
`json:"model"`
Model
string
`json:"model"`
// ReasoningEffort is the request's reasoning effort level (OpenAI Responses API).
// nil means not provided / not applicable.
ReasoningEffort
*
string
`json:"reasoning_effort,omitempty"`
GroupID
*
int64
`json:"group_id"`
GroupID
*
int64
`json:"group_id"`
SubscriptionID
*
int64
`json:"subscription_id"`
SubscriptionID
*
int64
`json:"subscription_id"`
...
...
backend/internal/repository/usage_log_repo.go
View file @
df4c0adf
...
@@ -22,7 +22,7 @@ import (
...
@@ -22,7 +22,7 @@ import (
"github.com/lib/pq"
"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, 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,
created_at"
type
usageLogRepository
struct
{
type
usageLogRepository
struct
{
client
*
dbent
.
Client
client
*
dbent
.
Client
...
@@ -111,21 +111,22 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
...
@@ -111,21 +111,22 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
duration_ms,
duration_ms,
first_token_ms,
first_token_ms,
user_agent,
user_agent,
ip_address,
ip_address,
image_count,
image_count,
image_size,
image_size,
created_at
reasoning_effort,
) VALUES (
created_at
$1, $2, $3, $4, $5,
) VALUES (
$6, $7,
$1, $2, $3, $4, $5,
$8, $9, $10, $11,
$6, $7,
$12, $13,
$8, $9, $10, $11,
$14, $15, $16, $17, $18, $19,
$12, $13,
$20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30
$14, $15, $16, $17, $18, $19,
)
$20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31
ON CONFLICT (request_id, api_key_id) DO NOTHING
)
RETURNING id, created_at
ON CONFLICT (request_id, api_key_id) DO NOTHING
`
RETURNING id, created_at
`
groupID
:=
nullInt64
(
log
.
GroupID
)
groupID
:=
nullInt64
(
log
.
GroupID
)
subscriptionID
:=
nullInt64
(
log
.
SubscriptionID
)
subscriptionID
:=
nullInt64
(
log
.
SubscriptionID
)
...
@@ -134,6 +135,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
...
@@ -134,6 +135,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
userAgent
:=
nullString
(
log
.
UserAgent
)
userAgent
:=
nullString
(
log
.
UserAgent
)
ipAddress
:=
nullString
(
log
.
IPAddress
)
ipAddress
:=
nullString
(
log
.
IPAddress
)
imageSize
:=
nullString
(
log
.
ImageSize
)
imageSize
:=
nullString
(
log
.
ImageSize
)
reasoningEffort
:=
nullString
(
log
.
ReasoningEffort
)
var
requestIDArg
any
var
requestIDArg
any
if
requestID
!=
""
{
if
requestID
!=
""
{
...
@@ -170,6 +172,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
...
@@ -170,6 +172,7 @@ func (r *usageLogRepository) Create(ctx context.Context, log *service.UsageLog)
ipAddress
,
ipAddress
,
log
.
ImageCount
,
log
.
ImageCount
,
imageSize
,
imageSize
,
reasoningEffort
,
createdAt
,
createdAt
,
}
}
if
err
:=
scanSingleRow
(
ctx
,
sqlq
,
query
,
args
,
&
log
.
ID
,
&
log
.
CreatedAt
);
err
!=
nil
{
if
err
:=
scanSingleRow
(
ctx
,
sqlq
,
query
,
args
,
&
log
.
ID
,
&
log
.
CreatedAt
);
err
!=
nil
{
...
@@ -2090,6 +2093,7 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
...
@@ -2090,6 +2093,7 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
ipAddress
sql
.
NullString
ipAddress
sql
.
NullString
imageCount
int
imageCount
int
imageSize
sql
.
NullString
imageSize
sql
.
NullString
reasoningEffort
sql
.
NullString
createdAt
time
.
Time
createdAt
time
.
Time
)
)
...
@@ -2124,6 +2128,7 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
...
@@ -2124,6 +2128,7 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
&
ipAddress
,
&
ipAddress
,
&
imageCount
,
&
imageCount
,
&
imageSize
,
&
imageSize
,
&
reasoningEffort
,
&
createdAt
,
&
createdAt
,
);
err
!=
nil
{
);
err
!=
nil
{
return
nil
,
err
return
nil
,
err
...
@@ -2183,6 +2188,9 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
...
@@ -2183,6 +2188,9 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e
if
imageSize
.
Valid
{
if
imageSize
.
Valid
{
log
.
ImageSize
=
&
imageSize
.
String
log
.
ImageSize
=
&
imageSize
.
String
}
}
if
reasoningEffort
.
Valid
{
log
.
ReasoningEffort
=
&
reasoningEffort
.
String
}
return
log
,
nil
return
log
,
nil
}
}
...
...
backend/internal/service/openai_gateway_service.go
View file @
df4c0adf
...
@@ -156,12 +156,15 @@ type OpenAIUsage struct {
...
@@ -156,12 +156,15 @@ type OpenAIUsage struct {
// OpenAIForwardResult represents the result of forwarding
// OpenAIForwardResult represents the result of forwarding
type
OpenAIForwardResult
struct
{
type
OpenAIForwardResult
struct
{
RequestID
string
RequestID
string
Usage
OpenAIUsage
Usage
OpenAIUsage
Model
string
Model
string
Stream
bool
// ReasoningEffort is extracted from request body (reasoning.effort) or derived from model suffix.
Duration
time
.
Duration
// Stored for usage records display; nil means not provided / not applicable.
FirstTokenMs
*
int
ReasoningEffort
*
string
Stream
bool
Duration
time
.
Duration
FirstTokenMs
*
int
}
}
// OpenAIGatewayService handles OpenAI API gateway operations
// OpenAIGatewayService handles OpenAI API gateway operations
...
@@ -958,13 +961,16 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
...
@@ -958,13 +961,16 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
}
}
}
}
reasoningEffort
:=
extractOpenAIReasoningEffort
(
reqBody
,
originalModel
)
return
&
OpenAIForwardResult
{
return
&
OpenAIForwardResult
{
RequestID
:
resp
.
Header
.
Get
(
"x-request-id"
),
RequestID
:
resp
.
Header
.
Get
(
"x-request-id"
),
Usage
:
*
usage
,
Usage
:
*
usage
,
Model
:
originalModel
,
Model
:
originalModel
,
Stream
:
reqStream
,
ReasoningEffort
:
reasoningEffort
,
Duration
:
time
.
Since
(
startTime
),
Stream
:
reqStream
,
FirstTokenMs
:
firstTokenMs
,
Duration
:
time
.
Since
(
startTime
),
FirstTokenMs
:
firstTokenMs
,
},
nil
},
nil
}
}
...
@@ -1728,6 +1734,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
...
@@ -1728,6 +1734,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
AccountID
:
account
.
ID
,
AccountID
:
account
.
ID
,
RequestID
:
result
.
RequestID
,
RequestID
:
result
.
RequestID
,
Model
:
result
.
Model
,
Model
:
result
.
Model
,
ReasoningEffort
:
result
.
ReasoningEffort
,
InputTokens
:
actualInputTokens
,
InputTokens
:
actualInputTokens
,
OutputTokens
:
result
.
Usage
.
OutputTokens
,
OutputTokens
:
result
.
Usage
.
OutputTokens
,
CacheCreationTokens
:
result
.
Usage
.
CacheCreationInputTokens
,
CacheCreationTokens
:
result
.
Usage
.
CacheCreationInputTokens
,
...
@@ -1922,3 +1929,86 @@ func (s *OpenAIGatewayService) updateCodexUsageSnapshot(ctx context.Context, acc
...
@@ -1922,3 +1929,86 @@ func (s *OpenAIGatewayService) updateCodexUsageSnapshot(ctx context.Context, acc
_
=
s
.
accountRepo
.
UpdateExtra
(
updateCtx
,
accountID
,
updates
)
_
=
s
.
accountRepo
.
UpdateExtra
(
updateCtx
,
accountID
,
updates
)
}()
}()
}
}
func
getOpenAIReasoningEffortFromReqBody
(
reqBody
map
[
string
]
any
)
(
value
string
,
present
bool
)
{
if
reqBody
==
nil
{
return
""
,
false
}
// Primary: reasoning.effort
if
reasoning
,
ok
:=
reqBody
[
"reasoning"
]
.
(
map
[
string
]
any
);
ok
{
if
effort
,
ok
:=
reasoning
[
"effort"
]
.
(
string
);
ok
{
return
normalizeOpenAIReasoningEffort
(
effort
),
true
}
}
// Fallback: some clients may use a flat field.
if
effort
,
ok
:=
reqBody
[
"reasoning_effort"
]
.
(
string
);
ok
{
return
normalizeOpenAIReasoningEffort
(
effort
),
true
}
return
""
,
false
}
func
deriveOpenAIReasoningEffortFromModel
(
model
string
)
string
{
if
strings
.
TrimSpace
(
model
)
==
""
{
return
""
}
modelID
:=
strings
.
TrimSpace
(
model
)
if
strings
.
Contains
(
modelID
,
"/"
)
{
parts
:=
strings
.
Split
(
modelID
,
"/"
)
modelID
=
parts
[
len
(
parts
)
-
1
]
}
parts
:=
strings
.
FieldsFunc
(
strings
.
ToLower
(
modelID
),
func
(
r
rune
)
bool
{
switch
r
{
case
'-'
,
'_'
,
' '
:
return
true
default
:
return
false
}
})
if
len
(
parts
)
==
0
{
return
""
}
return
normalizeOpenAIReasoningEffort
(
parts
[
len
(
parts
)
-
1
])
}
func
extractOpenAIReasoningEffort
(
reqBody
map
[
string
]
any
,
requestedModel
string
)
*
string
{
if
value
,
present
:=
getOpenAIReasoningEffortFromReqBody
(
reqBody
);
present
{
if
value
==
""
{
return
nil
}
return
&
value
}
value
:=
deriveOpenAIReasoningEffortFromModel
(
requestedModel
)
if
value
==
""
{
return
nil
}
return
&
value
}
func
normalizeOpenAIReasoningEffort
(
raw
string
)
string
{
value
:=
strings
.
ToLower
(
strings
.
TrimSpace
(
raw
))
if
value
==
""
{
return
""
}
// Normalize separators for "x-high"/"x_high" variants.
value
=
strings
.
NewReplacer
(
"-"
,
""
,
"_"
,
""
,
" "
,
""
)
.
Replace
(
value
)
switch
value
{
case
"none"
,
"minimal"
:
return
""
case
"low"
,
"medium"
,
"high"
:
return
value
case
"xhigh"
,
"extrahigh"
:
return
"xhigh"
default
:
// Only store known effort levels for now to keep UI consistent.
return
""
}
}
backend/internal/service/usage_log.go
View file @
df4c0adf
...
@@ -14,6 +14,9 @@ type UsageLog struct {
...
@@ -14,6 +14,9 @@ type UsageLog struct {
AccountID
int64
AccountID
int64
RequestID
string
RequestID
string
Model
string
Model
string
// ReasoningEffort is the request's reasoning effort level (OpenAI Responses API),
// e.g. "low" / "medium" / "high" / "xhigh". Nil means not provided / not applicable.
ReasoningEffort
*
string
GroupID
*
int64
GroupID
*
int64
SubscriptionID
*
int64
SubscriptionID
*
int64
...
...
backend/migrations/046_add_usage_log_reasoning_effort.sql
0 → 100644
View file @
df4c0adf
-- Add reasoning_effort field to usage_logs for OpenAI/Codex requests.
-- This stores the request's reasoning effort level (e.g. low/medium/high/xhigh).
ALTER
TABLE
usage_logs
ADD
COLUMN
IF
NOT
EXISTS
reasoning_effort
VARCHAR
(
20
);
frontend/src/components/admin/usage/UsageTable.vue
View file @
df4c0adf
...
@@ -21,6 +21,12 @@
...
@@ -21,6 +21,12 @@
<span
class=
"font-medium text-gray-900 dark:text-white"
>
{{
value
}}
</span>
<span
class=
"font-medium text-gray-900 dark:text-white"
>
{{
value
}}
</span>
</
template
>
</
template
>
<
template
#cell-reasoning_effort=
"{ row }"
>
<span
class=
"text-sm text-gray-900 dark:text-white"
>
{{
formatReasoningEffort
(
row
.
reasoning_effort
)
}}
</span>
</
template
>
<
template
#cell-group=
"{ row }"
>
<
template
#cell-group=
"{ row }"
>
<span
v-if=
"row.group"
class=
"inline-flex items-center rounded px-2 py-0.5 text-xs font-medium bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200"
>
<span
v-if=
"row.group"
class=
"inline-flex items-center rounded px-2 py-0.5 text-xs font-medium bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200"
>
{{
row
.
group
.
name
}}
{{
row
.
group
.
name
}}
...
@@ -232,14 +238,14 @@
...
@@ -232,14 +238,14 @@
</Teleport>
</Teleport>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
ref
,
computed
}
from
'
vue
'
import
{
ref
,
computed
}
from
'
vue
'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
formatDateTime
}
from
'
@/utils/format
'
import
{
formatDateTime
,
formatReasoningEffort
}
from
'
@/utils/format
'
import
DataTable
from
'
@/components/common/DataTable.vue
'
import
DataTable
from
'
@/components/common/DataTable.vue
'
import
EmptyState
from
'
@/components/common/EmptyState.vue
'
import
EmptyState
from
'
@/components/common/EmptyState.vue
'
import
Icon
from
'
@/components/icons/Icon.vue
'
import
Icon
from
'
@/components/icons/Icon.vue
'
import
type
{
AdminUsageLog
}
from
'
@/types
'
import
type
{
AdminUsageLog
}
from
'
@/types
'
defineProps
([
'
data
'
,
'
loading
'
])
defineProps
([
'
data
'
,
'
loading
'
])
const
{
t
}
=
useI18n
()
const
{
t
}
=
useI18n
()
...
@@ -259,6 +265,7 @@ const cols = computed(() => [
...
@@ -259,6 +265,7 @@ const cols = computed(() => [
{
key
:
'
api_key
'
,
label
:
t
(
'
usage.apiKeyFilter
'
),
sortable
:
false
},
{
key
:
'
api_key
'
,
label
:
t
(
'
usage.apiKeyFilter
'
),
sortable
:
false
},
{
key
:
'
account
'
,
label
:
t
(
'
admin.usage.account
'
),
sortable
:
false
},
{
key
:
'
account
'
,
label
:
t
(
'
admin.usage.account
'
),
sortable
:
false
},
{
key
:
'
model
'
,
label
:
t
(
'
usage.model
'
),
sortable
:
true
},
{
key
:
'
model
'
,
label
:
t
(
'
usage.model
'
),
sortable
:
true
},
{
key
:
'
reasoning_effort
'
,
label
:
t
(
'
usage.reasoningEffort
'
),
sortable
:
false
},
{
key
:
'
group
'
,
label
:
t
(
'
admin.usage.group
'
),
sortable
:
false
},
{
key
:
'
group
'
,
label
:
t
(
'
admin.usage.group
'
),
sortable
:
false
},
{
key
:
'
stream
'
,
label
:
t
(
'
usage.type
'
),
sortable
:
false
},
{
key
:
'
stream
'
,
label
:
t
(
'
usage.type
'
),
sortable
:
false
},
{
key
:
'
tokens
'
,
label
:
t
(
'
usage.tokens
'
),
sortable
:
false
},
{
key
:
'
tokens
'
,
label
:
t
(
'
usage.tokens
'
),
sortable
:
false
},
...
...
frontend/src/i18n/locales/en.ts
View file @
df4c0adf
...
@@ -502,6 +502,7 @@ export default {
...
@@ -502,6 +502,7 @@ export default {
exporting
:
'
Exporting...
'
,
exporting
:
'
Exporting...
'
,
preparingExport
:
'
Preparing export...
'
,
preparingExport
:
'
Preparing export...
'
,
model
:
'
Model
'
,
model
:
'
Model
'
,
reasoningEffort
:
'
Reasoning Effort
'
,
type
:
'
Type
'
,
type
:
'
Type
'
,
tokens
:
'
Tokens
'
,
tokens
:
'
Tokens
'
,
cost
:
'
Cost
'
,
cost
:
'
Cost
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
df4c0adf
...
@@ -498,6 +498,7 @@ export default {
...
@@ -498,6 +498,7 @@ export default {
exporting
:
'
导出中...
'
,
exporting
:
'
导出中...
'
,
preparingExport
:
'
正在准备导出...
'
,
preparingExport
:
'
正在准备导出...
'
,
model
:
'
模型
'
,
model
:
'
模型
'
,
reasoningEffort
:
'
推理强度
'
,
type
:
'
类型
'
,
type
:
'
类型
'
,
tokens
:
'
Token
'
,
tokens
:
'
Token
'
,
cost
:
'
费用
'
,
cost
:
'
费用
'
,
...
...
frontend/src/types/index.ts
View file @
df4c0adf
...
@@ -712,6 +712,7 @@ export interface UsageLog {
...
@@ -712,6 +712,7 @@ export interface UsageLog {
account_id
:
number
|
null
account_id
:
number
|
null
request_id
:
string
request_id
:
string
model
:
string
model
:
string
reasoning_effort
?:
string
|
null
group_id
:
number
|
null
group_id
:
number
|
null
subscription_id
:
number
|
null
subscription_id
:
number
|
null
...
...
frontend/src/utils/format.ts
View file @
df4c0adf
...
@@ -174,6 +174,35 @@ export function parseDateTimeLocalInput(value: string): number | null {
...
@@ -174,6 +174,35 @@ export function parseDateTimeLocalInput(value: string): number | null {
return
Math
.
floor
(
date
.
getTime
()
/
1000
)
return
Math
.
floor
(
date
.
getTime
()
/
1000
)
}
}
/**
* 格式化 OpenAI reasoning effort(用于使用记录展示)
* @param effort 原始 effort(如 "low" / "medium" / "high" / "xhigh")
* @returns 格式化后的字符串(Low / Medium / High / Xhigh),无值返回 "-"
*/
export
function
formatReasoningEffort
(
effort
:
string
|
null
|
undefined
):
string
{
const
raw
=
(
effort
??
''
).
toString
().
trim
()
if
(
!
raw
)
return
'
-
'
const
normalized
=
raw
.
toLowerCase
().
replace
(
/
[
-_
\s]
/g
,
''
)
switch
(
normalized
)
{
case
'
low
'
:
return
'
Low
'
case
'
medium
'
:
return
'
Medium
'
case
'
high
'
:
return
'
High
'
case
'
xhigh
'
:
case
'
extrahigh
'
:
return
'
Xhigh
'
case
'
none
'
:
case
'
minimal
'
:
return
'
-
'
default
:
// best-effort: Title-case first letter
return
raw
.
length
>
1
?
raw
[
0
].
toUpperCase
()
+
raw
.
slice
(
1
)
:
raw
.
toUpperCase
()
}
}
/**
/**
* 格式化时间(只显示时分)
* 格式化时间(只显示时分)
* @param date 日期字符串或 Date 对象
* @param date 日期字符串或 Date 对象
...
...
frontend/src/views/admin/UsageView.vue
View file @
df4c0adf
...
@@ -35,12 +35,13 @@
...
@@ -35,12 +35,13 @@
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
ref
,
reactive
,
computed
,
onMounted
,
onUnmounted
}
from
'
vue
'
import
{
ref
,
reactive
,
computed
,
onMounted
,
onUnmounted
}
from
'
vue
'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
saveAs
}
from
'
file-saver
'
import
{
saveAs
}
from
'
file-saver
'
import
{
useAppStore
}
from
'
@/stores/app
'
;
import
{
adminAPI
}
from
'
@/api/admin
'
;
import
{
adminUsageAPI
}
from
'
@/api/admin/usage
'
import
{
useAppStore
}
from
'
@/stores/app
'
;
import
{
adminAPI
}
from
'
@/api/admin
'
;
import
{
adminUsageAPI
}
from
'
@/api/admin/usage
'
import
AppLayout
from
'
@/components/layout/AppLayout.vue
'
;
import
Pagination
from
'
@/components/common/Pagination.vue
'
;
import
Select
from
'
@/components/common/Select.vue
'
import
{
formatReasoningEffort
}
from
'
@/utils/format
'
import
UsageStatsCards
from
'
@/components/admin/usage/UsageStatsCards.vue
'
;
import
UsageFilters
from
'
@/components/admin/usage/UsageFilters.vue
'
import
AppLayout
from
'
@/components/layout/AppLayout.vue
'
;
import
Pagination
from
'
@/components/common/Pagination.vue
'
;
import
Select
from
'
@/components/common/Select.vue
'
import
UsageTable
from
'
@/components/admin/usage/UsageTable.vue
'
;
import
UsageExportProgress
from
'
@/components/admin/usage/UsageExportProgress.vue
'
import
UsageStatsCards
from
'
@/components/admin/usage/UsageStatsCards.vue
'
;
import
UsageFilters
from
'
@/components/admin/usage/UsageFilters.vue
'
import
UsageCleanupDialog
from
'
@/components/admin/usage/UsageCleanupDialog.vue
'
import
UsageTable
from
'
@/components/admin/usage/UsageTable.vue
'
;
import
UsageExportProgress
from
'
@/components/admin/usage/UsageExportProgress.vue
'
import
UsageCleanupDialog
from
'
@/components/admin/usage/UsageCleanupDialog.vue
'
import
ModelDistributionChart
from
'
@/components/charts/ModelDistributionChart.vue
'
;
import
TokenUsageTrend
from
'
@/components/charts/TokenUsageTrend.vue
'
import
ModelDistributionChart
from
'
@/components/charts/ModelDistributionChart.vue
'
;
import
TokenUsageTrend
from
'
@/components/charts/TokenUsageTrend.vue
'
import
type
{
AdminUsageLog
,
TrendDataPoint
,
ModelStat
}
from
'
@/types
'
;
import
type
{
AdminUsageStatsResponse
,
AdminUsageQueryParams
}
from
'
@/api/admin/usage
'
import
type
{
AdminUsageLog
,
TrendDataPoint
,
ModelStat
}
from
'
@/types
'
;
import
type
{
AdminUsageStatsResponse
,
AdminUsageQueryParams
}
from
'
@/api/admin/usage
'
...
@@ -104,7 +105,7 @@ const exportToExcel = async () => {
...
@@ -104,7 +105,7 @@ const exportToExcel = async () => {
const
XLSX
=
await
import
(
'
xlsx
'
)
const
XLSX
=
await
import
(
'
xlsx
'
)
const
headers
=
[
const
headers
=
[
t
(
'
usage.time
'
),
t
(
'
admin.usage.user
'
),
t
(
'
usage.apiKeyFilter
'
),
t
(
'
usage.time
'
),
t
(
'
admin.usage.user
'
),
t
(
'
usage.apiKeyFilter
'
),
t
(
'
admin.usage.account
'
),
t
(
'
usage.model
'
),
t
(
'
admin.usage.group
'
),
t
(
'
admin.usage.account
'
),
t
(
'
usage.model
'
),
t
(
'
usage.reasoningEffort
'
),
t
(
'
admin.usage.group
'
),
t
(
'
usage.type
'
),
t
(
'
usage.type
'
),
t
(
'
admin.usage.inputTokens
'
),
t
(
'
admin.usage.outputTokens
'
),
t
(
'
admin.usage.inputTokens
'
),
t
(
'
admin.usage.outputTokens
'
),
t
(
'
admin.usage.cacheReadTokens
'
),
t
(
'
admin.usage.cacheCreationTokens
'
),
t
(
'
admin.usage.cacheReadTokens
'
),
t
(
'
admin.usage.cacheCreationTokens
'
),
...
@@ -120,6 +121,7 @@ const exportToExcel = async () => {
...
@@ -120,6 +121,7 @@ const exportToExcel = async () => {
log
.
api_key
?.
name
||
''
,
log
.
api_key
?.
name
||
''
,
log
.
account
?.
name
||
''
,
log
.
account
?.
name
||
''
,
log
.
model
,
log
.
model
,
formatReasoningEffort
(
log
.
reasoning_effort
),
log
.
group
?.
name
||
''
,
log
.
group
?.
name
||
''
,
log
.
stream
?
t
(
'
usage.stream
'
)
:
t
(
'
usage.sync
'
),
log
.
stream
?
t
(
'
usage.stream
'
)
:
t
(
'
usage.sync
'
),
log
.
input_tokens
,
log
.
input_tokens
,
...
...
frontend/src/views/user/UsageView.vue
View file @
df4c0adf
...
@@ -157,6 +157,12 @@
...
@@ -157,6 +157,12 @@
<span
class=
"font-medium text-gray-900 dark:text-white"
>
{{
value
}}
</span>
<span
class=
"font-medium text-gray-900 dark:text-white"
>
{{
value
}}
</span>
</
template
>
</
template
>
<
template
#cell-reasoning_effort=
"{ row }"
>
<span
class=
"text-sm text-gray-900 dark:text-white"
>
{{
formatReasoningEffort
(
row
.
reasoning_effort
)
}}
</span>
</
template
>
<
template
#cell-stream=
"{ row }"
>
<
template
#cell-stream=
"{ row }"
>
<span
<span
class=
"inline-flex items-center rounded px-2 py-0.5 text-xs font-medium"
class=
"inline-flex items-center rounded px-2 py-0.5 text-xs font-medium"
...
@@ -438,12 +444,12 @@ import TablePageLayout from '@/components/layout/TablePageLayout.vue'
...
@@ -438,12 +444,12 @@ import TablePageLayout from '@/components/layout/TablePageLayout.vue'
import
DataTable
from
'
@/components/common/DataTable.vue
'
import
DataTable
from
'
@/components/common/DataTable.vue
'
import
Pagination
from
'
@/components/common/Pagination.vue
'
import
Pagination
from
'
@/components/common/Pagination.vue
'
import
EmptyState
from
'
@/components/common/EmptyState.vue
'
import
EmptyState
from
'
@/components/common/EmptyState.vue
'
import
Select
from
'
@/components/common/Select.vue
'
import
Select
from
'
@/components/common/Select.vue
'
import
DateRangePicker
from
'
@/components/common/DateRangePicker.vue
'
import
DateRangePicker
from
'
@/components/common/DateRangePicker.vue
'
import
Icon
from
'
@/components/icons/Icon.vue
'
import
Icon
from
'
@/components/icons/Icon.vue
'
import
type
{
UsageLog
,
ApiKey
,
UsageQueryParams
,
UsageStatsResponse
}
from
'
@/types
'
import
type
{
UsageLog
,
ApiKey
,
UsageQueryParams
,
UsageStatsResponse
}
from
'
@/types
'
import
type
{
Column
}
from
'
@/components/common/types
'
import
type
{
Column
}
from
'
@/components/common/types
'
import
{
formatDateTime
}
from
'
@/utils/format
'
import
{
formatDateTime
,
formatReasoningEffort
}
from
'
@/utils/format
'
const
{
t
}
=
useI18n
()
const
{
t
}
=
useI18n
()
const
appStore
=
useAppStore
()
const
appStore
=
useAppStore
()
...
@@ -466,6 +472,7 @@ const usageStats = ref<UsageStatsResponse | null>(null)
...
@@ -466,6 +472,7 @@ const usageStats = ref<UsageStatsResponse | null>(null)
const
columns
=
computed
<
Column
[]
>
(()
=>
[
const
columns
=
computed
<
Column
[]
>
(()
=>
[
{
key
:
'
api_key
'
,
label
:
t
(
'
usage.apiKeyFilter
'
),
sortable
:
false
},
{
key
:
'
api_key
'
,
label
:
t
(
'
usage.apiKeyFilter
'
),
sortable
:
false
},
{
key
:
'
model
'
,
label
:
t
(
'
usage.model
'
),
sortable
:
true
},
{
key
:
'
model
'
,
label
:
t
(
'
usage.model
'
),
sortable
:
true
},
{
key
:
'
reasoning_effort
'
,
label
:
t
(
'
usage.reasoningEffort
'
),
sortable
:
false
},
{
key
:
'
stream
'
,
label
:
t
(
'
usage.type
'
),
sortable
:
false
},
{
key
:
'
stream
'
,
label
:
t
(
'
usage.type
'
),
sortable
:
false
},
{
key
:
'
tokens
'
,
label
:
t
(
'
usage.tokens
'
),
sortable
:
false
},
{
key
:
'
tokens
'
,
label
:
t
(
'
usage.tokens
'
),
sortable
:
false
},
{
key
:
'
cost
'
,
label
:
t
(
'
usage.cost
'
),
sortable
:
false
},
{
key
:
'
cost
'
,
label
:
t
(
'
usage.cost
'
),
sortable
:
false
},
...
@@ -723,6 +730,7 @@ const exportToCSV = async () => {
...
@@ -723,6 +730,7 @@ const exportToCSV = async () => {
'
Time
'
,
'
Time
'
,
'
API Key Name
'
,
'
API Key Name
'
,
'
Model
'
,
'
Model
'
,
'
Reasoning Effort
'
,
'
Type
'
,
'
Type
'
,
'
Input Tokens
'
,
'
Input Tokens
'
,
'
Output Tokens
'
,
'
Output Tokens
'
,
...
@@ -739,6 +747,7 @@ const exportToCSV = async () => {
...
@@ -739,6 +747,7 @@ const exportToCSV = async () => {
log
.
created_at
,
log
.
created_at
,
log
.
api_key
?.
name
||
''
,
log
.
api_key
?.
name
||
''
,
log
.
model
,
log
.
model
,
formatReasoningEffort
(
log
.
reasoning_effort
),
log
.
stream
?
'
Stream
'
:
'
Sync
'
,
log
.
stream
?
'
Stream
'
:
'
Sync
'
,
log
.
input_tokens
,
log
.
input_tokens
,
log
.
output_tokens
,
log
.
output_tokens
,
...
...
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