mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-04 00:17:45 +08:00
Merge pull request #868 from AAEE86/feat/batch-assign-create-model
feat(providers): 批量管理模型支持创建统一模型并刷新列表
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
:description="isEditMode ? '修改模型配置和价格信息' : ''"
|
||||
:icon="isEditMode ? SquarePen : Layers"
|
||||
:size="isEditMode ? '4xl' : '3xl'"
|
||||
:z-index="zIndex"
|
||||
@update:model-value="handleDialogUpdate"
|
||||
>
|
||||
<div
|
||||
@@ -869,6 +870,8 @@ import {
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
model?: GlobalModelResponse | null
|
||||
// 嵌套在其他弹窗内部时使用更高的层级,避免被父弹窗遮挡
|
||||
zIndex?: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -62,6 +62,16 @@
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="shrink-0 gap-1.5"
|
||||
data-testid="batch-assign-create-model"
|
||||
@click="openCreateModelDialog"
|
||||
>
|
||||
<Plus class="w-4 h-4" />
|
||||
创建模型
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 模型列表 -->
|
||||
@@ -139,6 +149,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 创建统一模型对话框(嵌套,创建成功后刷新下方模型列表)
|
||||
注意:不使用 v-if 挂载,GlobalModelFormDialog 依赖 open 变化来加载模型目录 -->
|
||||
<GlobalModelFormDialog
|
||||
:open="createModelDialogOpen"
|
||||
:z-index="70"
|
||||
@update:open="createModelDialogOpen = $event"
|
||||
@success="handleGlobalModelCreated"
|
||||
/>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-between w-full">
|
||||
@@ -170,10 +189,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { Layers, Loader2, Search, Check, ListChecks } from 'lucide-vue-next'
|
||||
import { Layers, Loader2, Search, Check, ListChecks, Plus } from 'lucide-vue-next'
|
||||
import Dialog from '@/components/ui/dialog/Dialog.vue'
|
||||
import Button from '@/components/ui/button.vue'
|
||||
import Input from '@/components/ui/input.vue'
|
||||
import GlobalModelFormDialog from '@/features/models/components/GlobalModelFormDialog.vue'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
@@ -227,6 +247,8 @@ const loadingGlobalModels = ref(false)
|
||||
const loadingProviderKeys = ref(false)
|
||||
const saving = ref(false)
|
||||
const fetchingAutoMatchedModels = ref(false)
|
||||
// 创建统一模型对话框是否打开(嵌套在本弹窗内)
|
||||
const createModelDialogOpen = ref(false)
|
||||
|
||||
// 数据
|
||||
const allGlobalModels = ref<GlobalModelResponse[]>([])
|
||||
@@ -422,6 +444,16 @@ async function applyAutoMatchFromKey(key: AutoMatchKey) {
|
||||
}
|
||||
}
|
||||
|
||||
// 打开"创建统一模型"对话框
|
||||
function openCreateModelDialog() {
|
||||
createModelDialogOpen.value = true
|
||||
}
|
||||
|
||||
// 统一模型创建成功:刷新下方全局模型列表,便于直接勾选新模型
|
||||
async function handleGlobalModelCreated() {
|
||||
await loadGlobalModels()
|
||||
}
|
||||
|
||||
// 处理关闭
|
||||
async function handleClose() {
|
||||
if (hasChanges.value) {
|
||||
@@ -517,6 +549,7 @@ watch(
|
||||
initialGlobalModelIds.value = new Set()
|
||||
providerKeys.value = []
|
||||
fetchingAutoMatchedModels.value = false
|
||||
createModelDialogOpen.value = false
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
|
||||
+87
@@ -33,6 +33,32 @@ vi.mock('@/features/providers/composables/useUpstreamModelsCache', () => ({
|
||||
fetchModels: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
vi.mock('@/features/models/components/GlobalModelFormDialog.vue', async () => {
|
||||
const { defineComponent, h } = await import('vue')
|
||||
return {
|
||||
default: defineComponent({
|
||||
name: 'GlobalModelFormDialogStub',
|
||||
props: {
|
||||
open: { type: Boolean, default: false },
|
||||
model: { type: Object, default: null },
|
||||
zIndex: { type: Number, default: undefined },
|
||||
},
|
||||
emits: ['update:open', 'success'],
|
||||
setup(props, { emit }) {
|
||||
return () => {
|
||||
if (!props.open) return null
|
||||
return h('div', { 'data-testid': 'global-model-form-dialog-stub' }, [
|
||||
h('button', {
|
||||
type: 'button',
|
||||
'data-testid': 'global-model-form-dialog-stub-success',
|
||||
onClick: () => emit('success'),
|
||||
}, 'submit'),
|
||||
])
|
||||
}
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
vi.mock('@/components/ui/dialog/Dialog.vue', async () => {
|
||||
const { defineComponent, h } = await import('vue')
|
||||
return {
|
||||
@@ -203,3 +229,64 @@ describe('BatchAssignModelsDialog loading', () => {
|
||||
expect(visibleModelIds(root)).toEqual(['gm-beta', 'gm-alpha'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('BatchAssignModelsDialog create model entry', () => {
|
||||
async function mountDialog() {
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
const app = createApp(defineComponent({
|
||||
setup() {
|
||||
return () => h(BatchAssignModelsDialog, {
|
||||
open: true,
|
||||
providerId: 'provider-1',
|
||||
})
|
||||
},
|
||||
}))
|
||||
app.mount(root)
|
||||
mountedApps.push({ app, root })
|
||||
await settle()
|
||||
return root
|
||||
}
|
||||
|
||||
it('opens the unified model dialog from the create button', async () => {
|
||||
const root = await mountDialog()
|
||||
expect(root.querySelector('[data-testid="global-model-form-dialog-stub"]')).toBeNull()
|
||||
|
||||
const createButton = root.querySelector('[data-testid="batch-assign-create-model"]') as HTMLButtonElement
|
||||
expect(createButton).toBeTruthy()
|
||||
createButton.click()
|
||||
await settle()
|
||||
|
||||
expect(root.querySelector('[data-testid="global-model-form-dialog-stub"]')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('refreshes the global model list after a model is created', async () => {
|
||||
globalModelMocks.getGlobalModels
|
||||
.mockResolvedValueOnce({
|
||||
models: [createGlobalModel('gm-old', 'old-model', 'Old Model')],
|
||||
total: 1,
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
models: [
|
||||
createGlobalModel('gm-old', 'old-model', 'Old Model'),
|
||||
createGlobalModel('gm-new', 'new-model', 'New Model'),
|
||||
],
|
||||
total: 2,
|
||||
})
|
||||
|
||||
const root = await mountDialog()
|
||||
expect(visibleModelIds(root)).toEqual(['gm-old'])
|
||||
|
||||
const createButton = root.querySelector('[data-testid="batch-assign-create-model"]') as HTMLButtonElement
|
||||
createButton.click()
|
||||
await settle()
|
||||
|
||||
const successButton = root.querySelector('[data-testid="global-model-form-dialog-stub-success"]') as HTMLButtonElement
|
||||
expect(successButton).toBeTruthy()
|
||||
successButton.click()
|
||||
await settle()
|
||||
|
||||
expect(globalModelMocks.getGlobalModels).toHaveBeenCalledTimes(2)
|
||||
expect(visibleModelIds(root)).toEqual(['gm-new', 'gm-old'])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user