mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
feat(provider): 支持 Provider 级别代理节点配置,修正配额分组匹配
- Provider 详情抽屉新增代理节点设置入口(Globe 图标),支持选择/清除代理 - 更新 Antigravity 配额分组:修正模型匹配规则以适配当前模型命名 - 简化上游元数据合并逻辑,以上游返回为准,不再保留已下架模型条目
This commit is contained in:
@@ -50,6 +50,52 @@
|
|||||||
<Shuffle class="w-4 h-4" />
|
<Shuffle class="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
|
<Popover
|
||||||
|
v-if="provider.provider_type !== 'custom'"
|
||||||
|
:open="providerProxyPopoverOpen"
|
||||||
|
@update:open="handleProviderProxyPopoverToggle"
|
||||||
|
>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
:class="provider.proxy?.node_id ? 'text-blue-500' : ''"
|
||||||
|
:disabled="savingProviderProxy"
|
||||||
|
:title="provider.proxy?.node_id ? `代理: ${getProviderProxyNodeName()}` : '设置代理节点'"
|
||||||
|
>
|
||||||
|
<Globe class="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
class="w-72 p-3"
|
||||||
|
side="bottom"
|
||||||
|
align="end"
|
||||||
|
>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-xs font-medium">代理节点</span>
|
||||||
|
<Button
|
||||||
|
v-if="provider.proxy?.node_id"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
class="h-6 px-2 text-[10px] text-muted-foreground"
|
||||||
|
:disabled="savingProviderProxy"
|
||||||
|
@click="clearProviderProxy"
|
||||||
|
>
|
||||||
|
清除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<ProxyNodeSelect
|
||||||
|
:model-value="provider.proxy?.node_id || ''"
|
||||||
|
trigger-class="h-8"
|
||||||
|
@update:model-value="setProviderProxy"
|
||||||
|
/>
|
||||||
|
<p class="text-[10px] text-muted-foreground">
|
||||||
|
{{ provider.proxy?.node_id ? '当前使用独立代理' : '未设置代理节点' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
@@ -1145,8 +1191,12 @@ const refreshingQuota = ref(false)
|
|||||||
const antigravityQuotaDialogOpen = ref(false)
|
const antigravityQuotaDialogOpen = ref(false)
|
||||||
const antigravityQuotaDialogKey = ref<EndpointAPIKey | null>(null)
|
const antigravityQuotaDialogKey = ref<EndpointAPIKey | null>(null)
|
||||||
|
|
||||||
// Key 级别代理配置状态
|
// Provider 级别代理配置状态
|
||||||
const proxyNodesStore = useProxyNodesStore()
|
const proxyNodesStore = useProxyNodesStore()
|
||||||
|
const providerProxyPopoverOpen = ref(false)
|
||||||
|
const savingProviderProxy = ref(false)
|
||||||
|
|
||||||
|
// Key 级别代理配置状态
|
||||||
const savingProxyKeyId = ref<string | null>(null)
|
const savingProxyKeyId = ref<string | null>(null)
|
||||||
const proxyPopoverOpenKeyId = ref<string | null>(null)
|
const proxyPopoverOpenKeyId = ref<string | null>(null)
|
||||||
|
|
||||||
@@ -1289,6 +1339,55 @@ async function toggleFormatConversion() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Provider 级别代理配置
|
||||||
|
function handleProviderProxyPopoverToggle(open: boolean) {
|
||||||
|
providerProxyPopoverOpen.value = open
|
||||||
|
if (open) {
|
||||||
|
proxyNodesStore.ensureLoaded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProviderProxyNodeName(): string {
|
||||||
|
const nodeId = provider.value?.proxy?.node_id
|
||||||
|
if (!nodeId) return '未知节点'
|
||||||
|
const node = proxyNodesStore.nodes.find(n => n.id === nodeId)
|
||||||
|
return node ? node.name : `${nodeId.slice(0, 8)}...`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setProviderProxy(nodeId: string) {
|
||||||
|
if (!provider.value) return
|
||||||
|
savingProviderProxy.value = true
|
||||||
|
try {
|
||||||
|
const updated = await updateProvider(provider.value.id, {
|
||||||
|
proxy: { node_id: nodeId, enabled: true },
|
||||||
|
})
|
||||||
|
provider.value = updated
|
||||||
|
providerProxyPopoverOpen.value = false
|
||||||
|
showSuccess('代理节点已设置')
|
||||||
|
emit('refresh')
|
||||||
|
} catch (err: unknown) {
|
||||||
|
showError(parseApiError(err, '设置代理失败'))
|
||||||
|
} finally {
|
||||||
|
savingProviderProxy.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearProviderProxy() {
|
||||||
|
if (!provider.value) return
|
||||||
|
savingProviderProxy.value = true
|
||||||
|
try {
|
||||||
|
const updated = await updateProvider(provider.value.id, { proxy: null })
|
||||||
|
provider.value = updated
|
||||||
|
providerProxyPopoverOpen.value = false
|
||||||
|
showSuccess('已清除提供商代理')
|
||||||
|
emit('refresh')
|
||||||
|
} catch (err: unknown) {
|
||||||
|
showError(parseApiError(err, '清除代理失败'))
|
||||||
|
} finally {
|
||||||
|
savingProviderProxy.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 显示端点管理对话框
|
// 显示端点管理对话框
|
||||||
function showAddEndpointDialog() {
|
function showAddEndpointDialog() {
|
||||||
endpointDialogOpen.value = true
|
endpointDialogOpen.value = true
|
||||||
@@ -2269,10 +2368,10 @@ interface AntigravityQuotaGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ANTIGRAVITY_QUOTA_GROUPS: AntigravityQuotaGroup[] = [
|
const ANTIGRAVITY_QUOTA_GROUPS: AntigravityQuotaGroup[] = [
|
||||||
{ key: 'claude-gpt', label: 'Claude 4.5', match: m => m.includes('claude') },
|
{ key: 'claude', label: 'Claude', match: m => m.includes('claude') },
|
||||||
{ key: 'gemini-3-pro', label: 'Gemini 3 Pro', match: m => m.includes('gemini-3-pro') && !m.includes('image') },
|
{ key: 'gemini-2.5', label: 'Gemini 2.5', match: m => m.includes('gemini-2.5') || m.includes('gemini-2-5') },
|
||||||
{ key: 'gemini-3-flash', label: 'Gemini 3 Flash', match: m => m.includes('gemini-3-flash') },
|
{ key: 'gemini-3', label: 'Gemini 3', match: m => m.includes('gemini-3') && !m.includes('image') },
|
||||||
{ key: 'gemini-3-pro-image', label: 'Gemini 3 Pro Image', match: m => m.includes('gemini-3-pro-image') },
|
{ key: 'gemini-3-image', label: 'Gemini 3 Image', match: m => m.includes('gemini-3') && m.includes('image') },
|
||||||
]
|
]
|
||||||
|
|
||||||
interface AntigravityQuotaSummaryItem {
|
interface AntigravityQuotaSummaryItem {
|
||||||
|
|||||||
@@ -125,9 +125,8 @@ def merge_upstream_metadata(
|
|||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""合并上游元数据,对 quota_by_model 做模型级深度合并。
|
"""合并上游元数据,对 quota_by_model 做模型级深度合并。
|
||||||
|
|
||||||
上游 API 在配额耗尽后可能不再返回该模型的 quotaInfo,因此需要:
|
以上游返回为准(全量替换),不再保留上游已下架的模型。
|
||||||
1. 保留旧数据中已有的 reset_time(当新数据缺少时)
|
仅对上游返回的模型补充旧数据中的 reset_time(当新数据缺少时)。
|
||||||
2. 保留旧数据中存在但新数据中缺失的模型条目(标记为 100% 已用)
|
|
||||||
"""
|
"""
|
||||||
merged: dict[str, Any] = dict(current) if isinstance(current, dict) else {}
|
merged: dict[str, Any] = dict(current) if isinstance(current, dict) else {}
|
||||||
for ns_key, ns_val in incoming.items():
|
for ns_key, ns_val in incoming.items():
|
||||||
@@ -152,13 +151,6 @@ def merge_upstream_metadata(
|
|||||||
and "reset_time" not in new_info
|
and "reset_time" not in new_info
|
||||||
):
|
):
|
||||||
new_info["reset_time"] = old_info["reset_time"]
|
new_info["reset_time"] = old_info["reset_time"]
|
||||||
# 保留新数据中缺失但旧数据中存在的模型(配额耗尽后上游可能不返回)
|
|
||||||
for model_id, old_info in old_qbm.items():
|
|
||||||
if model_id not in new_qbm and isinstance(old_info, dict):
|
|
||||||
exhausted = dict(old_info)
|
|
||||||
exhausted["remaining_fraction"] = 0.0
|
|
||||||
exhausted["used_percent"] = 100.0
|
|
||||||
new_qbm[model_id] = exhausted
|
|
||||||
merged[ns_key] = ns_val
|
merged[ns_key] = ns_val
|
||||||
return merged
|
return merged
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user