Commit ee4bfcbb authored by Elysia's avatar Elysia
Browse files

Merge remote-tracking branch 'origin/main'

parents 32d619a5 cac23020
......@@ -133,6 +133,8 @@ export default {
requests: '请求数',
inputTokens: '输入 Tokens',
outputTokens: '输出 Tokens',
cacheCreationTokens: '缓存创建',
cacheReadTokens: '缓存读取',
totalTokens: '总 Tokens',
cost: '费用',
// Status
......@@ -155,11 +157,19 @@ export default {
subscriptionExpires: '订阅到期',
// Usage stat cells
todayRequests: '今日请求',
todayInputTokens: '今日输入',
todayOutputTokens: '今日输出',
todayTokens: '今日 Tokens',
todayCacheCreation: '今日缓存创建',
todayCacheRead: '今日缓存读取',
todayCost: '今日费用',
rpmTpm: 'RPM / TPM',
totalRequests: '累计请求',
totalInputTokens: '累计输入',
totalOutputTokens: '累计输出',
totalTokensLabel: '累计 Tokens',
totalCacheCreation: '累计缓存创建',
totalCacheRead: '累计缓存读取',
totalCost: '累计费用',
avgDuration: '平均耗时',
// Messages
......@@ -1774,8 +1784,20 @@ export default {
stickyExemptWarning: 'RPM 限制 (粘性豁免) - 接近阈值',
stickyExemptOver: 'RPM 限制 (粘性豁免) - 超限,仅粘性会话'
},
quota: {
exceeded: '配额已用完,账号暂停调度',
normal: '配额正常'
},
},
clearRateLimit: '清除速率限制',
resetQuota: '重置配额',
quotaLimit: '配额限制',
quotaLimitPlaceholder: '0 表示不限制',
quotaLimitHint: '设置最大使用额度(美元),达到后账号暂停调度。修改限额不会重置已用额度。',
quotaLimitToggle: '启用配额限制',
quotaLimitToggleHint: '开启后,当账号用量达到设定额度时自动暂停调度',
quotaLimitAmount: '限额金额',
quotaLimitAmountHint: '账号最大可用额度(美元),达到后自动暂停。修改限额不会重置已用额度。',
testConnection: '测试连接',
reAuthorize: '重新授权',
refreshToken: '刷新令牌',
......@@ -2123,10 +2145,12 @@ export default {
proxy: '代理',
noProxy: '无代理',
concurrency: '并发数',
loadFactor: '负载因子',
loadFactorHint: '提高负载因子可以提高对账号的调度频率',
priority: '优先级',
priorityHint: '优先级越小的账号优先使用',
billingRateMultiplier: '账号计费倍率',
billingRateMultiplierHint: '>=0,0 表示该账号计费为 0;仅影响账号计费口径',
billingRateMultiplierHint: '0 表示不计费,仅影响账号计费',
expiresAt: '过期时间',
expiresAtHint: '留空表示不过期',
higherPriorityFirst: '数值越小优先级越高',
......@@ -2142,6 +2166,7 @@ export default {
accountUpdated: '账号更新成功',
failedToCreate: '创建账号失败',
failedToUpdate: '更新账号失败',
pleaseSelectStatus: '请选择有效的账号状态',
mixedChannelWarningTitle: '混合渠道警告',
mixedChannelWarning: '警告:分组 "{groupName}" 中同时包含 {currentPlatform} 和 {otherPlatform} 账号。混合使用不同渠道可能导致 thinking block 签名验证问题,会自动回退到非 thinking 模式。确定要继续吗?',
pleaseEnterAccountName: '请输入账号名称',
......
......@@ -653,6 +653,7 @@ export interface Account {
} & Record<string, unknown>)
proxy_id: number | null
concurrency: number
load_factor?: number | null
current_concurrency?: number // Real-time concurrency count from Redis
priority: number
rate_multiplier?: number // Account billing multiplier (>=0, 0 means free)
......@@ -705,6 +706,10 @@ export interface Account {
cache_ttl_override_enabled?: boolean | null
cache_ttl_override_target?: string | null
// API Key 账号配额限制
quota_limit?: number | null
quota_used?: number | null
// 运行时状态(仅当启用对应限制时返回)
current_window_cost?: number | null // 当前窗口费用
active_sessions?: number | null // 当前活跃会话数
......@@ -783,6 +788,7 @@ export interface CreateAccountRequest {
extra?: Record<string, unknown>
proxy_id?: number | null
concurrency?: number
load_factor?: number | null
priority?: number
rate_multiplier?: number // Account billing multiplier (>=0, 0 means free)
group_ids?: number[]
......@@ -799,6 +805,7 @@ export interface UpdateAccountRequest {
extra?: Record<string, unknown>
proxy_id?: number | null
concurrency?: number
load_factor?: number | null
priority?: number
rate_multiplier?: number // Account billing multiplier (>=0, 0 means free)
schedulable?: boolean
......@@ -1098,7 +1105,8 @@ export interface TrendDataPoint {
requests: number
input_tokens: number
output_tokens: number
cache_tokens: number
cache_creation_tokens: number
cache_read_tokens: number
total_tokens: number
cost: number // 标准计费
actual_cost: number // 实际扣除
......@@ -1109,6 +1117,8 @@ export interface ModelStat {
requests: number
input_tokens: number
output_tokens: number
cache_creation_tokens: number
cache_read_tokens: number
total_tokens: number
cost: number // 标准计费
actual_cost: number // 实际扣除
......
......@@ -302,6 +302,8 @@
<th class="px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-dark-400">{{ t('keyUsage.requests') }}</th>
<th class="px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-dark-400">{{ t('keyUsage.inputTokens') }}</th>
<th class="px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-dark-400">{{ t('keyUsage.outputTokens') }}</th>
<th class="px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-dark-400">{{ t('keyUsage.cacheCreationTokens') }}</th>
<th class="px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-dark-400">{{ t('keyUsage.cacheReadTokens') }}</th>
<th class="px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-dark-400">{{ t('keyUsage.totalTokens') }}</th>
<th class="px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-dark-400">{{ t('keyUsage.cost') }}</th>
</tr>
......@@ -316,6 +318,8 @@
<td class="px-4 py-3 text-sm tabular-nums text-right text-gray-700 dark:text-dark-200">{{ fmtNum(m.requests) }}</td>
<td class="px-4 py-3 text-sm tabular-nums text-right text-gray-700 dark:text-dark-200">{{ fmtNum(m.input_tokens) }}</td>
<td class="px-4 py-3 text-sm tabular-nums text-right text-gray-700 dark:text-dark-200">{{ fmtNum(m.output_tokens) }}</td>
<td class="px-4 py-3 text-sm tabular-nums text-right text-gray-700 dark:text-dark-200">{{ fmtNum(m.cache_creation_tokens) }}</td>
<td class="px-4 py-3 text-sm tabular-nums text-right text-gray-700 dark:text-dark-200">{{ fmtNum(m.cache_read_tokens) }}</td>
<td class="px-4 py-3 text-sm tabular-nums text-right text-gray-700 dark:text-dark-200">{{ fmtNum(m.total_tokens) }}</td>
<td class="px-4 py-3 text-sm tabular-nums text-right font-medium text-gray-900 dark:text-white">{{ usd(m.actual_cost != null ? m.actual_cost : m.cost) }}</td>
</tr>
......@@ -694,11 +698,19 @@ const usageStatCells = computed<StatCell[]>(() => {
return [
{ label: t('keyUsage.todayRequests'), value: fmtNum(today.requests) },
{ label: t('keyUsage.todayInputTokens'), value: fmtNum(today.input_tokens) },
{ label: t('keyUsage.todayOutputTokens'), value: fmtNum(today.output_tokens) },
{ label: t('keyUsage.todayTokens'), value: fmtNum(today.total_tokens) },
{ label: t('keyUsage.todayCacheCreation'), value: fmtNum(today.cache_creation_tokens) },
{ label: t('keyUsage.todayCacheRead'), value: fmtNum(today.cache_read_tokens) },
{ label: t('keyUsage.todayCost'), value: usd(today.actual_cost) },
{ label: t('keyUsage.rpmTpm'), value: `${usage.rpm || 0} / ${usage.tpm || 0}` },
{ label: t('keyUsage.totalRequests'), value: fmtNum(total.requests) },
{ label: t('keyUsage.totalInputTokens'), value: fmtNum(total.input_tokens) },
{ label: t('keyUsage.totalOutputTokens'), value: fmtNum(total.output_tokens) },
{ label: t('keyUsage.totalTokensLabel'), value: fmtNum(total.total_tokens) },
{ label: t('keyUsage.totalCacheCreation'), value: fmtNum(total.cache_creation_tokens) },
{ label: t('keyUsage.totalCacheRead'), value: fmtNum(total.cache_read_tokens) },
{ label: t('keyUsage.totalCost'), value: usd(total.actual_cost) },
{ label: t('keyUsage.avgDuration'), value: usage.average_duration_ms ? `${Math.round(usage.average_duration_ms)} ms` : '-' },
]
......
......@@ -261,7 +261,7 @@
<AccountTestModal :show="showTest" :account="testingAcc" @close="closeTestModal" />
<AccountStatsModal :show="showStats" :account="statsAcc" @close="closeStatsModal" />
<ScheduledTestsPanel :show="showSchedulePanel" :account-id="scheduleAcc?.id ?? null" :model-options="scheduleModelOptions" @close="closeSchedulePanel" />
<AccountActionMenu :show="menu.show" :account="menu.acc" :position="menu.pos" @close="menu.show = false" @test="handleTest" @stats="handleViewStats" @schedule="handleSchedule" @reauth="handleReAuth" @refresh-token="handleRefresh" @reset-status="handleResetStatus" @clear-rate-limit="handleClearRateLimit" />
<AccountActionMenu :show="menu.show" :account="menu.acc" :position="menu.pos" @close="menu.show = false" @test="handleTest" @stats="handleViewStats" @schedule="handleSchedule" @reauth="handleReAuth" @refresh-token="handleRefresh" @reset-status="handleResetStatus" @clear-rate-limit="handleClearRateLimit" @reset-quota="handleResetQuota" />
<SyncFromCrsModal :show="showSync" @close="showSync = false" @synced="reload" />
<ImportDataModal :show="showImportData" @close="showImportData = false" @imported="handleDataImported" />
<BulkEditAccountModal :show="showBulkEdit" :account-ids="selIds" :selected-platforms="selPlatforms" :selected-types="selTypes" :proxies="proxies" :groups="groups" @close="showBulkEdit = false" @updated="handleBulkUpdated" />
......@@ -1125,6 +1125,16 @@ const handleClearRateLimit = async (a: Account) => {
console.error('Failed to clear rate limit:', error)
}
}
const handleResetQuota = async (a: Account) => {
try {
const updated = await adminAPI.accounts.resetAccountQuota(a.id)
patchAccountInList(updated)
enterAutoRefreshSilentWindow()
appStore.showSuccess(t('common.success'))
} catch (error) {
console.error('Failed to reset quota:', error)
}
}
const handleDelete = (a: Account) => { deletingAcc.value = a; showDeleteDialog.value = true }
const confirmDelete = async () => { if(!deletingAcc.value) return; try { await adminAPI.accounts.delete(deletingAcc.value.id); showDeleteDialog.value = false; deletingAcc.value = null; reload() } catch (error) { console.error('Failed to delete account:', error) } }
const handleToggleSchedulable = async (a: Account) => {
......
......@@ -113,6 +113,9 @@
<!-- Actions -->
<div class="ml-auto flex items-center gap-3">
<button @click="applyFilters" :disabled="loading" class="btn btn-secondary">
{{ t('common.refresh') }}
</button>
<button @click="resetFilters" class="btn btn-secondary">
{{ t('common.reset') }}
</button>
......
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