"backend/vscode:/vscode.git/clone" did not exist on "0c4f1762c9ba98d91b2eb895a20eeeb48a77323a"
Commit 2a5ef6d3 authored by shaw's avatar shaw
Browse files

Merge PR #279: feat(计费): 账号计费倍率快照与账号口径费用统计

parents 99cbfa15 ec24a3c3
...@@ -1196,7 +1196,7 @@ ...@@ -1196,7 +1196,7 @@
<ProxySelector v-model="form.proxy_id" :proxies="proxies" /> <ProxySelector v-model="form.proxy_id" :proxies="proxies" />
</div> </div>
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4 lg:grid-cols-3">
<div> <div>
<label class="input-label">{{ t('admin.accounts.concurrency') }}</label> <label class="input-label">{{ t('admin.accounts.concurrency') }}</label>
<input v-model.number="form.concurrency" type="number" min="1" class="input" /> <input v-model.number="form.concurrency" type="number" min="1" class="input" />
...@@ -1212,6 +1212,11 @@ ...@@ -1212,6 +1212,11 @@
/> />
<p class="input-hint">{{ t('admin.accounts.priorityHint') }}</p> <p class="input-hint">{{ t('admin.accounts.priorityHint') }}</p>
</div> </div>
<div>
<label class="input-label">{{ t('admin.accounts.billingRateMultiplier') }}</label>
<input v-model.number="form.rate_multiplier" type="number" min="0" step="0.01" class="input" />
<p class="input-hint">{{ t('admin.accounts.billingRateMultiplierHint') }}</p>
</div>
</div> </div>
<div class="border-t border-gray-200 pt-4 dark:border-dark-600"> <div class="border-t border-gray-200 pt-4 dark:border-dark-600">
<label class="input-label">{{ t('admin.accounts.expiresAt') }}</label> <label class="input-label">{{ t('admin.accounts.expiresAt') }}</label>
...@@ -1832,6 +1837,7 @@ const form = reactive({ ...@@ -1832,6 +1837,7 @@ const form = reactive({
proxy_id: null as number | null, proxy_id: null as number | null,
concurrency: 10, concurrency: 10,
priority: 1, priority: 1,
rate_multiplier: 1,
group_ids: [] as number[], group_ids: [] as number[],
expires_at: null as number | null expires_at: null as number | null
}) })
...@@ -2119,6 +2125,7 @@ const resetForm = () => { ...@@ -2119,6 +2125,7 @@ const resetForm = () => {
form.proxy_id = null form.proxy_id = null
form.concurrency = 10 form.concurrency = 10
form.priority = 1 form.priority = 1
form.rate_multiplier = 1
form.group_ids = [] form.group_ids = []
form.expires_at = null form.expires_at = null
accountCategory.value = 'oauth-based' accountCategory.value = 'oauth-based'
...@@ -2272,6 +2279,7 @@ const createAccountAndFinish = async ( ...@@ -2272,6 +2279,7 @@ const createAccountAndFinish = async (
proxy_id: form.proxy_id, proxy_id: form.proxy_id,
concurrency: form.concurrency, concurrency: form.concurrency,
priority: form.priority, priority: form.priority,
rate_multiplier: form.rate_multiplier,
group_ids: form.group_ids, group_ids: form.group_ids,
expires_at: form.expires_at, expires_at: form.expires_at,
auto_pause_on_expired: autoPauseOnExpired.value auto_pause_on_expired: autoPauseOnExpired.value
...@@ -2490,6 +2498,7 @@ const handleCookieAuth = async (sessionKey: string) => { ...@@ -2490,6 +2498,7 @@ const handleCookieAuth = async (sessionKey: string) => {
proxy_id: form.proxy_id, proxy_id: form.proxy_id,
concurrency: form.concurrency, concurrency: form.concurrency,
priority: form.priority, priority: form.priority,
rate_multiplier: form.rate_multiplier,
group_ids: form.group_ids, group_ids: form.group_ids,
expires_at: form.expires_at, expires_at: form.expires_at,
auto_pause_on_expired: autoPauseOnExpired.value auto_pause_on_expired: autoPauseOnExpired.value
......
...@@ -549,7 +549,7 @@ ...@@ -549,7 +549,7 @@
<ProxySelector v-model="form.proxy_id" :proxies="proxies" /> <ProxySelector v-model="form.proxy_id" :proxies="proxies" />
</div> </div>
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4 lg:grid-cols-3">
<div> <div>
<label class="input-label">{{ t('admin.accounts.concurrency') }}</label> <label class="input-label">{{ t('admin.accounts.concurrency') }}</label>
<input v-model.number="form.concurrency" type="number" min="1" class="input" /> <input v-model.number="form.concurrency" type="number" min="1" class="input" />
...@@ -564,6 +564,11 @@ ...@@ -564,6 +564,11 @@
data-tour="account-form-priority" data-tour="account-form-priority"
/> />
</div> </div>
<div>
<label class="input-label">{{ t('admin.accounts.billingRateMultiplier') }}</label>
<input v-model.number="form.rate_multiplier" type="number" min="0" step="0.01" class="input" />
<p class="input-hint">{{ t('admin.accounts.billingRateMultiplierHint') }}</p>
</div>
</div> </div>
<div class="border-t border-gray-200 pt-4 dark:border-dark-600"> <div class="border-t border-gray-200 pt-4 dark:border-dark-600">
<label class="input-label">{{ t('admin.accounts.expiresAt') }}</label> <label class="input-label">{{ t('admin.accounts.expiresAt') }}</label>
...@@ -807,6 +812,7 @@ const form = reactive({ ...@@ -807,6 +812,7 @@ const form = reactive({
proxy_id: null as number | null, proxy_id: null as number | null,
concurrency: 1, concurrency: 1,
priority: 1, priority: 1,
rate_multiplier: 1,
status: 'active' as 'active' | 'inactive', status: 'active' as 'active' | 'inactive',
group_ids: [] as number[], group_ids: [] as number[],
expires_at: null as number | null expires_at: null as number | null
...@@ -834,6 +840,7 @@ watch( ...@@ -834,6 +840,7 @@ watch(
form.proxy_id = newAccount.proxy_id form.proxy_id = newAccount.proxy_id
form.concurrency = newAccount.concurrency form.concurrency = newAccount.concurrency
form.priority = newAccount.priority form.priority = newAccount.priority
form.rate_multiplier = newAccount.rate_multiplier ?? 1
form.status = newAccount.status as 'active' | 'inactive' form.status = newAccount.status as 'active' | 'inactive'
form.group_ids = newAccount.group_ids || [] form.group_ids = newAccount.group_ids || []
form.expires_at = newAccount.expires_at ?? null form.expires_at = newAccount.expires_at ?? null
......
...@@ -15,7 +15,13 @@ ...@@ -15,7 +15,13 @@
<span class="rounded bg-gray-100 px-1.5 py-0.5 dark:bg-gray-800"> <span class="rounded bg-gray-100 px-1.5 py-0.5 dark:bg-gray-800">
{{ formatTokens }} {{ formatTokens }}
</span> </span>
<span class="rounded bg-gray-100 px-1.5 py-0.5 dark:bg-gray-800"> ${{ formatCost }} </span> <span class="rounded bg-gray-100 px-1.5 py-0.5 dark:bg-gray-800"> A ${{ formatAccountCost }} </span>
<span
v-if="windowStats?.user_cost != null"
class="rounded bg-gray-100 px-1.5 py-0.5 dark:bg-gray-800"
>
U ${{ formatUserCost }}
</span>
</div> </div>
</div> </div>
...@@ -149,8 +155,13 @@ const formatTokens = computed(() => { ...@@ -149,8 +155,13 @@ const formatTokens = computed(() => {
return t.toString() return t.toString()
}) })
const formatCost = computed(() => { const formatAccountCost = computed(() => {
if (!props.windowStats) return '0.00' if (!props.windowStats) return '0.00'
return props.windowStats.cost.toFixed(2) return props.windowStats.cost.toFixed(2)
}) })
const formatUserCost = computed(() => {
if (!props.windowStats || props.windowStats.user_cost == null) return '0.00'
return props.windowStats.user_cost.toFixed(2)
})
</script> </script>
...@@ -61,11 +61,12 @@ ...@@ -61,11 +61,12 @@
</p> </p>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400"> <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{{ t('admin.accounts.stats.accumulatedCost') }} {{ t('admin.accounts.stats.accumulatedCost') }}
<span class="text-gray-400 dark:text-gray-500" <span class="text-gray-400 dark:text-gray-500">
>({{ t('admin.accounts.stats.standardCost') }}: ${{ ({{ t('usage.userBilled') }}: ${{ formatCost(stats.summary.total_user_cost) }} ·
{{ t('admin.accounts.stats.standardCost') }}: ${{
formatCost(stats.summary.total_standard_cost) formatCost(stats.summary.total_standard_cost)
}})</span }})
> </span>
</p> </p>
</div> </div>
...@@ -108,12 +109,15 @@ ...@@ -108,12 +109,15 @@
<p class="text-2xl font-bold text-gray-900 dark:text-white"> <p class="text-2xl font-bold text-gray-900 dark:text-white">
${{ formatCost(stats.summary.avg_daily_cost) }} ${{ formatCost(stats.summary.avg_daily_cost) }}
</p> </p>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400"> <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{{ {{
t('admin.accounts.stats.basedOnActualDays', { t('admin.accounts.stats.basedOnActualDays', {
days: stats.summary.actual_days_used days: stats.summary.actual_days_used
}) })
}} }}
<span class="text-gray-400 dark:text-gray-500">
({{ t('usage.userBilled') }}: ${{ formatCost(stats.summary.avg_daily_user_cost) }})
</span>
</p> </p>
</div> </div>
...@@ -164,13 +168,17 @@ ...@@ -164,13 +168,17 @@
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-xs text-gray-500 dark:text-gray-400">{{ <span class="text-xs text-gray-500 dark:text-gray-400">{{ t('usage.accountBilled') }}</span>
t('admin.accounts.stats.cost')
}}</span>
<span class="text-sm font-semibold text-gray-900 dark:text-white" <span class="text-sm font-semibold text-gray-900 dark:text-white"
>${{ formatCost(stats.summary.today?.cost || 0) }}</span >${{ formatCost(stats.summary.today?.cost || 0) }}</span
> >
</div> </div>
<div class="flex items-center justify-between">
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('usage.userBilled') }}</span>
<span class="text-sm font-semibold text-gray-900 dark:text-white"
>${{ formatCost(stats.summary.today?.user_cost || 0) }}</span
>
</div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-xs text-gray-500 dark:text-gray-400">{{ <span class="text-xs text-gray-500 dark:text-gray-400">{{
t('admin.accounts.stats.requests') t('admin.accounts.stats.requests')
...@@ -210,13 +218,17 @@ ...@@ -210,13 +218,17 @@
}}</span> }}</span>
</div> </div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-xs text-gray-500 dark:text-gray-400">{{ <span class="text-xs text-gray-500 dark:text-gray-400">{{ t('usage.accountBilled') }}</span>
t('admin.accounts.stats.cost')
}}</span>
<span class="text-sm font-semibold text-orange-600 dark:text-orange-400" <span class="text-sm font-semibold text-orange-600 dark:text-orange-400"
>${{ formatCost(stats.summary.highest_cost_day?.cost || 0) }}</span >${{ formatCost(stats.summary.highest_cost_day?.cost || 0) }}</span
> >
</div> </div>
<div class="flex items-center justify-between">
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('usage.userBilled') }}</span>
<span class="text-sm font-semibold text-gray-900 dark:text-white"
>${{ formatCost(stats.summary.highest_cost_day?.user_cost || 0) }}</span
>
</div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-xs text-gray-500 dark:text-gray-400">{{ <span class="text-xs text-gray-500 dark:text-gray-400">{{
t('admin.accounts.stats.requests') t('admin.accounts.stats.requests')
...@@ -260,13 +272,17 @@ ...@@ -260,13 +272,17 @@
}}</span> }}</span>
</div> </div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-xs text-gray-500 dark:text-gray-400">{{ <span class="text-xs text-gray-500 dark:text-gray-400">{{ t('usage.accountBilled') }}</span>
t('admin.accounts.stats.cost')
}}</span>
<span class="text-sm font-semibold text-gray-900 dark:text-white" <span class="text-sm font-semibold text-gray-900 dark:text-white"
>${{ formatCost(stats.summary.highest_request_day?.cost || 0) }}</span >${{ formatCost(stats.summary.highest_request_day?.cost || 0) }}</span
> >
</div> </div>
<div class="flex items-center justify-between">
<span class="text-xs text-gray-500 dark:text-gray-400">{{ t('usage.userBilled') }}</span>
<span class="text-sm font-semibold text-gray-900 dark:text-white"
>${{ formatCost(stats.summary.highest_request_day?.user_cost || 0) }}</span
>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -485,14 +501,24 @@ const trendChartData = computed(() => { ...@@ -485,14 +501,24 @@ const trendChartData = computed(() => {
labels: stats.value.history.map((h) => h.label), labels: stats.value.history.map((h) => h.label),
datasets: [ datasets: [
{ {
label: t('admin.accounts.stats.cost') + ' (USD)', label: t('usage.accountBilled') + ' (USD)',
data: stats.value.history.map((h) => h.cost), data: stats.value.history.map((h) => h.actual_cost),
borderColor: '#3b82f6', borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)', backgroundColor: 'rgba(59, 130, 246, 0.1)',
fill: true, fill: true,
tension: 0.3, tension: 0.3,
yAxisID: 'y' yAxisID: 'y'
}, },
{
label: t('usage.userBilled') + ' (USD)',
data: stats.value.history.map((h) => h.user_cost),
borderColor: '#10b981',
backgroundColor: 'rgba(16, 185, 129, 0.08)',
fill: false,
tension: 0.3,
borderDash: [5, 5],
yAxisID: 'y'
},
{ {
label: t('admin.accounts.stats.requests'), label: t('admin.accounts.stats.requests'),
data: stats.value.history.map((h) => h.requests), data: stats.value.history.map((h) => h.requests),
...@@ -570,7 +596,7 @@ const lineChartOptions = computed(() => ({ ...@@ -570,7 +596,7 @@ const lineChartOptions = computed(() => ({
}, },
title: { title: {
display: true, display: true,
text: t('admin.accounts.stats.cost') + ' (USD)', text: t('usage.accountBilled') + ' (USD)',
color: '#3b82f6', color: '#3b82f6',
font: { font: {
size: 11 size: 11
......
...@@ -27,9 +27,18 @@ ...@@ -27,9 +27,18 @@
</div> </div>
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<p class="text-xs font-medium text-gray-500">{{ t('usage.totalCost') }}</p> <p class="text-xs font-medium text-gray-500">{{ t('usage.totalCost') }}</p>
<p class="text-xl font-bold text-green-600">${{ (stats?.total_actual_cost || 0).toFixed(4) }}</p> <p class="text-xl font-bold text-green-600">
<p class="text-xs text-gray-400"> ${{ ((stats?.total_account_cost ?? stats?.total_actual_cost) || 0).toFixed(4) }}
{{ t('usage.standardCost') }}: <span class="line-through">${{ (stats?.total_cost || 0).toFixed(4) }}</span> </p>
<p class="text-xs text-gray-400" v-if="stats?.total_account_cost != null">
{{ t('usage.userBilled') }}:
<span class="text-gray-300">${{ (stats?.total_actual_cost || 0).toFixed(4) }}</span>
· {{ t('usage.standardCost') }}:
<span class="text-gray-300">${{ (stats?.total_cost || 0).toFixed(4) }}</span>
</p>
<p class="text-xs text-gray-400" v-else>
{{ t('usage.standardCost') }}:
<span class="line-through">${{ (stats?.total_cost || 0).toFixed(4) }}</span>
</p> </p>
</div> </div>
</div> </div>
......
...@@ -81,18 +81,23 @@ ...@@ -81,18 +81,23 @@
</template> </template>
<template #cell-cost="{ row }"> <template #cell-cost="{ row }">
<div class="flex items-center gap-1.5 text-sm"> <div class="text-sm">
<span class="font-medium text-green-600 dark:text-green-400">${{ row.actual_cost?.toFixed(6) || '0.000000' }}</span> <div class="flex items-center gap-1.5">
<!-- Cost Detail Tooltip --> <span class="font-medium text-green-600 dark:text-green-400">${{ row.actual_cost?.toFixed(6) || '0.000000' }}</span>
<div <!-- Cost Detail Tooltip -->
class="group relative" <div
@mouseenter="showTooltip($event, row)" class="group relative"
@mouseleave="hideTooltip" @mouseenter="showTooltip($event, row)"
> @mouseleave="hideTooltip"
<div class="flex h-4 w-4 cursor-help items-center justify-center rounded-full bg-gray-100 transition-colors group-hover:bg-blue-100 dark:bg-gray-700 dark:group-hover:bg-blue-900/50"> >
<Icon name="infoCircle" size="xs" class="text-gray-400 group-hover:text-blue-500 dark:text-gray-500 dark:group-hover:text-blue-400" /> <div class="flex h-4 w-4 cursor-help items-center justify-center rounded-full bg-gray-100 transition-colors group-hover:bg-blue-100 dark:bg-gray-700 dark:group-hover:bg-blue-900/50">
<Icon name="infoCircle" size="xs" class="text-gray-400 group-hover:text-blue-500 dark:text-gray-500 dark:group-hover:text-blue-400" />
</div>
</div> </div>
</div> </div>
<div v-if="row.account_rate_multiplier != null" class="mt-0.5 text-[11px] text-gray-400">
A ${{ (row.total_cost * row.account_rate_multiplier).toFixed(6) }}
</div>
</div> </div>
</template> </template>
...@@ -202,14 +207,24 @@ ...@@ -202,14 +207,24 @@
<span class="text-gray-400">{{ t('usage.rate') }}</span> <span class="text-gray-400">{{ t('usage.rate') }}</span>
<span class="font-semibold text-blue-400">{{ (tooltipData?.rate_multiplier || 1).toFixed(2) }}x</span> <span class="font-semibold text-blue-400">{{ (tooltipData?.rate_multiplier || 1).toFixed(2) }}x</span>
</div> </div>
<div class="flex items-center justify-between gap-6">
<span class="text-gray-400">{{ t('usage.accountMultiplier') }}</span>
<span class="font-semibold text-blue-400">{{ (tooltipData?.account_rate_multiplier ?? 1).toFixed(2) }}x</span>
</div>
<div class="flex items-center justify-between gap-6"> <div class="flex items-center justify-between gap-6">
<span class="text-gray-400">{{ t('usage.original') }}</span> <span class="text-gray-400">{{ t('usage.original') }}</span>
<span class="font-medium text-white">${{ tooltipData?.total_cost?.toFixed(6) || '0.000000' }}</span> <span class="font-medium text-white">${{ tooltipData?.total_cost?.toFixed(6) || '0.000000' }}</span>
</div> </div>
<div class="flex items-center justify-between gap-6 border-t border-gray-700 pt-1.5"> <div class="flex items-center justify-between gap-6">
<span class="text-gray-400">{{ t('usage.billed') }}</span> <span class="text-gray-400">{{ t('usage.userBilled') }}</span>
<span class="font-semibold text-green-400">${{ tooltipData?.actual_cost?.toFixed(6) || '0.000000' }}</span> <span class="font-semibold text-green-400">${{ tooltipData?.actual_cost?.toFixed(6) || '0.000000' }}</span>
</div> </div>
<div class="flex items-center justify-between gap-6 border-t border-gray-700 pt-1.5">
<span class="text-gray-400">{{ t('usage.accountBilled') }}</span>
<span class="font-semibold text-green-400">
${{ (((tooltipData?.total_cost || 0) * (tooltipData?.account_rate_multiplier ?? 1)) || 0).toFixed(6) }}
</span>
</div>
</div> </div>
<div class="absolute right-full top-1/2 h-0 w-0 -translate-y-1/2 border-b-[6px] border-r-[6px] border-t-[6px] border-b-transparent border-r-gray-900 border-t-transparent dark:border-r-gray-800"></div> <div class="absolute right-full top-1/2 h-0 w-0 -translate-y-1/2 border-b-[6px] border-r-[6px] border-t-[6px] border-b-transparent border-r-gray-900 border-t-transparent dark:border-r-gray-800"></div>
</div> </div>
......
...@@ -429,6 +429,9 @@ export default { ...@@ -429,6 +429,9 @@ export default {
totalCost: 'Total Cost', totalCost: 'Total Cost',
standardCost: 'Standard', standardCost: 'Standard',
actualCost: 'Actual', actualCost: 'Actual',
userBilled: 'User billed',
accountBilled: 'Account billed',
accountMultiplier: 'Account rate',
avgDuration: 'Avg Duration', avgDuration: 'Avg Duration',
inSelectedRange: 'in selected range', inSelectedRange: 'in selected range',
perRequest: 'per request', perRequest: 'per request',
...@@ -1059,6 +1062,7 @@ export default { ...@@ -1059,6 +1062,7 @@ export default {
concurrencyStatus: 'Concurrency', concurrencyStatus: 'Concurrency',
notes: 'Notes', notes: 'Notes',
priority: 'Priority', priority: 'Priority',
billingRateMultiplier: 'Billing Rate',
weight: 'Weight', weight: 'Weight',
status: 'Status', status: 'Status',
schedulable: 'Schedulable', schedulable: 'Schedulable',
...@@ -1226,6 +1230,8 @@ export default { ...@@ -1226,6 +1230,8 @@ export default {
concurrency: 'Concurrency', concurrency: 'Concurrency',
priority: 'Priority', priority: 'Priority',
priorityHint: 'Lower value accounts are used first', priorityHint: 'Lower value accounts are used first',
billingRateMultiplier: 'Billing Rate Multiplier',
billingRateMultiplierHint: '>=0, 0 means free. Affects account billing only',
expiresAt: 'Expires At', expiresAt: 'Expires At',
expiresAtHint: 'Leave empty for no expiration', expiresAtHint: 'Leave empty for no expiration',
higherPriorityFirst: 'Lower value means higher priority', higherPriorityFirst: 'Lower value means higher priority',
......
...@@ -426,6 +426,9 @@ export default { ...@@ -426,6 +426,9 @@ export default {
totalCost: '总消费', totalCost: '总消费',
standardCost: '标准', standardCost: '标准',
actualCost: '实际', actualCost: '实际',
userBilled: '用户扣费',
accountBilled: '账号计费',
accountMultiplier: '账号倍率',
avgDuration: '平均耗时', avgDuration: '平均耗时',
inSelectedRange: '所选范围内', inSelectedRange: '所选范围内',
perRequest: '每次请求', perRequest: '每次请求',
...@@ -1109,6 +1112,7 @@ export default { ...@@ -1109,6 +1112,7 @@ export default {
concurrencyStatus: '并发', concurrencyStatus: '并发',
notes: '备注', notes: '备注',
priority: '优先级', priority: '优先级',
billingRateMultiplier: '账号倍率',
weight: '权重', weight: '权重',
status: '状态', status: '状态',
schedulable: '调度', schedulable: '调度',
...@@ -1360,6 +1364,8 @@ export default { ...@@ -1360,6 +1364,8 @@ export default {
concurrency: '并发数', concurrency: '并发数',
priority: '优先级', priority: '优先级',
priorityHint: '优先级越小的账号优先使用', priorityHint: '优先级越小的账号优先使用',
billingRateMultiplier: '账号计费倍率',
billingRateMultiplierHint: '>=0,0 表示该账号计费为 0;仅影响账号计费口径',
expiresAt: '过期时间', expiresAt: '过期时间',
expiresAtHint: '留空表示不过期', expiresAtHint: '留空表示不过期',
higherPriorityFirst: '数值越小优先级越高', higherPriorityFirst: '数值越小优先级越高',
......
...@@ -428,6 +428,7 @@ export interface Account { ...@@ -428,6 +428,7 @@ export interface Account {
concurrency: number concurrency: number
current_concurrency?: number // Real-time concurrency count from Redis current_concurrency?: number // Real-time concurrency count from Redis
priority: number priority: number
rate_multiplier?: number // Account billing multiplier (>=0, 0 means free)
status: 'active' | 'inactive' | 'error' status: 'active' | 'inactive' | 'error'
error_message: string | null error_message: string | null
last_used_at: string | null last_used_at: string | null
...@@ -457,7 +458,9 @@ export interface Account { ...@@ -457,7 +458,9 @@ export interface Account {
export interface WindowStats { export interface WindowStats {
requests: number requests: number
tokens: number tokens: number
cost: number cost: number // Account cost (account multiplier)
standard_cost?: number
user_cost?: number
} }
export interface UsageProgress { export interface UsageProgress {
...@@ -522,6 +525,7 @@ export interface CreateAccountRequest { ...@@ -522,6 +525,7 @@ export interface CreateAccountRequest {
proxy_id?: number | null proxy_id?: number | null
concurrency?: number concurrency?: number
priority?: number priority?: number
rate_multiplier?: number // Account billing multiplier (>=0, 0 means free)
group_ids?: number[] group_ids?: number[]
expires_at?: number | null expires_at?: number | null
auto_pause_on_expired?: boolean auto_pause_on_expired?: boolean
...@@ -537,6 +541,7 @@ export interface UpdateAccountRequest { ...@@ -537,6 +541,7 @@ export interface UpdateAccountRequest {
proxy_id?: number | null proxy_id?: number | null
concurrency?: number concurrency?: number
priority?: number priority?: number
rate_multiplier?: number // Account billing multiplier (>=0, 0 means free)
schedulable?: boolean schedulable?: boolean
status?: 'active' | 'inactive' status?: 'active' | 'inactive'
group_ids?: number[] group_ids?: number[]
...@@ -593,6 +598,7 @@ export interface UsageLog { ...@@ -593,6 +598,7 @@ export interface UsageLog {
total_cost: number total_cost: number
actual_cost: number actual_cost: number
rate_multiplier: number rate_multiplier: number
account_rate_multiplier?: number | null
stream: boolean stream: boolean
duration_ms: number duration_ms: number
...@@ -852,23 +858,27 @@ export interface AccountUsageHistory { ...@@ -852,23 +858,27 @@ export interface AccountUsageHistory {
requests: number requests: number
tokens: number tokens: number
cost: number cost: number
actual_cost: number actual_cost: number // Account cost (account multiplier)
user_cost: number // User/API key billed cost (group multiplier)
} }
export interface AccountUsageSummary { export interface AccountUsageSummary {
days: number days: number
actual_days_used: number actual_days_used: number
total_cost: number total_cost: number // Account cost (account multiplier)
total_user_cost: number
total_standard_cost: number total_standard_cost: number
total_requests: number total_requests: number
total_tokens: number total_tokens: number
avg_daily_cost: number avg_daily_cost: number // Account cost
avg_daily_user_cost: number
avg_daily_requests: number avg_daily_requests: number
avg_daily_tokens: number avg_daily_tokens: number
avg_duration_ms: number avg_duration_ms: number
today: { today: {
date: string date: string
cost: number cost: number
user_cost: number
requests: number requests: number
tokens: number tokens: number
} | null } | null
...@@ -876,6 +886,7 @@ export interface AccountUsageSummary { ...@@ -876,6 +886,7 @@ export interface AccountUsageSummary {
date: string date: string
label: string label: string
cost: number cost: number
user_cost: number
requests: number requests: number
} | null } | null
highest_request_day: { highest_request_day: {
...@@ -883,6 +894,7 @@ export interface AccountUsageSummary { ...@@ -883,6 +894,7 @@ export interface AccountUsageSummary {
label: string label: string
requests: number requests: number
cost: number cost: number
user_cost: number
} | null } | null
} }
......
...@@ -61,6 +61,11 @@ ...@@ -61,6 +61,11 @@
<template #cell-usage="{ row }"> <template #cell-usage="{ row }">
<AccountUsageCell :account="row" /> <AccountUsageCell :account="row" />
</template> </template>
<template #cell-rate_multiplier="{ row }">
<span class="text-sm font-mono text-gray-700 dark:text-gray-300">
{{ (row.rate_multiplier ?? 1).toFixed(2) }}x
</span>
</template>
<template #cell-priority="{ value }"> <template #cell-priority="{ value }">
<span class="text-sm text-gray-700 dark:text-gray-300">{{ value }}</span> <span class="text-sm text-gray-700 dark:text-gray-300">{{ value }}</span>
</template> </template>
...@@ -190,10 +195,11 @@ const cols = computed(() => { ...@@ -190,10 +195,11 @@ const cols = computed(() => {
if (!authStore.isSimpleMode) { if (!authStore.isSimpleMode) {
c.push({ key: 'groups', label: t('admin.accounts.columns.groups'), sortable: false }) c.push({ key: 'groups', label: t('admin.accounts.columns.groups'), sortable: false })
} }
c.push( c.push(
{ key: 'usage', label: t('admin.accounts.columns.usageWindows'), sortable: false }, { key: 'usage', label: t('admin.accounts.columns.usageWindows'), sortable: false },
{ key: 'priority', label: t('admin.accounts.columns.priority'), sortable: true }, { key: 'priority', label: t('admin.accounts.columns.priority'), sortable: true },
{ key: 'last_used_at', label: t('admin.accounts.columns.lastUsed'), sortable: true }, { key: 'rate_multiplier', label: t('admin.accounts.columns.billingRateMultiplier'), sortable: true },
{ key: 'last_used_at', label: t('admin.accounts.columns.lastUsed'), sortable: true },
{ key: 'expires_at', label: t('admin.accounts.columns.expiresAt'), sortable: true }, { key: 'expires_at', label: t('admin.accounts.columns.expiresAt'), sortable: true },
{ key: 'notes', label: t('admin.accounts.columns.notes'), sortable: false }, { key: 'notes', label: t('admin.accounts.columns.notes'), sortable: false },
{ key: 'actions', label: t('admin.accounts.columns.actions'), sortable: false } { key: 'actions', label: t('admin.accounts.columns.actions'), sortable: false }
......
...@@ -94,7 +94,7 @@ const exportToExcel = async () => { ...@@ -94,7 +94,7 @@ const exportToExcel = async () => {
t('admin.usage.cacheReadTokens'), t('admin.usage.cacheCreationTokens'), t('admin.usage.cacheReadTokens'), t('admin.usage.cacheCreationTokens'),
t('admin.usage.inputCost'), t('admin.usage.outputCost'), t('admin.usage.inputCost'), t('admin.usage.outputCost'),
t('admin.usage.cacheReadCost'), t('admin.usage.cacheCreationCost'), t('admin.usage.cacheReadCost'), t('admin.usage.cacheCreationCost'),
t('usage.rate'), t('usage.original'), t('usage.billed'), t('usage.rate'), t('usage.accountMultiplier'), t('usage.original'), t('usage.userBilled'), t('usage.accountBilled'),
t('usage.firstToken'), t('usage.duration'), t('usage.firstToken'), t('usage.duration'),
t('admin.usage.requestId'), t('usage.userAgent'), t('admin.usage.ipAddress') t('admin.usage.requestId'), t('usage.userAgent'), t('admin.usage.ipAddress')
] ]
...@@ -115,8 +115,10 @@ const exportToExcel = async () => { ...@@ -115,8 +115,10 @@ const exportToExcel = async () => {
log.cache_read_cost?.toFixed(6) || '0.000000', log.cache_read_cost?.toFixed(6) || '0.000000',
log.cache_creation_cost?.toFixed(6) || '0.000000', log.cache_creation_cost?.toFixed(6) || '0.000000',
log.rate_multiplier?.toFixed(2) || '1.00', log.rate_multiplier?.toFixed(2) || '1.00',
(log.account_rate_multiplier ?? 1).toFixed(2),
log.total_cost?.toFixed(6) || '0.000000', log.total_cost?.toFixed(6) || '0.000000',
log.actual_cost?.toFixed(6) || '0.000000', log.actual_cost?.toFixed(6) || '0.000000',
(log.total_cost * (log.account_rate_multiplier ?? 1)).toFixed(6),
log.first_token_ms ?? '', log.first_token_ms ?? '',
log.duration_ms, log.duration_ms,
log.request_id || '', log.request_id || '',
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment