mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-10 19:29:50 +08:00
feat: 缓存计费细分、能力匹配优化、用户模型调用计数
1. 缓存创建 tokens 区分 5min/1h TTL,支持按缓存时长差异化计费 - Usage 表新增 cache_creation_input_tokens_5m/1h 字段 - Claude handler 解析新格式 (ephemeral_5m/1h, claude_cache_creation_5/1h) - 计费规则支持 cache_ttl_pricing 覆盖 cache_creation 价格 2. 能力匹配机制优化 - COMPATIBLE 能力不再硬过滤,改为排序阶段通过 capability_miss_count 优先级处理 - cache_1h 改为 COMPATIBLE + REQUEST_PARAM(自动检测请求体中的 ttl=1h) - gemini_files 改为 EXCLUSIVE + REQUEST_PARAM(自动检测 fileData.fileUri) - 移除前端模型偏好/能力配置 UI(不再需要用户手动配置) 3. 新增用户-模型维度调用次数计数器 (UserModelUsageCount) - 原子递增,避免从 Usage 表聚合查询 - 前端模型目录和用户可用模型列表展示调用次数 4. 其他改进 - global_model_id 改为必填(NOT NULL),清理孤立模型 - 模型映射对话框支持从上游获取模型列表并分组折叠 - 端点测试不再依赖端点启用状态 - 异步任务页面对普通用户隐藏用户信息列 - Dashboard 响应式布局断点调整 (sm -> lg) - 号池管理仅展示已启用号池的提供商
This commit is contained in:
@@ -76,9 +76,6 @@
|
||||
<TableHead class="min-w-[200px] h-12 font-semibold">
|
||||
密钥名称
|
||||
</TableHead>
|
||||
<TableHead class="min-w-[80px] h-12 font-semibold">
|
||||
能力
|
||||
</TableHead>
|
||||
<TableHead class="min-w-[160px] h-12 font-semibold">
|
||||
密钥
|
||||
</TableHead>
|
||||
@@ -120,42 +117,6 @@
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<!-- 能力 -->
|
||||
<TableCell class="py-4">
|
||||
<div class="flex gap-1.5 flex-wrap items-center">
|
||||
<template v-if="userConfigurableCapabilities.length > 0">
|
||||
<button
|
||||
v-for="cap in userConfigurableCapabilities"
|
||||
:key="cap.name"
|
||||
class="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium transition-all"
|
||||
:class="[
|
||||
apiKey.is_locked ? 'opacity-50 cursor-not-allowed' : '',
|
||||
isCapabilityEnabled(apiKey, cap.name)
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-transparent text-muted-foreground border border-dashed border-muted-foreground/50 hover:border-primary/50 hover:text-foreground'
|
||||
]"
|
||||
:title="apiKey.is_locked ? '已锁定' : getCapabilityTooltip(cap, isCapabilityEnabled(apiKey, cap.name))"
|
||||
:disabled="apiKey.is_locked"
|
||||
@click.stop="!apiKey.is_locked && toggleCapability(apiKey, cap.name)"
|
||||
>
|
||||
<Check
|
||||
v-if="isCapabilityEnabled(apiKey, cap.name)"
|
||||
class="w-3 h-3"
|
||||
/>
|
||||
<Plus
|
||||
v-else
|
||||
class="w-3 h-3"
|
||||
/>
|
||||
{{ cap.short_name || cap.display_name }}
|
||||
</button>
|
||||
</template>
|
||||
<span
|
||||
v-else
|
||||
class="text-muted-foreground text-xs"
|
||||
>-</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<!-- 密钥显示 -->
|
||||
<TableCell class="py-4">
|
||||
<div class="flex items-center gap-1.5">
|
||||
@@ -478,7 +439,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { meApi, type ApiKey } from '@/api/me'
|
||||
import { getAllCapabilities, type CapabilityDefinition } from '@/api/endpoints'
|
||||
import Card from '@/components/ui/card.vue'
|
||||
import Button from '@/components/ui/button.vue'
|
||||
import Input from '@/components/ui/input.vue'
|
||||
@@ -495,7 +455,7 @@ import {
|
||||
TableRow
|
||||
} from '@/components/ui'
|
||||
import RefreshButton from '@/components/ui/refresh-button.vue'
|
||||
import { Plus, Key, Copy, Trash2, Loader2, Activity, CheckCircle, Power, Check } from 'lucide-vue-next'
|
||||
import { Plus, Key, Copy, Trash2, Loader2, Activity, CheckCircle, Power } from 'lucide-vue-next'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
import { log } from '@/utils/logger'
|
||||
import { parseApiError } from '@/utils/errorParser'
|
||||
@@ -526,26 +486,10 @@ const newKeyName = ref('')
|
||||
const newKeyValue = ref('')
|
||||
const keyToDelete = ref<ApiKey | null>(null)
|
||||
|
||||
// 能力配置相关
|
||||
const availableCapabilities = ref<CapabilityDefinition[]>([])
|
||||
const userConfigurableCapabilities = computed(() =>
|
||||
availableCapabilities.value.filter(cap => cap.config_mode === 'user_configurable')
|
||||
)
|
||||
const savingCapability = ref<string | null>(null) // 正在保存的能力标识 "keyId:capName"
|
||||
|
||||
onMounted(() => {
|
||||
loadApiKeys()
|
||||
loadCapabilities()
|
||||
})
|
||||
|
||||
async function loadCapabilities() {
|
||||
try {
|
||||
availableCapabilities.value = await getAllCapabilities()
|
||||
} catch (error) {
|
||||
log.error('Failed to load capabilities:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadApiKeys() {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -625,50 +569,6 @@ async function toggleApiKey(apiKey: ApiKey) {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查某个能力是否已启用
|
||||
function isCapabilityEnabled(apiKey: ApiKey, capName: string): boolean {
|
||||
return apiKey.force_capabilities?.[capName] || false
|
||||
}
|
||||
|
||||
// 切换能力配置
|
||||
async function toggleCapability(apiKey: ApiKey, capName: string) {
|
||||
const capKey = `${apiKey.id}:${capName}`
|
||||
if (savingCapability.value === capKey) return // 防止重复点击
|
||||
|
||||
savingCapability.value = capKey
|
||||
try {
|
||||
const currentEnabled = isCapabilityEnabled(apiKey, capName)
|
||||
const newEnabled = !currentEnabled
|
||||
|
||||
// 构建新的能力配置
|
||||
const newCapabilities: Record<string, boolean> = { ...(apiKey.force_capabilities || {}) }
|
||||
|
||||
if (newEnabled) {
|
||||
newCapabilities[capName] = true
|
||||
} else {
|
||||
delete newCapabilities[capName]
|
||||
}
|
||||
|
||||
const capabilitiesData = Object.keys(newCapabilities).length > 0 ? newCapabilities : null
|
||||
|
||||
// 调用 API 保存
|
||||
await meApi.updateApiKeyCapabilities(apiKey.id, {
|
||||
force_capabilities: capabilitiesData
|
||||
})
|
||||
|
||||
// 更新本地数据
|
||||
const index = apiKeys.value.findIndex(k => k.id === apiKey.id)
|
||||
if (index !== -1) {
|
||||
apiKeys.value[index].force_capabilities = capabilitiesData
|
||||
}
|
||||
} catch (err) {
|
||||
log.error('保存能力配置失败:', err)
|
||||
showError('保存失败,请重试')
|
||||
} finally {
|
||||
savingCapability.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function copyApiKey(apiKey: ApiKey) {
|
||||
try {
|
||||
// 调用后端 API 获取完整密钥
|
||||
@@ -745,12 +645,4 @@ function formatRelativeTime(dateString: string): string {
|
||||
return formatDate(dateString)
|
||||
}
|
||||
|
||||
// 获取能力按钮的提示文字
|
||||
function getCapabilityTooltip(cap: CapabilityDefinition, isEnabled: boolean): string {
|
||||
if (isEnabled) {
|
||||
return `[已启用] 此密钥只能访问支持${cap.display_name}的模型`
|
||||
}
|
||||
return `${cap.description}`
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user