mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-03 16:07:46 +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 ? '修改模型配置和价格信息' : ''"
|
:description="isEditMode ? '修改模型配置和价格信息' : ''"
|
||||||
:icon="isEditMode ? SquarePen : Layers"
|
:icon="isEditMode ? SquarePen : Layers"
|
||||||
:size="isEditMode ? '4xl' : '3xl'"
|
:size="isEditMode ? '4xl' : '3xl'"
|
||||||
|
:z-index="zIndex"
|
||||||
@update:model-value="handleDialogUpdate"
|
@update:model-value="handleDialogUpdate"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -869,6 +870,8 @@ import {
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
open: boolean
|
open: boolean
|
||||||
model?: GlobalModelResponse | null
|
model?: GlobalModelResponse | null
|
||||||
|
// 嵌套在其他弹窗内部时使用更高的层级,避免被父弹窗遮挡
|
||||||
|
zIndex?: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -62,6 +62,16 @@
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- 模型列表 -->
|
<!-- 模型列表 -->
|
||||||
@@ -139,6 +149,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 创建统一模型对话框(嵌套,创建成功后刷新下方模型列表)
|
||||||
|
注意:不使用 v-if 挂载,GlobalModelFormDialog 依赖 open 变化来加载模型目录 -->
|
||||||
|
<GlobalModelFormDialog
|
||||||
|
:open="createModelDialogOpen"
|
||||||
|
:z-index="70"
|
||||||
|
@update:open="createModelDialogOpen = $event"
|
||||||
|
@success="handleGlobalModelCreated"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex items-center justify-between w-full">
|
<div class="flex items-center justify-between w-full">
|
||||||
@@ -170,10 +189,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue'
|
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 Dialog from '@/components/ui/dialog/Dialog.vue'
|
||||||
import Button from '@/components/ui/button.vue'
|
import Button from '@/components/ui/button.vue'
|
||||||
import Input from '@/components/ui/input.vue'
|
import Input from '@/components/ui/input.vue'
|
||||||
|
import GlobalModelFormDialog from '@/features/models/components/GlobalModelFormDialog.vue'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
@@ -227,6 +247,8 @@ const loadingGlobalModels = ref(false)
|
|||||||
const loadingProviderKeys = ref(false)
|
const loadingProviderKeys = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const fetchingAutoMatchedModels = ref(false)
|
const fetchingAutoMatchedModels = ref(false)
|
||||||
|
// 创建统一模型对话框是否打开(嵌套在本弹窗内)
|
||||||
|
const createModelDialogOpen = ref(false)
|
||||||
|
|
||||||
// 数据
|
// 数据
|
||||||
const allGlobalModels = ref<GlobalModelResponse[]>([])
|
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() {
|
async function handleClose() {
|
||||||
if (hasChanges.value) {
|
if (hasChanges.value) {
|
||||||
@@ -517,6 +549,7 @@ watch(
|
|||||||
initialGlobalModelIds.value = new Set()
|
initialGlobalModelIds.value = new Set()
|
||||||
providerKeys.value = []
|
providerKeys.value = []
|
||||||
fetchingAutoMatchedModels.value = false
|
fetchingAutoMatchedModels.value = false
|
||||||
|
createModelDialogOpen.value = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
|
|||||||
+87
@@ -33,6 +33,32 @@ vi.mock('@/features/providers/composables/useUpstreamModelsCache', () => ({
|
|||||||
fetchModels: vi.fn(),
|
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 () => {
|
vi.mock('@/components/ui/dialog/Dialog.vue', async () => {
|
||||||
const { defineComponent, h } = await import('vue')
|
const { defineComponent, h } = await import('vue')
|
||||||
return {
|
return {
|
||||||
@@ -203,3 +229,64 @@ describe('BatchAssignModelsDialog loading', () => {
|
|||||||
expect(visibleModelIds(root)).toEqual(['gm-beta', 'gm-alpha'])
|
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