mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
feat: 实现基于费用的速率限制功能
- 新增 rateLimitCost 字段,支持按费用进行速率限制 - 新增 weeklyOpusCostLimit 字段,支持 Opus 模型周费用限制 - 优化速率限制逻辑,支持费用、请求数、token多维度控制 - 更新前端界面,添加费用限制配置选项 - 增强账户管理功能,支持费用统计和限制 - 改进 Redis 数据模型,支持费用计数器 - 优化价格计算服务,支持更精确的成本核算 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -797,6 +797,25 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Claude 5小时限制自动停止调度选项 -->
|
||||
<div v-if="form.platform === 'claude'" class="mt-4">
|
||||
<label class="flex items-start">
|
||||
<input
|
||||
v-model="form.autoStopOnWarning"
|
||||
class="mt-1 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div class="ml-3">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
5小时使用量接近限制时自动停止调度
|
||||
</span>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
当系统检测到账户接近5小时使用限制时,自动暂停调度该账户。进入新的时间窗口后会自动恢复调度。
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- 所有平台的优先级设置 -->
|
||||
<div>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
@@ -1308,6 +1327,25 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Claude 5小时限制自动停止调度选项(编辑模式) -->
|
||||
<div v-if="form.platform === 'claude'" class="mt-4">
|
||||
<label class="flex items-start">
|
||||
<input
|
||||
v-model="form.autoStopOnWarning"
|
||||
class="mt-1 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div class="ml-3">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
5小时使用量接近限制时自动停止调度
|
||||
</span>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
当系统检测到账户接近5小时使用限制时,自动暂停调度该账户。进入新的时间窗口后会自动恢复调度。
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- 所有平台的优先级设置(编辑模式) -->
|
||||
<div>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
@@ -1770,6 +1808,7 @@ const form = ref({
|
||||
description: props.account?.description || '',
|
||||
accountType: props.account?.accountType || 'shared',
|
||||
subscriptionType: 'claude_max', // 默认为 Claude Max,兼容旧数据
|
||||
autoStopOnWarning: props.account?.autoStopOnWarning || false, // 5小时限制自动停止调度
|
||||
groupId: '',
|
||||
projectId: props.account?.projectId || '',
|
||||
idToken: '',
|
||||
@@ -2029,6 +2068,7 @@ const handleOAuthSuccess = async (tokenInfo) => {
|
||||
// Claude使用claudeAiOauth字段
|
||||
data.claudeAiOauth = tokenInfo.claudeAiOauth || tokenInfo
|
||||
data.priority = form.value.priority || 50
|
||||
data.autoStopOnWarning = form.value.autoStopOnWarning || false
|
||||
// 添加订阅类型信息
|
||||
data.subscriptionInfo = {
|
||||
accountType: form.value.subscriptionType || 'claude_max',
|
||||
@@ -2166,6 +2206,7 @@ const createAccount = async () => {
|
||||
scopes: [] // 手动添加没有 scopes
|
||||
}
|
||||
data.priority = form.value.priority || 50
|
||||
data.autoStopOnWarning = form.value.autoStopOnWarning || false
|
||||
// 添加订阅类型信息
|
||||
data.subscriptionInfo = {
|
||||
accountType: form.value.subscriptionType || 'claude_max',
|
||||
@@ -2392,6 +2433,7 @@ const updateAccount = async () => {
|
||||
// Claude 官方账号优先级和订阅类型更新
|
||||
if (props.account.platform === 'claude') {
|
||||
data.priority = form.value.priority || 50
|
||||
data.autoStopOnWarning = form.value.autoStopOnWarning || false
|
||||
// 更新订阅类型信息
|
||||
data.subscriptionInfo = {
|
||||
accountType: form.value.subscriptionType || 'claude_max',
|
||||
@@ -2730,6 +2772,7 @@ watch(
|
||||
description: newAccount.description || '',
|
||||
accountType: newAccount.accountType || 'shared',
|
||||
subscriptionType: subscriptionType,
|
||||
autoStopOnWarning: newAccount.autoStopOnWarning || false,
|
||||
groupId: groupId,
|
||||
projectId: newAccount.projectId || '',
|
||||
accessToken: '',
|
||||
|
||||
@@ -252,17 +252,17 @@
|
||||
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300"
|
||||
>Token 限制</label
|
||||
>费用限制 (美元)</label
|
||||
>
|
||||
<input
|
||||
v-model="form.tokenLimit"
|
||||
v-model="form.rateLimitCost"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
min="0"
|
||||
placeholder="无限制"
|
||||
step="0.01"
|
||||
type="number"
|
||||
/>
|
||||
<p class="ml-2 mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
窗口内最大Token
|
||||
</p>
|
||||
<p class="ml-2 mt-0.5 text-xs text-gray-500 dark:text-gray-400">窗口内最大费用</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -275,12 +275,9 @@
|
||||
<div>
|
||||
<strong>示例1:</strong> 时间窗口=60,请求次数=1000 → 每60分钟最多1000次请求
|
||||
</div>
|
||||
<div><strong>示例2:</strong> 时间窗口=1,费用=0.1 → 每分钟最多$0.1费用</div>
|
||||
<div>
|
||||
<strong>示例2:</strong> 时间窗口=1,Token=10000 → 每分钟最多10,000个Token
|
||||
</div>
|
||||
<div>
|
||||
<strong>示例3:</strong> 窗口=30,请求=50,Token=100000 →
|
||||
每30分钟50次请求且不超10万Token
|
||||
<strong>示例3:</strong> 窗口=30,请求=50,费用=5 → 每30分钟50次请求且不超$5费用
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -336,6 +333,55 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>Opus 模型周费用限制 (美元)</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.weeklyOpusCostLimit = '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.weeklyOpusCostLimit = '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.weeklyOpusCostLimit = '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.weeklyOpusCostLimit = ''"
|
||||
>
|
||||
自定义
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.weeklyOpusCostLimit"
|
||||
class="form-input w-full 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">
|
||||
设置 Opus 模型的周费用限制(周一到周日),仅限 Claude 官方账户,0 或留空表示无限制
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>并发限制 (可选)</label
|
||||
@@ -739,11 +785,12 @@ const form = reactive({
|
||||
batchCount: 10,
|
||||
name: '',
|
||||
description: '',
|
||||
tokenLimit: '',
|
||||
rateLimitWindow: '',
|
||||
rateLimitRequests: '',
|
||||
rateLimitCost: '', // 新增:费用限制
|
||||
concurrencyLimit: '',
|
||||
dailyCostLimit: '',
|
||||
weeklyOpusCostLimit: '',
|
||||
expireDuration: '',
|
||||
customExpireDate: '',
|
||||
expiresAt: null,
|
||||
@@ -985,14 +1032,32 @@ const createApiKey = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否设置了时间窗口但费用限制为0
|
||||
if (form.rateLimitWindow && (!form.rateLimitCost || parseFloat(form.rateLimitCost) === 0)) {
|
||||
let confirmed = false
|
||||
if (window.showConfirm) {
|
||||
confirmed = await window.showConfirm(
|
||||
'费用限制提醒',
|
||||
'您设置了时间窗口但费用限制为0,这意味着不会有费用限制。\n\n是否继续?',
|
||||
'继续创建',
|
||||
'返回修改'
|
||||
)
|
||||
} else {
|
||||
// 降级方案
|
||||
confirmed = confirm('您设置了时间窗口但费用限制为0,这意味着不会有费用限制。\n是否继续?')
|
||||
}
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
// 准备提交的数据
|
||||
const baseData = {
|
||||
description: form.description || undefined,
|
||||
tokenLimit:
|
||||
form.tokenLimit !== '' && form.tokenLimit !== null ? parseInt(form.tokenLimit) : null,
|
||||
tokenLimit: 0, // 设置为0,清除历史token限制
|
||||
rateLimitWindow:
|
||||
form.rateLimitWindow !== '' && form.rateLimitWindow !== null
|
||||
? parseInt(form.rateLimitWindow)
|
||||
@@ -1001,6 +1066,10 @@ const createApiKey = async () => {
|
||||
form.rateLimitRequests !== '' && form.rateLimitRequests !== null
|
||||
? parseInt(form.rateLimitRequests)
|
||||
: null,
|
||||
rateLimitCost:
|
||||
form.rateLimitCost !== '' && form.rateLimitCost !== null
|
||||
? parseFloat(form.rateLimitCost)
|
||||
: null,
|
||||
concurrencyLimit:
|
||||
form.concurrencyLimit !== '' && form.concurrencyLimit !== null
|
||||
? parseInt(form.concurrencyLimit)
|
||||
@@ -1009,6 +1078,10 @@ const createApiKey = async () => {
|
||||
form.dailyCostLimit !== '' && form.dailyCostLimit !== null
|
||||
? parseFloat(form.dailyCostLimit)
|
||||
: 0,
|
||||
weeklyOpusCostLimit:
|
||||
form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null
|
||||
? parseFloat(form.weeklyOpusCostLimit)
|
||||
: 0,
|
||||
expiresAt: form.expiresAt || undefined,
|
||||
permissions: form.permissions,
|
||||
tags: form.tags.length > 0 ? form.tags : undefined,
|
||||
|
||||
@@ -166,17 +166,17 @@
|
||||
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300"
|
||||
>Token 限制</label
|
||||
>费用限制 (美元)</label
|
||||
>
|
||||
<input
|
||||
v-model="form.tokenLimit"
|
||||
v-model="form.rateLimitCost"
|
||||
class="form-input w-full text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
min="0"
|
||||
placeholder="无限制"
|
||||
step="0.01"
|
||||
type="number"
|
||||
/>
|
||||
<p class="ml-2 mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
窗口内最大Token
|
||||
</p>
|
||||
<p class="ml-2 mt-0.5 text-xs text-gray-500 dark:text-gray-400">窗口内最大费用</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -189,12 +189,9 @@
|
||||
<div>
|
||||
<strong>示例1:</strong> 时间窗口=60,请求次数=1000 → 每60分钟最多1000次请求
|
||||
</div>
|
||||
<div><strong>示例2:</strong> 时间窗口=1,费用=0.1 → 每分钟最多$0.1费用</div>
|
||||
<div>
|
||||
<strong>示例2:</strong> 时间窗口=1,Token=10000 → 每分钟最多10,000个Token
|
||||
</div>
|
||||
<div>
|
||||
<strong>示例3:</strong> 窗口=30,请求=50,Token=100000 →
|
||||
每30分钟50次请求且不超10万Token
|
||||
<strong>示例3:</strong> 窗口=30,请求=50,费用=5 → 每30分钟50次请求且不超$5费用
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -250,6 +247,55 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>Opus 模型周费用限制 (美元)</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.weeklyOpusCostLimit = '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.weeklyOpusCostLimit = '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.weeklyOpusCostLimit = '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.weeklyOpusCostLimit = ''"
|
||||
>
|
||||
自定义
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.weeklyOpusCostLimit"
|
||||
class="form-input w-full 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">
|
||||
设置 Opus 模型的周费用限制(周一到周日),仅限 Claude 官方账户,0 或留空表示无限制
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>并发限制</label
|
||||
@@ -632,11 +678,13 @@ const unselectedTags = computed(() => {
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
name: '',
|
||||
tokenLimit: '',
|
||||
tokenLimit: '', // 保留用于检测历史数据
|
||||
rateLimitWindow: '',
|
||||
rateLimitRequests: '',
|
||||
rateLimitCost: '', // 新增:费用限制
|
||||
concurrencyLimit: '',
|
||||
dailyCostLimit: '',
|
||||
weeklyOpusCostLimit: '',
|
||||
permissions: 'all',
|
||||
claudeAccountId: '',
|
||||
geminiAccountId: '',
|
||||
@@ -702,13 +750,31 @@ const removeTag = (index) => {
|
||||
|
||||
// 更新 API Key
|
||||
const updateApiKey = async () => {
|
||||
// 检查是否设置了时间窗口但费用限制为0
|
||||
if (form.rateLimitWindow && (!form.rateLimitCost || parseFloat(form.rateLimitCost) === 0)) {
|
||||
let confirmed = false
|
||||
if (window.showConfirm) {
|
||||
confirmed = await window.showConfirm(
|
||||
'费用限制提醒',
|
||||
'您设置了时间窗口但费用限制为0,这意味着不会有费用限制。\n\n是否继续?',
|
||||
'继续保存',
|
||||
'返回修改'
|
||||
)
|
||||
} else {
|
||||
// 降级方案
|
||||
confirmed = confirm('您设置了时间窗口但费用限制为0,这意味着不会有费用限制。\n是否继续?')
|
||||
}
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
// 准备提交的数据
|
||||
const data = {
|
||||
tokenLimit:
|
||||
form.tokenLimit !== '' && form.tokenLimit !== null ? parseInt(form.tokenLimit) : 0,
|
||||
tokenLimit: 0, // 清除历史token限制
|
||||
rateLimitWindow:
|
||||
form.rateLimitWindow !== '' && form.rateLimitWindow !== null
|
||||
? parseInt(form.rateLimitWindow)
|
||||
@@ -717,6 +783,10 @@ const updateApiKey = async () => {
|
||||
form.rateLimitRequests !== '' && form.rateLimitRequests !== null
|
||||
? parseInt(form.rateLimitRequests)
|
||||
: 0,
|
||||
rateLimitCost:
|
||||
form.rateLimitCost !== '' && form.rateLimitCost !== null
|
||||
? parseFloat(form.rateLimitCost)
|
||||
: 0,
|
||||
concurrencyLimit:
|
||||
form.concurrencyLimit !== '' && form.concurrencyLimit !== null
|
||||
? parseInt(form.concurrencyLimit)
|
||||
@@ -725,6 +795,10 @@ const updateApiKey = async () => {
|
||||
form.dailyCostLimit !== '' && form.dailyCostLimit !== null
|
||||
? parseFloat(form.dailyCostLimit)
|
||||
: 0,
|
||||
weeklyOpusCostLimit:
|
||||
form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null
|
||||
? parseFloat(form.weeklyOpusCostLimit)
|
||||
: 0,
|
||||
permissions: form.permissions,
|
||||
tags: form.tags
|
||||
}
|
||||
@@ -893,11 +967,22 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
form.name = props.apiKey.name
|
||||
|
||||
// 处理速率限制迁移:如果有tokenLimit且没有rateLimitCost,提示用户
|
||||
form.tokenLimit = props.apiKey.tokenLimit || ''
|
||||
form.rateLimitCost = props.apiKey.rateLimitCost || ''
|
||||
|
||||
// 如果有历史tokenLimit但没有rateLimitCost,提示用户需要重新设置
|
||||
if (props.apiKey.tokenLimit > 0 && !props.apiKey.rateLimitCost) {
|
||||
// 可以根据需要添加提示,或者自动迁移(这里选择让用户手动设置)
|
||||
console.log('检测到历史Token限制,请考虑设置费用限制')
|
||||
}
|
||||
|
||||
form.rateLimitWindow = props.apiKey.rateLimitWindow || ''
|
||||
form.rateLimitRequests = props.apiKey.rateLimitRequests || ''
|
||||
form.concurrencyLimit = props.apiKey.concurrencyLimit || ''
|
||||
form.dailyCostLimit = props.apiKey.dailyCostLimit || ''
|
||||
form.weeklyOpusCostLimit = props.apiKey.weeklyOpusCostLimit || ''
|
||||
form.permissions = props.apiKey.permissions || 'all'
|
||||
// 处理 Claude 账号(区分 OAuth 和 Console)
|
||||
if (props.apiKey.claudeConsoleAccountId) {
|
||||
|
||||
@@ -196,6 +196,8 @@
|
||||
时间窗口限制
|
||||
</h5>
|
||||
<WindowCountdown
|
||||
:cost-limit="apiKey.rateLimitCost"
|
||||
:current-cost="apiKey.currentWindowCost"
|
||||
:current-requests="apiKey.currentWindowRequests"
|
||||
:current-tokens="apiKey.currentWindowTokens"
|
||||
label="窗口状态"
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Token限制(向后兼容) -->
|
||||
<div v-if="hasTokenLimit" class="space-y-0.5">
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="text-gray-400">Token</span>
|
||||
@@ -48,6 +49,23 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 费用限制(新功能) -->
|
||||
<div v-if="hasCostLimit" class="space-y-0.5">
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="text-gray-400">费用</span>
|
||||
<span class="text-gray-600">
|
||||
${{ (currentCost || 0).toFixed(2) }}/${{ costLimit.toFixed(2) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="h-1 w-full rounded-full bg-gray-200">
|
||||
<div
|
||||
class="h-1 rounded-full transition-all duration-300"
|
||||
:class="getCostProgressColor()"
|
||||
:style="{ width: getCostProgress() + '%' }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 额外提示信息 -->
|
||||
@@ -102,6 +120,14 @@ const props = defineProps({
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
currentCost: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
costLimit: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
showProgress: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@@ -132,6 +158,7 @@ const windowState = computed(() => {
|
||||
|
||||
const hasRequestLimit = computed(() => props.requestLimit > 0)
|
||||
const hasTokenLimit = computed(() => props.tokenLimit > 0)
|
||||
const hasCostLimit = computed(() => props.costLimit > 0)
|
||||
|
||||
// 方法
|
||||
const formatTime = (seconds) => {
|
||||
@@ -196,6 +223,19 @@ const getTokenProgressColor = () => {
|
||||
return 'bg-purple-500'
|
||||
}
|
||||
|
||||
const getCostProgress = () => {
|
||||
if (!props.costLimit || props.costLimit === 0) return 0
|
||||
const percentage = ((props.currentCost || 0) / props.costLimit) * 100
|
||||
return Math.min(percentage, 100)
|
||||
}
|
||||
|
||||
const getCostProgressColor = () => {
|
||||
const progress = getCostProgress()
|
||||
if (progress >= 100) return 'bg-red-500'
|
||||
if (progress >= 80) return 'bg-yellow-500'
|
||||
return 'bg-green-500'
|
||||
}
|
||||
|
||||
// 更新倒计时
|
||||
const updateCountdown = () => {
|
||||
if (props.windowEndTime && remainingSeconds.value > 0) {
|
||||
|
||||
@@ -45,10 +45,14 @@
|
||||
<div
|
||||
v-if="
|
||||
statsData.limits.rateLimitWindow > 0 &&
|
||||
(statsData.limits.rateLimitRequests > 0 || statsData.limits.tokenLimit > 0)
|
||||
(statsData.limits.rateLimitRequests > 0 ||
|
||||
statsData.limits.tokenLimit > 0 ||
|
||||
statsData.limits.rateLimitCost > 0)
|
||||
"
|
||||
>
|
||||
<WindowCountdown
|
||||
:cost-limit="statsData.limits.rateLimitCost"
|
||||
:current-cost="statsData.limits.currentWindowCost"
|
||||
:current-requests="statsData.limits.currentWindowRequests"
|
||||
:current-tokens="statsData.limits.currentWindowTokens"
|
||||
label="时间窗口限制"
|
||||
@@ -64,7 +68,13 @@
|
||||
|
||||
<div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-info-circle mr-1" />
|
||||
请求次数和Token使用量为"或"的关系,任一达到限制即触发限流
|
||||
<span v-if="statsData.limits.rateLimitCost > 0">
|
||||
请求次数和费用限制为"或"的关系,任一达到限制即触发限流
|
||||
</span>
|
||||
<span v-else-if="statsData.limits.tokenLimit > 0">
|
||||
请求次数和Token使用量为"或"的关系,任一达到限制即触发限流
|
||||
</span>
|
||||
<span v-else> 仅限制请求次数 </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user