模型黑名单与密钥模型白名单界面、分组填入、删除弹窗排版
This commit is contained in:
+111
-13
@@ -1,23 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton } from 'naive-ui'
|
||||
import { NButton, useDialog } from 'naive-ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { listAiChannels, listAiKeys, listAiModels } from '@/api/aigateway'
|
||||
import {
|
||||
addAiBlacklist,
|
||||
listAiBlacklist,
|
||||
listAiChannels,
|
||||
listAiKeys,
|
||||
listAiModels,
|
||||
removeAiBlacklist,
|
||||
} 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`
|
||||
|
||||
const endpoints = [
|
||||
{ method: 'POST', path: '/chat/completions', note: 'OpenAI · 流式/非流式' },
|
||||
{ method: 'POST', path: '/responses', note: 'OpenAI Responses · 无状态子集' },
|
||||
{ method: 'POST', path: '/messages', note: 'Claude · 流式/非流式' },
|
||||
{ method: 'POST', path: '/embeddings', note: 'OpenAI Embeddings · 向量化' },
|
||||
@@ -43,9 +52,12 @@ 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
|
||||
}
|
||||
@@ -71,9 +83,43 @@ async function copyModel(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function refreshModels() {
|
||||
void models.run({ silent: true })
|
||||
void blacklist.run({ silent: true })
|
||||
}
|
||||
|
||||
function refreshChannels() {
|
||||
void channels.run({ silent: true })
|
||||
void models.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()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -114,7 +160,7 @@ function refreshChannels() {
|
||||
<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 class="mt-0.5 text-xs text-ink-3">按厂商分组;点击厂商展开,点击模型名复制,✕ 加入黑名单</div>
|
||||
</div>
|
||||
<span class="text-xs text-ink-3">{{ models.data.value?.length ?? '…' }} 个</span>
|
||||
</div>
|
||||
@@ -144,16 +190,67 @@ function refreshChannels() {
|
||||
v-if="expandedVendor === g.vendor"
|
||||
class="flex max-h-36 flex-wrap content-start gap-1.5 overflow-y-auto pb-2 pl-5"
|
||||
>
|
||||
<button
|
||||
<span
|
||||
v-for="id in g.ids"
|
||||
:key="id"
|
||||
type="button"
|
||||
class="mono cursor-pointer rounded-[5px] border border-line bg-card px-2 py-0.5 text-[11.5px] hover:border-accent hover:text-accent"
|
||||
:title="`复制 ${id}`"
|
||||
@click="copyModel(id)"
|
||||
class="mono inline-flex items-center overflow-hidden rounded-[5px] border border-line bg-card text-[11.5px]"
|
||||
>
|
||||
{{ id }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="cursor-pointer py-0.5 pr-1 pl-2 hover:text-accent"
|
||||
:title="`复制 ${id}`"
|
||||
@click="copyModel(id)"
|
||||
>
|
||||
{{ 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">
|
||||
@@ -170,6 +267,7 @@ function refreshChannels() {
|
||||
:keys="keys.data.value ?? []"
|
||||
:loading="keys.loading.value"
|
||||
:groups="groups"
|
||||
:models="models.data.value ?? []"
|
||||
@changed="keys.run({ silent: true })"
|
||||
/>
|
||||
|
||||
|
||||
@@ -140,8 +140,15 @@ async function remove() {
|
||||
<template #trigger>
|
||||
<NButton size="small" type="error" ghost>删除租户</NButton>
|
||||
</template>
|
||||
删除「{{ config.data.value.alias }}」?将清理本面板中的关联任务、快照、回传事件与 Webhook
|
||||
密钥、告警及 AI 渠道数据;OCI 云端资源不会被删除(云端日志回传链路请先在“其他”页销毁)。
|
||||
<div class="max-w-80 text-[13px] leading-relaxed">
|
||||
<div class="font-medium">删除「{{ config.data.value.alias }}」?</div>
|
||||
<div class="mt-1 text-xs text-ink-3">
|
||||
将清理面板内关联数据:后台任务、测活 / 成本快照、回传事件、Webhook 密钥、告警规则与 AI 渠道。
|
||||
</div>
|
||||
<div class="mt-0.5 text-xs text-ink-3">
|
||||
OCI 云端资源不受影响;云端日志回传链路请先在「其他」页销毁。
|
||||
</div>
|
||||
</div>
|
||||
</NPopconfirm>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -222,7 +222,19 @@ function renderActions(row: OciConfigSummary): VNodeChild {
|
||||
trigger: () =>
|
||||
h(NButton, { size: 'tiny', quaternary: true, type: 'error' }, { default: () => '删除' }),
|
||||
default: () =>
|
||||
`删除「${row.alias}」?将清理本面板中的关联任务、快照、回传事件与 Webhook 密钥、告警及 AI 渠道数据;OCI 云端资源不会被删除(云端日志回传链路请先在“其他”页销毁)。`,
|
||||
h('div', { class: 'max-w-80 text-[13px] leading-relaxed' }, [
|
||||
h('div', { class: 'font-medium' }, `删除「${row.alias}」?`),
|
||||
h(
|
||||
'div',
|
||||
{ class: 'mt-1 text-xs text-ink-3' },
|
||||
'将清理面板内关联数据:后台任务、测活 / 成本快照、回传事件、Webhook 密钥、告警规则与 AI 渠道。',
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{ class: 'mt-0.5 text-xs text-ink-3' },
|
||||
'OCI 云端资源不受影响;云端日志回传链路请先在「其他」页销毁。',
|
||||
),
|
||||
]),
|
||||
},
|
||||
),
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user