Files
Aether/frontend/src/features/providers/components/ProviderDetailHeader.vue
T

230 lines
8.4 KiB
Vue

<template>
<div class="sticky top-0 z-10 bg-background border-b px-4 sm:px-6 pt-4 sm:pt-6 pb-3 sm:pb-3">
<div class="flex items-center justify-between gap-x-3 sm:gap-x-4 flex-wrap">
<div class="flex items-center gap-2 min-w-0">
<h2 class="text-lg sm:text-xl font-bold truncate">
{{ provider.name }}
</h2>
<Badge
:variant="provider.is_active ? 'default' : 'secondary'"
class="text-xs shrink-0"
>
{{ legacyT(provider.is_active ? '活跃' : '停用') }}
</Badge>
</div>
<div class="flex items-center gap-1 shrink-0">
<span :title="formatConversionTitle">
<Button
variant="ghost"
size="icon"
:class="(provider.enable_format_conversion || systemFormatConversionEnabled) ? 'text-primary' : ''"
:disabled="systemFormatConversionEnabled"
@click="$emit('toggleFormatConversion')"
>
<Shuffle class="w-4 h-4" />
</Button>
</span>
<span :title="keepPriorityTitle">
<Button
variant="ghost"
size="icon"
:class="provider.keep_priority_on_conversion ? 'text-primary' : ''"
:disabled="!formatConversionAvailable"
@click="$emit('toggleKeepPriorityOnConversion')"
>
<Layers class="w-4 h-4" />
</Button>
</span>
<span :title="legacyT(hasFailoverRules ? '已配置故障转移规则(点击编辑)' : '配置故障转移规则')">
<Button
variant="ghost"
size="icon"
:class="hasFailoverRules ? 'text-orange-500 dark:text-orange-400' : ''"
@click="$emit('openFailoverRules')"
>
<GitBranch class="w-4 h-4" />
</Button>
</span>
<Popover
:open="providerProxyPopoverOpen"
@update:open="$emit('update:providerProxyPopoverOpen', $event)"
>
<PopoverTrigger as-child>
<Button
variant="ghost"
size="icon"
:class="provider.proxy?.node_id ? 'text-blue-500' : ''"
:disabled="savingProviderProxy"
:title="provider.proxy?.node_id ? `${legacyT('代理')}: ${providerProxyNodeName}` : legacyT('设置代理节点')"
>
<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">{{ legacyT('代理节点') }}</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="$emit('clearProviderProxy')"
>
{{ legacyT('清除') }}
</Button>
</div>
<ProxyNodeSelect
:model-value="provider.proxy?.node_id || ''"
trigger-class="h-8"
@update:model-value="$emit('setProviderProxy', $event)"
/>
<p class="text-[10px] text-muted-foreground">
{{ legacyT(provider.proxy?.node_id ? '当前使用独立代理' : '未设置代理节点') }}
</p>
</div>
</PopoverContent>
</Popover>
<Button
variant="ghost"
size="icon"
:title="legacyT('编辑提供商')"
@click="$emit('edit', provider)"
>
<Edit class="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="icon"
:title="legacyT(provider.is_active ? '点击停用' : '点击启用')"
:aria-label="legacyT(provider.is_active ? '点击停用' : '点击启用')"
data-testid="provider-toggle-active"
@click="$emit('toggleStatus', provider)"
>
<Power class="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="icon"
:title="legacyT('关闭')"
@click="$emit('close')"
>
<X class="w-4 h-4" />
</Button>
</div>
</div>
<div
v-if="provider.website"
class="-mt-0.5"
>
<a
:href="provider.website"
target="_blank"
rel="noopener noreferrer"
class="text-xs text-muted-foreground hover:text-primary hover:underline transition-colors truncate block"
:title="provider.website"
>{{ provider.website }}</a>
</div>
<div class="flex items-center gap-1.5 flex-wrap mt-3">
<template v-if="loadingProviderEndpoints && endpoints.length === 0">
<span class="inline-flex items-center gap-1.5 text-xs text-muted-foreground">
<Loader2 class="w-3.5 h-3.5 animate-spin" />
{{ legacyT('加载端点中') }}
</span>
</template>
<template v-else>
<template
v-for="endpoint in endpoints"
:key="endpoint.id"
>
<span
class="text-xs px-2 py-0.5 rounded-md border border-border bg-background hover:bg-accent hover:border-accent-foreground/20 cursor-pointer transition-colors font-medium"
:class="{ 'opacity-40': !endpoint.is_active }"
:title="legacyT('编辑端点')"
@click="$emit('editEndpoint', endpoint)"
>{{ formatApiFormat(endpoint.api_format) }}</span>
</template>
<span
v-if="endpoints.length > 0"
class="text-xs px-2 py-0.5 rounded-md border border-dashed border-border hover:bg-accent hover:border-accent-foreground/20 cursor-pointer transition-colors text-muted-foreground"
:title="legacyT('编辑端点')"
@click="$emit('addEndpoint')"
>{{ legacyT('编辑') }}</span>
<Button
v-else
variant="outline"
size="sm"
class="h-7 text-xs"
@click="$emit('addEndpoint')"
>
<Plus class="w-3 h-3 mr-1" />
{{ legacyT('添加 API 端点') }}
</Button>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { Edit, GitBranch, Globe, Layers, Loader2, Plus, Power, Shuffle, X } from 'lucide-vue-next'
import Button from '@/components/ui/button.vue'
import Badge from '@/components/ui/badge.vue'
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui'
import { useI18n } from '@/i18n'
import { formatApiFormat } from '@/api/endpoints/types/api-format'
import type { ProviderEndpoint, ProviderWithEndpointsSummary } from '@/api/endpoints'
import ProxyNodeSelect from './ProxyNodeSelect.vue'
const props = defineProps<{
provider: ProviderWithEndpointsSummary
endpoints: ProviderEndpoint[]
loadingProviderEndpoints: boolean
systemFormatConversionEnabled: boolean
hasFailoverRules: boolean
providerProxyPopoverOpen: boolean
providerProxyNodeName: string
savingProviderProxy: boolean
}>()
defineEmits<{
(e: 'toggleFormatConversion'): void
(e: 'toggleKeepPriorityOnConversion'): void
(e: 'openFailoverRules'): void
(e: 'update:providerProxyPopoverOpen', value: boolean): void
(e: 'setProviderProxy', value: string): void
(e: 'clearProviderProxy'): void
(e: 'edit', provider: ProviderWithEndpointsSummary): void
(e: 'toggleStatus', provider: ProviderWithEndpointsSummary): void
(e: 'close'): void
(e: 'editEndpoint', endpoint: ProviderEndpoint): void
(e: 'addEndpoint'): void
}>()
const { legacyT } = useI18n()
const formatConversionTitle = computed(() => {
if (props.systemFormatConversionEnabled) return legacyT('系统级格式转换已启用')
if (props.provider.enable_format_conversion) return legacyT('已启用格式转换(点击关闭)')
return legacyT('启用格式转换')
})
const formatConversionAvailable = computed(() => (
props.provider.enable_format_conversion || props.systemFormatConversionEnabled
))
const keepPriorityTitle = computed(() => {
if (!formatConversionAvailable.value) return legacyT('请先启用格式转换')
return props.provider.keep_priority_on_conversion
? legacyT('已启用格式转换保持优先级(点击关闭)')
: legacyT('启用格式转换保持优先级')
})
</script>