mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
feat(fingerprint): 引入 per-key 请求指纹系统,替代全局 TLS 指纹开关
为每个 ProviderAPIKey 生成并持久化独立的请求指纹配置,涵盖 TLS impersonate profile、浏览器 UA、Stainless SDK 头部、Node/Chrome/Electron 版本等维度。 指纹基于 key ID 确定性生成,支持手动编辑和批量重新生成。 - 新增 fingerprint 模块:生成、加载、校验、懒持久化 - 数据库迁移:provider_api_keys 新增 fingerprint JSON 列 - 请求链路注入:handler 基类设置上下文指纹,request_builder 和 envelope 消费 - HTTP Client 支持动态 impersonate profile 选择 - Antigravity 适配器使用指纹覆盖 UA/session/Node 版本 - 前端移除手动 TLS 指纹开关,新增批量 regenerate_fingerprint 操作 - 号池管理 UI 优化:token 缩写格式、blocked 行样式、时间显示改为日期格式
This commit is contained in:
@@ -310,7 +310,7 @@
|
||||
v-for="key in keyPage.keys"
|
||||
:key="key.key_id"
|
||||
class="border-b border-border/40 last:border-b-0 hover:bg-muted/30 transition-colors"
|
||||
:class="{ 'opacity-50': !key.is_active }"
|
||||
:class="getRowClass(key)"
|
||||
>
|
||||
<TableCell class="py-3">
|
||||
<div class="max-w-[260px] min-w-0">
|
||||
@@ -461,7 +461,7 @@
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="text-muted-foreground">Token</span>
|
||||
<span class="tabular-nums text-foreground/90">
|
||||
{{ formatStatInteger(key.total_tokens) }}
|
||||
{{ formatTokenCount(key.total_tokens) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
@@ -599,7 +599,7 @@
|
||||
v-for="key in keyPage.keys"
|
||||
:key="key.key_id"
|
||||
class="p-4 sm:p-5 hover:bg-muted/30 transition-colors"
|
||||
:class="{ 'opacity-50': !key.is_active }"
|
||||
:class="getRowClass(key)"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
@@ -825,7 +825,7 @@
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="text-muted-foreground">Token</span>
|
||||
<span class="tabular-nums">{{ formatStatInteger(key.total_tokens) }}</span>
|
||||
<span class="tabular-nums">{{ formatTokenCount(key.total_tokens) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="text-muted-foreground">费用</span>
|
||||
@@ -1837,7 +1837,7 @@ function getSchedulingBadgeVariant(key: PoolKeyDetail): PoolStatusVariant {
|
||||
if (getAccountAlertLabel(key)) return 'destructive'
|
||||
|
||||
const reason = key.scheduling_reason
|
||||
if (reason === 'manual_disabled') return 'dark'
|
||||
if (reason === 'manual_disabled') return 'secondary'
|
||||
if (reason === 'cooldown' || reason === 'circuit_open' || reason === 'cost_exhausted') return 'destructive'
|
||||
if (reason === 'cost_soft' || reason === 'cost') return 'warning'
|
||||
if (reason === 'health_low' || reason === 'health_degraded' || reason === 'health') return 'warning'
|
||||
@@ -1876,6 +1876,12 @@ function formatTTL(seconds: number): string {
|
||||
return m > 0 ? `${m}m ${s}s` : `${s}s`
|
||||
}
|
||||
|
||||
function getRowClass(key: PoolKeyDetail): string {
|
||||
const status = getSchedulingStatus(key)
|
||||
if (!key.is_active || status === 'blocked') return 'bg-muted/50 opacity-60'
|
||||
return ''
|
||||
}
|
||||
|
||||
function getHealthScoreColor(score: number): string {
|
||||
if (score >= 0.8) return 'text-green-600 dark:text-green-400'
|
||||
if (score >= 0.5) return 'text-yellow-600 dark:text-yellow-400'
|
||||
@@ -2116,6 +2122,14 @@ function formatStatInteger(value: number | null | undefined): string {
|
||||
return Math.round(n).toLocaleString('en-US')
|
||||
}
|
||||
|
||||
function formatTokenCount(value: number | null | undefined): string {
|
||||
const n = Number(value ?? 0)
|
||||
if (!Number.isFinite(n) || n <= 0) return '0'
|
||||
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
|
||||
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`
|
||||
return String(Math.round(n))
|
||||
}
|
||||
|
||||
function formatStatUsd(value: number | null | undefined): string {
|
||||
const n = Number(value ?? 0)
|
||||
if (!Number.isFinite(n) || n <= 0) return '$0.00'
|
||||
@@ -2126,11 +2140,13 @@ function formatStatUsd(value: number | null | undefined): string {
|
||||
}
|
||||
|
||||
function formatRelativeTime(isoStr: string): string {
|
||||
const diff = (Date.now() - new Date(isoStr).getTime()) / 1000
|
||||
if (diff < 60) return '刚刚'
|
||||
if (diff < 3600) return `${Math.floor(diff / 60)}m 前`
|
||||
if (diff < 86400) return `${Math.floor(diff / 3600)}h 前`
|
||||
return `${Math.floor(diff / 86400)}d 前`
|
||||
const date = new Date(isoStr)
|
||||
const pad = (n: number) => String(n).padStart(2, '0')
|
||||
const M = pad(date.getMonth() + 1)
|
||||
const D = pad(date.getDate())
|
||||
const h = pad(date.getHours())
|
||||
const m = pad(date.getMinutes())
|
||||
return `${M}-${D} ${h}:${m}`
|
||||
}
|
||||
|
||||
// --- Init ---
|
||||
|
||||
Reference in New Issue
Block a user