mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 给key增加总用量限制
This commit is contained in:
@@ -218,6 +218,20 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300">
|
||||
总费用限制 (美元)
|
||||
</label>
|
||||
<input
|
||||
v-model="form.totalUsageLimit"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
|
||||
min="0"
|
||||
placeholder="不修改 (0 表示无限制)"
|
||||
step="0.01"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Opus 模型周费用限制 -->
|
||||
<div>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300">
|
||||
@@ -521,6 +535,7 @@ const form = reactive({
|
||||
rateLimitRequests: '',
|
||||
concurrencyLimit: '',
|
||||
dailyCostLimit: '',
|
||||
totalUsageLimit: '',
|
||||
weeklyOpusCostLimit: '', // 新增Opus周费用限制
|
||||
permissions: '', // 空字符串表示不修改
|
||||
claudeAccountId: '',
|
||||
@@ -652,6 +667,9 @@ const batchUpdateApiKeys = async () => {
|
||||
if (form.dailyCostLimit !== '' && form.dailyCostLimit !== null) {
|
||||
updates.dailyCostLimit = parseFloat(form.dailyCostLimit)
|
||||
}
|
||||
if (form.totalUsageLimit !== '' && form.totalUsageLimit !== null) {
|
||||
updates.totalUsageLimit = parseFloat(form.totalUsageLimit)
|
||||
}
|
||||
if (form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null) {
|
||||
updates.weeklyOpusCostLimit = parseFloat(form.weeklyOpusCostLimit)
|
||||
}
|
||||
|
||||
@@ -329,12 +329,61 @@
|
||||
step="0.01"
|
||||
type="number"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
<p class="dark:text灰-400 text-xs text-gray-500">
|
||||
设置此 API Key 每日的费用限制,超过限制将拒绝请求,0 或留空表示无限制
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>总费用限制 (美元)</label
|
||||
>
|
||||
<div class="space-y-2">
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||
type="button"
|
||||
@click="form.totalUsageLimit = '100'"
|
||||
>
|
||||
$100
|
||||
</button>
|
||||
<button
|
||||
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||
type="button"
|
||||
@click="form.totalUsageLimit = '500'"
|
||||
>
|
||||
$500
|
||||
</button>
|
||||
<button
|
||||
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||
type="button"
|
||||
@click="form.totalUsageLimit = '1000'"
|
||||
>
|
||||
$1000
|
||||
</button>
|
||||
<button
|
||||
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||
type="button"
|
||||
@click="form.totalUsageLimit = ''"
|
||||
>
|
||||
自定义
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.totalUsageLimit"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
min="0"
|
||||
placeholder="0 表示无限制"
|
||||
step="0.01"
|
||||
type="number"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
设置此 API Key 的累计总费用限制,达到限制后将拒绝所有后续请求,0 或留空表示无限制
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>Opus 模型周费用限制 (美元)</label
|
||||
@@ -861,6 +910,7 @@ const form = reactive({
|
||||
rateLimitCost: '', // 新增:费用限制
|
||||
concurrencyLimit: '',
|
||||
dailyCostLimit: '',
|
||||
totalUsageLimit: '',
|
||||
weeklyOpusCostLimit: '',
|
||||
expireDuration: '',
|
||||
customExpireDate: '',
|
||||
@@ -1199,6 +1249,10 @@ const createApiKey = async () => {
|
||||
form.dailyCostLimit !== '' && form.dailyCostLimit !== null
|
||||
? parseFloat(form.dailyCostLimit)
|
||||
: 0,
|
||||
totalUsageLimit:
|
||||
form.totalUsageLimit !== '' && form.totalUsageLimit !== null
|
||||
? parseFloat(form.totalUsageLimit)
|
||||
: 0,
|
||||
weeklyOpusCostLimit:
|
||||
form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null
|
||||
? parseFloat(form.weeklyOpusCostLimit)
|
||||
|
||||
@@ -273,6 +273,55 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>总费用限制 (美元)</label
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="rounded-lg bg-gray-100 px-3 py-1 text-sm font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||
type="button"
|
||||
@click="form.totalUsageLimit = '100'"
|
||||
>
|
||||
$100
|
||||
</button>
|
||||
<button
|
||||
class="rounded-lg bg-gray-100 px-3 py-1 text-sm font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||
type="button"
|
||||
@click="form.totalUsageLimit = '500'"
|
||||
>
|
||||
$500
|
||||
</button>
|
||||
<button
|
||||
class="rounded-lg bg-gray-100 px-3 py-1 text-sm font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||
type="button"
|
||||
@click="form.totalUsageLimit = '1000'"
|
||||
>
|
||||
$1000
|
||||
</button>
|
||||
<button
|
||||
class="rounded-lg bg-gray-100 px-3 py-1 text-sm font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||
type="button"
|
||||
@click="form.totalUsageLimit = ''"
|
||||
>
|
||||
自定义
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.totalUsageLimit"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
min="0"
|
||||
placeholder="0 表示无限制"
|
||||
step="0.01"
|
||||
type="number"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
设置此 API Key 的累计总费用限制,达到限制后将拒绝所有后续请求,0 或留空表示无限制
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>Opus 模型周费用限制 (美元)</label
|
||||
@@ -713,6 +762,7 @@ const form = reactive({
|
||||
rateLimitCost: '', // 新增:费用限制
|
||||
concurrencyLimit: '',
|
||||
dailyCostLimit: '',
|
||||
totalUsageLimit: '',
|
||||
weeklyOpusCostLimit: '',
|
||||
permissions: 'all',
|
||||
claudeAccountId: '',
|
||||
@@ -826,6 +876,10 @@ const updateApiKey = async () => {
|
||||
form.dailyCostLimit !== '' && form.dailyCostLimit !== null
|
||||
? parseFloat(form.dailyCostLimit)
|
||||
: 0,
|
||||
totalUsageLimit:
|
||||
form.totalUsageLimit !== '' && form.totalUsageLimit !== null
|
||||
? parseFloat(form.totalUsageLimit)
|
||||
: 0,
|
||||
weeklyOpusCostLimit:
|
||||
form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null
|
||||
? parseFloat(form.weeklyOpusCostLimit)
|
||||
@@ -1100,6 +1154,7 @@ onMounted(async () => {
|
||||
form.rateLimitRequests = props.apiKey.rateLimitRequests || ''
|
||||
form.concurrencyLimit = props.apiKey.concurrencyLimit || ''
|
||||
form.dailyCostLimit = props.apiKey.dailyCostLimit || ''
|
||||
form.totalUsageLimit = props.apiKey.totalUsageLimit || ''
|
||||
form.weeklyOpusCostLimit = props.apiKey.weeklyOpusCostLimit || ''
|
||||
form.permissions = props.apiKey.permissions || 'all'
|
||||
// 处理 Claude 账号(区分 OAuth 和 Console)
|
||||
|
||||
@@ -190,6 +190,31 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="apiKey.totalUsageLimit > 0" class="space-y-2">
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-gray-600 dark:text-gray-400">总费用限制</span>
|
||||
<span class="font-semibold text-gray-900 dark:text-gray-100">
|
||||
${{ apiKey.totalUsageLimit.toFixed(2) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="h-2 w-full rounded-full bg-gray-200 dark:bg-gray-600">
|
||||
<div
|
||||
class="h-2 rounded-full transition-all duration-300"
|
||||
:class="
|
||||
totalUsagePercentage >= 100
|
||||
? 'bg-red-500'
|
||||
: totalUsagePercentage >= 80
|
||||
? 'bg-yellow-500'
|
||||
: 'bg-indigo-500'
|
||||
"
|
||||
:style="{ width: Math.min(totalUsagePercentage, 100) + '%' }"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-right text-xs text-gray-500 dark:text-gray-400">
|
||||
已使用 {{ totalUsagePercentage.toFixed(1) }}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="apiKey.rateLimitWindow > 0" class="space-y-2">
|
||||
<h5 class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<i class="fas fa-clock mr-1 text-blue-500" />
|
||||
@@ -250,6 +275,7 @@ const totalTokens = computed(() => props.apiKey.usage?.total?.tokens || 0)
|
||||
const dailyTokens = computed(() => props.apiKey.usage?.daily?.tokens || 0)
|
||||
const totalCost = computed(() => props.apiKey.usage?.total?.cost || 0)
|
||||
const dailyCost = computed(() => props.apiKey.dailyCost || 0)
|
||||
const totalUsageLimit = computed(() => props.apiKey.totalUsageLimit || 0)
|
||||
const inputTokens = computed(() => props.apiKey.usage?.total?.inputTokens || 0)
|
||||
const outputTokens = computed(() => props.apiKey.usage?.total?.outputTokens || 0)
|
||||
const cacheCreateTokens = computed(() => props.apiKey.usage?.total?.cacheCreateTokens || 0)
|
||||
@@ -260,6 +286,7 @@ const tpm = computed(() => props.apiKey.usage?.averages?.tpm || 0)
|
||||
const hasLimits = computed(() => {
|
||||
return (
|
||||
props.apiKey.dailyCostLimit > 0 ||
|
||||
props.apiKey.totalUsageLimit > 0 ||
|
||||
props.apiKey.concurrencyLimit > 0 ||
|
||||
props.apiKey.rateLimitWindow > 0 ||
|
||||
props.apiKey.tokenLimit > 0
|
||||
@@ -271,6 +298,11 @@ const dailyCostPercentage = computed(() => {
|
||||
return (dailyCost.value / props.apiKey.dailyCostLimit) * 100
|
||||
})
|
||||
|
||||
const totalUsagePercentage = computed(() => {
|
||||
if (!totalUsageLimit.value || totalUsageLimit.value === 0) return 0
|
||||
return (totalCost.value / totalUsageLimit.value) * 100
|
||||
})
|
||||
|
||||
// 方法
|
||||
const formatNumber = (num) => {
|
||||
if (!num && num !== 0) return '0'
|
||||
|
||||
Reference in New Issue
Block a user