+4
-125
@@ -1,30 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NSwitch, useDialog } from 'naive-ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import {
|
||||
addAiBlacklist,
|
||||
getAiSettings,
|
||||
listAiBlacklist,
|
||||
listAiChannels,
|
||||
listAiKeys,
|
||||
listAiModels,
|
||||
removeAiBlacklist,
|
||||
updateAiSettings,
|
||||
} from '@/api/aigateway'
|
||||
import AiChannelPanel from '@/components/ai/AiChannelPanel.vue'
|
||||
import AiKeyPanel from '@/components/ai/AiKeyPanel.vue'
|
||||
import { useAsync } from '@/composables/useAsync'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
import type { AiModelBlacklistItem } from '@/types/api'
|
||||
|
||||
const message = useToast()
|
||||
const dialog = useDialog()
|
||||
|
||||
const keys = useAsync(listAiKeys)
|
||||
const channels = useAsync(listAiChannels)
|
||||
const models = useAsync(listAiModels)
|
||||
const blacklist = useAsync(listAiBlacklist)
|
||||
|
||||
const baseUrl = `${window.location.origin}/ai/v1`
|
||||
|
||||
@@ -59,12 +50,9 @@ const modelGroups = computed(() => {
|
||||
.map(([vendor, ids]) => ({ vendor, label: VENDOR_LABEL[vendor] ?? vendor, ids: ids.sort() }))
|
||||
})
|
||||
|
||||
/** 手风琴:同时只展开一个分组(厂商或黑名单),配合展开区限高控制卡片总高度 */
|
||||
/** 手风琴:同时只展开一个厂商,配合展开区限高控制卡片总高度 */
|
||||
const expandedVendor = ref<string | null>(null)
|
||||
|
||||
/** 黑名单在手风琴中的分组 key(厂商 key 取自模型名前缀,不会与此冲突) */
|
||||
const BLACKLIST_KEY = '__blacklist__'
|
||||
|
||||
function toggleVendor(vendor: string) {
|
||||
expandedVendor.value = expandedVendor.value === vendor ? null : vendor
|
||||
}
|
||||
@@ -90,62 +78,9 @@ async function copyModel(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 「过滤弃用模型」开关:开启后已宣布弃用(即使未退役)的模型从列表与路由中排除 */
|
||||
const filterDeprecated = ref(false)
|
||||
void getAiSettings()
|
||||
.then((s) => { filterDeprecated.value = s.filterDeprecated })
|
||||
.catch(() => {})
|
||||
|
||||
async function toggleFilterDeprecated(v: boolean) {
|
||||
filterDeprecated.value = v
|
||||
try {
|
||||
const s = await updateAiSettings({ filterDeprecated: v })
|
||||
filterDeprecated.value = s.filterDeprecated
|
||||
message.success(v ? '已过滤弃用模型(列表与路由同时排除)' : '已恢复展示弃用模型')
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '操作失败')
|
||||
filterDeprecated.value = !v
|
||||
}
|
||||
refreshModels()
|
||||
}
|
||||
|
||||
function refreshModels() {
|
||||
void models.run({ silent: true })
|
||||
void blacklist.run({ silent: true })
|
||||
}
|
||||
|
||||
function refreshChannels() {
|
||||
void channels.run({ silent: true })
|
||||
refreshModels()
|
||||
}
|
||||
|
||||
/** 拉黑:删除全部渠道缓存中的该模型,后续同步 / 探测均过滤 */
|
||||
function banModel(id: string) {
|
||||
dialog.warning({
|
||||
title: '加入模型黑名单',
|
||||
content: `拉黑「${id}」?全部渠道立即剔除该模型,后续同步与探测不再收录。`,
|
||||
positiveText: '拉黑',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await addAiBlacklist(id)
|
||||
message.success(`已拉黑:${id}`)
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '操作失败')
|
||||
}
|
||||
refreshModels()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function unbanModel(b: AiModelBlacklistItem) {
|
||||
try {
|
||||
await removeAiBlacklist(b.id)
|
||||
message.success(`已移出黑名单:${b.name},重新同步渠道后恢复`)
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '操作失败')
|
||||
}
|
||||
refreshModels()
|
||||
void models.run({ silent: true })
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -186,18 +121,9 @@ async function unbanModel(b: AiModelBlacklistItem) {
|
||||
<div class="flex items-center justify-between border-b border-line-soft px-4.5 py-3">
|
||||
<div>
|
||||
<div class="text-sm font-semibold">可用模型</div>
|
||||
<div class="mt-0.5 text-xs text-ink-3">按厂商分组;点击厂商展开,点击模型名复制,✕ 加入黑名单</div>
|
||||
</div>
|
||||
<div class="flex flex-none items-center gap-3">
|
||||
<label
|
||||
class="flex cursor-pointer items-center gap-1.5 text-xs text-ink-3"
|
||||
title="开启后,已宣布弃用(即使未退役)的模型从模型列表与路由中排除;关闭恢复"
|
||||
>
|
||||
过滤弃用
|
||||
<NSwitch size="small" :value="filterDeprecated" @update:value="toggleFilterDeprecated" />
|
||||
</label>
|
||||
<span class="text-xs text-ink-3">{{ models.data.value?.length ?? '…' }} 个</span>
|
||||
<div class="mt-0.5 text-xs text-ink-3">按厂商分组;点击厂商展开,点击模型名复制</div>
|
||||
</div>
|
||||
<span class="text-xs text-ink-3">{{ models.data.value?.length ?? '…' }} 个</span>
|
||||
</div>
|
||||
<div class="px-4.5 py-1.5">
|
||||
<div
|
||||
@@ -238,56 +164,9 @@ async function unbanModel(b: AiModelBlacklistItem) {
|
||||
>
|
||||
{{ id }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="cursor-pointer self-stretch pr-1.5 pl-0.5 text-ink-3 hover:text-err"
|
||||
:title="`加入黑名单:${id}`"
|
||||
@click="banModel(id)"
|
||||
>
|
||||
<svg
|
||||
class="h-2.5 w-2.5"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
>
|
||||
<path d="M18 6 6 18M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="blacklist.data.value?.length" class="border-b border-line-soft py-1 last:border-b-0">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full cursor-pointer items-center gap-2 py-1.5 text-left"
|
||||
@click="toggleVendor(BLACKLIST_KEY)"
|
||||
>
|
||||
<svg
|
||||
class="h-3 w-3 flex-none text-ink-3 transition-transform"
|
||||
:class="expandedVendor === BLACKLIST_KEY ? 'rotate-90' : ''"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
>
|
||||
<path d="m9 18 6-6-6-6" />
|
||||
</svg>
|
||||
<span class="text-[13px] font-medium">模型黑名单</span>
|
||||
<span class="text-xs text-ink-3">{{ blacklist.data.value.length }} 个</span>
|
||||
</button>
|
||||
<div v-if="expandedVendor === BLACKLIST_KEY" class="pb-2 pl-5">
|
||||
<div class="mb-1.5 text-xs text-ink-3">同步与探测均过滤;点击移出,重新同步渠道后恢复入池</div>
|
||||
<div class="flex max-h-36 flex-wrap content-start gap-1.5 overflow-y-auto">
|
||||
<button
|
||||
v-for="b in blacklist.data.value"
|
||||
:key="b.id"
|
||||
type="button"
|
||||
class="mono cursor-pointer rounded-[5px] border border-err/30 bg-err/5 px-2 py-0.5 text-[11.5px] text-ink-3 line-through hover:border-err hover:text-err"
|
||||
:title="`移出黑名单:${b.name}`"
|
||||
@click="unbanModel(b)"
|
||||
>
|
||||
{{ b.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="models.data.value && !models.data.value.length" class="py-2 text-xs text-ink-3">
|
||||
暂无可用模型:请先添加渠道并探测(需租户订阅 GenAI 提供区域)
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user