mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 适配 Antigravity 账户余额查询与流式响应优化
1. Antigravity 账户适配: - 新增 GeminiBalanceProvider,支持 Antigravity 账户的额度查询(API 模式) - AccountBalanceService 增加 queryMode 逻辑与安全限制 - 前端 BalanceDisplay 适配 Antigravity 配额显示 2. 流式响应增强: - 优化 thoughtSignature 捕获与回填,支持思维链透传 - 修复工具调用签名校验 3. 其他: - 请求体大小限制提升至 100MB - .gitignore 更新
This commit is contained in:
@@ -52,10 +52,51 @@
|
||||
</div>
|
||||
|
||||
<!-- 配额(如适用) -->
|
||||
<div v-if="quotaInfo" class="space-y-1">
|
||||
<div v-if="quotaInfo && isAntigravityQuota" class="space-y-2">
|
||||
<div class="flex items-center justify-between text-xs text-gray-600 dark:text-gray-400">
|
||||
<span>已用: {{ formatNumber(quotaInfo.used) }}</span>
|
||||
<span>剩余: {{ formatNumber(quotaInfo.remaining) }}</span>
|
||||
<span>剩余</span>
|
||||
<span>{{ formatQuotaNumber(quotaInfo.remaining) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<div
|
||||
v-for="row in antigravityRows"
|
||||
:key="row.category"
|
||||
class="flex items-center gap-2 rounded-md bg-gray-50 px-2 py-1.5 dark:bg-gray-700/60"
|
||||
>
|
||||
<span class="h-2 w-2 shrink-0 rounded-full" :class="row.dotClass"></span>
|
||||
<span
|
||||
class="min-w-0 flex-1 truncate text-xs font-medium text-gray-800 dark:text-gray-100"
|
||||
:title="row.category"
|
||||
>
|
||||
{{ row.category }}
|
||||
</span>
|
||||
|
||||
<div class="flex w-[94px] flex-col gap-0.5">
|
||||
<div class="h-1.5 w-full rounded-full bg-gray-200 dark:bg-gray-600">
|
||||
<div
|
||||
class="h-1.5 rounded-full transition-all"
|
||||
:class="row.barClass"
|
||||
:style="{ width: `${row.remainingPercent ?? 0}%` }"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-between text-[11px] text-gray-500 dark:text-gray-300"
|
||||
>
|
||||
<span>{{ row.remainingText }}</span>
|
||||
<span v-if="row.resetAt" class="text-gray-400 dark:text-gray-400">{{
|
||||
formatResetTime(row.resetAt)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="quotaInfo" class="space-y-1">
|
||||
<div class="flex items-center justify-between text-xs text-gray-600 dark:text-gray-400">
|
||||
<span>已用: {{ formatQuotaNumber(quotaInfo.used) }}</span>
|
||||
<span>剩余: {{ formatQuotaNumber(quotaInfo.remaining) }}</span>
|
||||
</div>
|
||||
<div class="h-1.5 w-full rounded-full bg-gray-200 dark:bg-gray-700">
|
||||
<div
|
||||
@@ -100,7 +141,8 @@ const props = defineProps({
|
||||
platform: { type: String, required: true },
|
||||
initialBalance: { type: Object, default: null },
|
||||
hideRefresh: { type: Boolean, default: false },
|
||||
autoLoad: { type: Boolean, default: true }
|
||||
autoLoad: { type: Boolean, default: true },
|
||||
queryMode: { type: String, default: 'local' } // local | auto | api
|
||||
})
|
||||
|
||||
const emit = defineEmits(['refreshed', 'error'])
|
||||
@@ -136,6 +178,43 @@ const quotaInfo = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const isAntigravityQuota = computed(() => {
|
||||
return balanceData.value?.quota?.type === 'antigravity'
|
||||
})
|
||||
|
||||
const antigravityRows = computed(() => {
|
||||
if (!isAntigravityQuota.value) return []
|
||||
|
||||
const buckets = balanceData.value?.quota?.buckets
|
||||
const list = Array.isArray(buckets) ? buckets : []
|
||||
const map = new Map(list.map((b) => [b?.category, b]))
|
||||
|
||||
const order = ['Gemini Pro', 'Claude', 'Gemini Flash', 'Gemini Image']
|
||||
const styles = {
|
||||
'Gemini Pro': { dotClass: 'bg-blue-500', barClass: 'bg-blue-500 dark:bg-blue-400' },
|
||||
Claude: { dotClass: 'bg-purple-500', barClass: 'bg-purple-500 dark:bg-purple-400' },
|
||||
'Gemini Flash': { dotClass: 'bg-cyan-500', barClass: 'bg-cyan-500 dark:bg-cyan-400' },
|
||||
'Gemini Image': { dotClass: 'bg-emerald-500', barClass: 'bg-emerald-500 dark:bg-emerald-400' }
|
||||
}
|
||||
|
||||
return order.map((category) => {
|
||||
const raw = map.get(category) || null
|
||||
const remaining = raw?.remaining
|
||||
const remainingPercent = Number.isFinite(Number(remaining))
|
||||
? Math.max(0, Math.min(100, Number(remaining)))
|
||||
: null
|
||||
|
||||
return {
|
||||
category,
|
||||
remainingPercent,
|
||||
remainingText: remainingPercent === null ? '—' : `${Math.round(remainingPercent)}%`,
|
||||
resetAt: raw?.resetAt || null,
|
||||
dotClass: styles[category]?.dotClass || 'bg-gray-400',
|
||||
barClass: styles[category]?.barClass || 'bg-gray-400'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const quotaBarClass = computed(() => {
|
||||
const percentage = quotaInfo.value?.percentage || 0
|
||||
if (percentage >= 90) return 'bg-red-500 dark:bg-red-600'
|
||||
@@ -144,7 +223,12 @@ const quotaBarClass = computed(() => {
|
||||
})
|
||||
|
||||
const canRefresh = computed(() => {
|
||||
// 仅在“已启用脚本且该账户配置了脚本”时允许刷新,避免误导(非脚本 Provider 多为降级策略)
|
||||
// antigravity 配额:允许直接触发 Provider 刷新(无需脚本)
|
||||
if (props.queryMode === 'api' || props.queryMode === 'auto') {
|
||||
return true
|
||||
}
|
||||
|
||||
// 其他平台:仅在“已启用脚本且该账户配置了脚本”时允许刷新,避免误导(非脚本 Provider 多为降级策略)
|
||||
const data = balanceData.value
|
||||
if (!data) return false
|
||||
if (data.scriptEnabled === false) return false
|
||||
@@ -159,6 +243,9 @@ const refreshTitle = computed(() => {
|
||||
}
|
||||
return '请先配置余额脚本'
|
||||
}
|
||||
if (isAntigravityQuota.value) {
|
||||
return '刷新配额(调用 Antigravity API)'
|
||||
}
|
||||
return '刷新余额(调用脚本配置的余额 API)'
|
||||
})
|
||||
|
||||
@@ -179,7 +266,10 @@ const load = async () => {
|
||||
|
||||
try {
|
||||
const response = await apiClient.get(`/admin/accounts/${props.accountId}/balance`, {
|
||||
params: { platform: props.platform, queryApi: false }
|
||||
params: {
|
||||
platform: props.platform,
|
||||
queryApi: props.queryMode === 'api' ? true : props.queryMode === 'auto' ? 'auto' : false
|
||||
}
|
||||
})
|
||||
if (response?.success) {
|
||||
balanceData.value = response.data
|
||||
@@ -231,6 +321,16 @@ const formatNumber = (num) => {
|
||||
return value.toLocaleString('zh-CN', { maximumFractionDigits: 2 })
|
||||
}
|
||||
|
||||
const formatQuotaNumber = (num) => {
|
||||
if (num === Infinity) return '∞'
|
||||
const value = Number(num)
|
||||
if (!Number.isFinite(value)) return 'N/A'
|
||||
if (isAntigravityQuota.value) {
|
||||
return `${Math.round(value)}%`
|
||||
}
|
||||
return formatNumber(value)
|
||||
}
|
||||
|
||||
const formatCurrency = (amount) => {
|
||||
const value = Number(amount)
|
||||
if (!Number.isFinite(value)) return '$0.00'
|
||||
|
||||
@@ -797,11 +797,19 @@
|
||||
:account-id="account.id"
|
||||
:initial-balance="account.balanceInfo"
|
||||
:platform="account.platform"
|
||||
:query-mode="
|
||||
account.platform === 'gemini' && account.oauthProvider === 'antigravity'
|
||||
? 'auto'
|
||||
: 'local'
|
||||
"
|
||||
@error="(error) => handleBalanceError(account.id, error)"
|
||||
@refreshed="(data) => handleBalanceRefreshed(account.id, data)"
|
||||
/>
|
||||
<div class="mt-1 text-xs">
|
||||
<button
|
||||
v-if="
|
||||
!(account.platform === 'gemini' && account.oauthProvider === 'antigravity')
|
||||
"
|
||||
class="text-blue-500 hover:underline dark:text-blue-300"
|
||||
@click="openBalanceScriptModal(account)"
|
||||
>
|
||||
@@ -1476,11 +1484,17 @@
|
||||
:account-id="account.id"
|
||||
:initial-balance="account.balanceInfo"
|
||||
:platform="account.platform"
|
||||
:query-mode="
|
||||
account.platform === 'gemini' && account.oauthProvider === 'antigravity'
|
||||
? 'auto'
|
||||
: 'local'
|
||||
"
|
||||
@error="(error) => handleBalanceError(account.id, error)"
|
||||
@refreshed="(data) => handleBalanceRefreshed(account.id, data)"
|
||||
/>
|
||||
<div class="mt-1 text-xs">
|
||||
<button
|
||||
v-if="!(account.platform === 'gemini' && account.oauthProvider === 'antigravity')"
|
||||
class="text-blue-500 hover:underline dark:text-blue-300"
|
||||
@click="openBalanceScriptModal(account)"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user