mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
feat(admin,pool,billing): 端点级模型测试、Provider 自动置顶、缓存 TTL 分级计费展示与账号状态增强
- 模型测试支持指定端点:新增 ModelTestDialog 组件,多端点时弹窗选择,单端点直接测试; 后端 test-model-failover 接口新增 endpoint_id 参数,支持 global/direct 模式下按端点过滤候选 - 创建 Provider 时优先级自动置顶(provider_priority 默认 None,后端取 min-1), 显式指定优先级时 shift 已有行;前端创建时不发送 priority,更新时保留 - 缓存计费 UI 增强:RequestDetailDrawer 支持 5min/1h 缓存创建 token 分级展示, 含按 TTL 匹配单价和分行成本计算;ModelDetailDrawer/ModelsTab 标签区分 5min/1h 缓存创建 - Pool 批量操作额度筛选拆分为「无5H限额」和「无周限额」,按 | 分隔 segment 匹配 - KeyFormDialog 优化非 vertex_ai 时布局,API 密钥输入内联到 grid 右列 - Codex refresher 结构化错误标记:401/402/403 使用 [OAUTH_EXPIRED]/[ACCOUNT_BLOCK] 前缀, 新增 deactivated_workspace 识别与分类 - 前后端 accountBlock 关键词同步:新增 token invalidated、deactivated_workspace 识别, OAuth 失效提示清理 block 前缀后展示 - PoolConfig 新增 batch_concurrency 配置(默认 8,上限 32) - 预设模型新增 gpt-5.4;TestResultDialog 响应式布局与 key 脱敏优化
This commit is contained in:
@@ -87,14 +87,14 @@
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="getEffectiveCachePrice(model, 'creation') > 0 || getEffectiveCachePrice(model, 'read') > 0">
|
||||
<span class="text-muted-foreground text-right">缓存:</span>
|
||||
<span class="text-muted-foreground text-right">{{ get1hCachePrice(model) > 0 ? '5min 缓存:' : '缓存:' }}</span>
|
||||
<span class="font-mono font-semibold">
|
||||
${{ formatPrice(getEffectiveCachePrice(model, 'creation')) }}/${{ formatPrice(getEffectiveCachePrice(model, 'read')) }}
|
||||
</span>
|
||||
</template>
|
||||
<!-- 1h 缓存价格 -->
|
||||
<template v-if="get1hCachePrice(model) > 0">
|
||||
<span class="text-muted-foreground text-right">1h 缓存:</span>
|
||||
<span class="text-muted-foreground text-right">1h 缓存创建:</span>
|
||||
<span class="font-mono font-semibold">
|
||||
${{ formatPrice(get1hCachePrice(model)) }}
|
||||
</span>
|
||||
@@ -211,11 +211,18 @@
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- 测试结果对话框 -->
|
||||
<TestResultDialog
|
||||
<ModelTestDialog
|
||||
:open="testDialogOpen"
|
||||
:result="testResult"
|
||||
:mode="testResultMode"
|
||||
@close="testResult = null"
|
||||
:selecting-model-name="pendingTestModel ? (pendingTestModel.global_model_display_name || pendingTestModel.provider_model_name) : null"
|
||||
:endpoints="activeEndpoints"
|
||||
:selected-endpoint="selectedTestEndpoint"
|
||||
:testing="!!pendingTestModel && testingModelId === pendingTestModel.id"
|
||||
:show-endpoint-selector="activeEndpoints.length > 1"
|
||||
@close="handleTestDialogClose"
|
||||
@back="handleTestDialogBack"
|
||||
@select-endpoint="handleSelectTestEndpoint"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -231,16 +238,19 @@ import { sortResolutionEntries } from '@/utils/form'
|
||||
import {
|
||||
testModelFailover,
|
||||
type Model,
|
||||
type ProviderEndpoint,
|
||||
type TestModelFailoverResponse,
|
||||
} from '@/api/endpoints'
|
||||
import { updateModel } from '@/api/endpoints/models'
|
||||
import { parseApiError } from '@/utils/errorParser'
|
||||
import { formatApiFormat } from '@/api/endpoints/types/api-format'
|
||||
import type { ProviderWithEndpointsSummary } from '@/api/endpoints'
|
||||
import TestResultDialog from './TestResultDialog.vue'
|
||||
import ModelTestDialog from './ModelTestDialog.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
provider: ProviderWithEndpointsSummary
|
||||
models?: Model[]
|
||||
endpoints?: ProviderEndpoint[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -259,6 +269,11 @@ const togglingModelId = ref<string | null>(null)
|
||||
const testingModelId = ref<string | null>(null)
|
||||
const testResult = ref<TestModelFailoverResponse | null>(null)
|
||||
const testResultMode = ref<'global' | 'direct'>('global')
|
||||
const testDialogOpen = ref(false)
|
||||
const pendingTestModel = ref<Model | null>(null)
|
||||
const selectedTestEndpoint = ref<ProviderEndpoint | null>(null)
|
||||
// 使用 props 传入的数据,或使用本地数据
|
||||
const activeEndpoints = computed(() => (props.endpoints ?? []).filter(endpoint => endpoint.is_active))
|
||||
// 使用 props 传入的数据,或使用本地数据
|
||||
const models = computed(() => props.models ?? localModels.value)
|
||||
// 按名称排序的模型列表
|
||||
@@ -421,11 +436,36 @@ async function toggleModelActive(model: Model) {
|
||||
}
|
||||
}
|
||||
|
||||
// 测试模型连接性(模拟外部请求,带故障转移)
|
||||
async function testModelConnection(model: Model) {
|
||||
function resetTestDialogState() {
|
||||
testDialogOpen.value = false
|
||||
pendingTestModel.value = null
|
||||
selectedTestEndpoint.value = null
|
||||
testResult.value = null
|
||||
}
|
||||
|
||||
function handleTestDialogClose() {
|
||||
resetTestDialogState()
|
||||
}
|
||||
|
||||
function handleTestDialogBack() {
|
||||
if (testingModelId.value) return
|
||||
testResult.value = null
|
||||
selectedTestEndpoint.value = null
|
||||
}
|
||||
|
||||
async function handleSelectTestEndpoint(endpointId: string) {
|
||||
if (!pendingTestModel.value) return
|
||||
const endpoint = activeEndpoints.value.find(item => item.id === endpointId)
|
||||
if (!endpoint) return
|
||||
await runModelTest(pendingTestModel.value, endpoint)
|
||||
}
|
||||
|
||||
async function runModelTest(model: Model, endpoint?: ProviderEndpoint) {
|
||||
if (testingModelId.value) return
|
||||
|
||||
testingModelId.value = model.id
|
||||
testDialogOpen.value = true
|
||||
selectedTestEndpoint.value = endpoint ?? null
|
||||
try {
|
||||
const modelName = model.global_model_name || model.provider_model_name
|
||||
|
||||
@@ -433,7 +473,9 @@ async function testModelConnection(model: Model) {
|
||||
provider_id: props.provider.id,
|
||||
mode: 'global',
|
||||
model_name: modelName,
|
||||
message: "hello",
|
||||
api_format: endpoint?.api_format,
|
||||
endpoint_id: endpoint?.id,
|
||||
message: 'hello',
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
@@ -442,18 +484,44 @@ async function testModelConnection(model: Model) {
|
||||
const mapped = successAttempt?.effective_model && successAttempt.effective_model !== modelName
|
||||
? ` -> ${successAttempt.effective_model}`
|
||||
: ''
|
||||
showSuccess(`${modelName}${mapped} 测试成功${latency}`)
|
||||
} else {
|
||||
testResultMode.value = 'global'
|
||||
testResult.value = result
|
||||
const endpointPrefix = endpoint ? `[${formatApiFormat(endpoint.api_format)}] ` : ''
|
||||
showSuccess(`${endpointPrefix}${modelName}${mapped} 测试成功${latency}`)
|
||||
resetTestDialogState()
|
||||
return
|
||||
}
|
||||
testResultMode.value = 'global'
|
||||
testResult.value = result
|
||||
} catch (err: unknown) {
|
||||
showError(`模型测试失败: ${parseApiError(err, '测试请求失败')}`)
|
||||
if (activeEndpoints.value.length <= 1) {
|
||||
resetTestDialogState()
|
||||
return
|
||||
}
|
||||
selectedTestEndpoint.value = null
|
||||
} finally {
|
||||
testingModelId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// 测试模型连接性(模拟外部请求,带故障转移)
|
||||
async function testModelConnection(model: Model) {
|
||||
if (testingModelId.value) return
|
||||
|
||||
if (activeEndpoints.value.length === 0) {
|
||||
showError('暂无可用于测试的活跃端点')
|
||||
return
|
||||
}
|
||||
|
||||
pendingTestModel.value = model
|
||||
selectedTestEndpoint.value = null
|
||||
testResult.value = null
|
||||
testDialogOpen.value = true
|
||||
|
||||
if (activeEndpoints.value.length === 1) {
|
||||
await runModelTest(model, activeEndpoints.value[0])
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露给父组件
|
||||
defineExpose({
|
||||
reload: refresh
|
||||
|
||||
Reference in New Issue
Block a user