feat(proxy): 节点状态简化、连接事件记录、可靠性指标与批量删除

- 移除 UNHEALTHY 中间状态,节点状态简化为 ONLINE/OFFLINE
- 新增 proxy_node_events 表记录 tunnel 连接/断开/错误事件
- 新增 failed_requests/dns_failures/stream_errors 可靠性指标(增量累加)
- tunnel 重连改为固定 1s 延迟,移除指数退避逻辑
- resolver/service 改为以 TunnelManager 内存状态判断节点可用性,避免 DB 竞态
- 修正 Claude cache_control 字段格式,使用 ttl 字段控制缓存时长
- 移除前端手动勾选 capability 的 UI,改为从价格配置自动推断
- 新增全局模型批量删除 API,替换前端并行单个删除
This commit is contained in:
fawney19
2026-02-28 13:52:32 +08:00
parent ecb16d345a
commit 54530faf03
25 changed files with 586 additions and 225 deletions

View File

@@ -179,31 +179,6 @@
</div>
</section>
<!-- Key 能力配置 -->
<section
v-if="availableCapabilities.length > 0"
class="space-y-2"
>
<h4 class="font-medium text-sm">
模型偏好
</h4>
<div class="flex flex-wrap gap-2">
<label
v-for="cap in availableCapabilities"
:key="cap.name"
class="flex items-center gap-2 px-2.5 py-1 rounded-md border bg-muted/30 cursor-pointer text-sm"
>
<input
type="checkbox"
:checked="form.supported_capabilities?.includes(cap.name)"
class="rounded"
@change="toggleCapability(cap.name)"
>
<span>{{ cap.display_name }}</span>
</label>
</div>
</section>
<!-- 价格配置 -->
<section class="space-y-3">
<h4 class="font-medium text-sm">
@@ -212,7 +187,7 @@
<TieredPricingEditor
ref="tieredPricingEditorRef"
v-model="tieredPricing"
:show-cache1h="form.supported_capabilities?.includes('cache_1h')"
:show-cache1h="true"
/>
<div class="flex items-center gap-3 pt-2 border-t">
<Label class="text-xs whitespace-nowrap">按次计费</Label>
@@ -353,7 +328,7 @@
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { ref, computed, watch } from 'vue'
import {
Loader2, Layers, SquarePen,
Search, ChevronRight, Plus, Trash2
@@ -378,7 +353,6 @@ import {
type GlobalModelUpdate,
} from '@/api/global-models'
import type { TieredPricingConfig } from '@/api/endpoints/types'
import { getAllCapabilities, type CapabilityDefinition } from '@/api/endpoints'
const props = defineProps<{
open: boolean
@@ -665,34 +639,6 @@ function fillVideoResolutionPricePreset(preset: 'common' | 'sora' | 'veo') {
}))
}
// Key 能力选项
const availableCapabilities = ref<CapabilityDefinition[]>([])
// 加载可用能力列表
async function loadCapabilities() {
try {
availableCapabilities.value = await getAllCapabilities()
} catch (err) {
log.error('Failed to load capabilities:', err)
}
}
// 切换能力
function toggleCapability(capName: string) {
if (!form.value.supported_capabilities) {
form.value.supported_capabilities = []
}
const index = form.value.supported_capabilities.indexOf(capName)
if (index >= 0) {
form.value.supported_capabilities.splice(index, 1)
} else {
form.value.supported_capabilities.push(capName)
}
}
onMounted(() => {
loadCapabilities()
})
// 加载模型列表
async function loadModels() {
@@ -829,6 +775,19 @@ async function handleSubmit() {
// Apply billing (video) pricing into config before cleaning/submitting.
applyVideoPricingToConfig()
// Auto-infer supported_capabilities from tiered pricing config
const caps = new Set(form.value.supported_capabilities || [])
const has1hPricing = finalTieredPricing?.tiers?.some(
(t: Record<string, unknown>) => Array.isArray(t.cache_ttl_pricing)
&& (t.cache_ttl_pricing as Array<Record<string, unknown>>).some(c => c.ttl_minutes === 60)
)
if (has1hPricing) {
caps.add('cache_1h')
} else {
caps.delete('cache_1h')
}
form.value.supported_capabilities = caps.size > 0 ? [...caps] : []
// 清理空的 config
const cleanConfig = form.value.config && Object.keys(form.value.config).length > 0
? form.value.config

View File

@@ -182,7 +182,7 @@
v-if="getFirst1hCachePrice(model.default_tiered_pricing) !== '-'"
class="flex items-center gap-3 p-3 rounded-lg border bg-muted/20"
>
<Label class="text-xs text-muted-foreground whitespace-nowrap">1h 缓存创建</Label>
<Label class="text-xs text-muted-foreground whitespace-nowrap">1h 缓存</Label>
<span class="text-sm font-mono">{{ getFirst1hCachePrice(model.default_tiered_pricing) }}</span>
</div>
<!-- 按次计费 -->

View File

@@ -112,7 +112,7 @@
v-if="showCache1h"
class="space-y-1"
>
<Label class="text-xs text-muted-foreground">1h 缓存创建</Label>
<Label class="text-xs text-muted-foreground">1h 缓存</Label>
<Input
:model-value="getCache1hDisplay(index)"
type="number"
@@ -209,26 +209,6 @@ watch(
{ immediate: true }
)
// 监听 showCache1h 变化
watch(
() => props.showCache1h,
(newValue, oldValue) => {
if (oldValue === true && newValue === false) {
// 取消勾选时,清除本地的 1h 缓存数据和手动设置标记
localTiers.value.forEach((tier, i) => {
tier.cache_ttl_pricing = undefined
if (cacheManuallySet[i]) {
cacheManuallySet[i].cache1h = false
}
})
syncToParent()
} else if (oldValue === false && newValue === true) {
// 勾选时,同步自动计算的价格到父组件
syncToParent()
}
}
)
// 验证错误
const validationError = computed(() => {
if (localTiers.value.length === 0) {