<template>
<div class="plugin-modelmanager">
<plugin-panel
:title="title"
:fixed-name="PLUGIN_NAME.ModelManager"
:fixedPanels="fixedPanels"
@close="pluginPanelClosed"
>
<template #header>
<svg-button
v-if="showExportSql"
class="flow-download-icon"
name="flow-download"
placement="bottom"
tips="导出SQL"
@click="exportModel"
></svg-button>
</template>
<template #content>
<tiny-button class="add-model" @click="handleAddModel"> <svg-icon name="add"></svg-icon>新建模型 </tiny-button>
<div class="model-manager-search" clearable placeholder="搜索">
<tiny-search v-model="localKeyword">
<template #prefix>
<tiny-icon-search />
</template>
</tiny-search>
</div>
<div class="model-list">
<template v-if="models.length">
<div
v-for="model in filteredModels"
:key="model.id"
:class="['model-item', { active: selectedModel?.id === model.id }]"
>
<div class="item-label">
<div class="item-name">
<svg-icon name="plugin-icon-modelmanager" class="plugin-icon-modelmanager"> </svg-icon>
{{ model.nameCn }}
</div>
<div class="item-handler">
<svg-button
class="set-page"
:hoverBgColor="false"
tips="设置模型"
name="setting"
@mousedown.stop.prevent="selectModel(model)"
>
</svg-button>
</div>
</div>
</div>
</template>
<search-empty :isShow="!models.length" />
</div>
</template>
</plugin-panel>
<model-setting
:model="selectedModel"
:models="models"
:showExport="showExportSql"
@deleteCallback="handleDeleteModel"
@editCallback="editCallback"
@exportModel="exportModel"
></model-setting>
</div>
</template>
<script setup>
import { ref, reactive, provide, computed, onMounted } from 'vue'
import { TinySearch, Modal, TinyButton } from '@opentiny/vue'
import { IconSearch } from '@opentiny/vue-icon'
import { PluginPanel, SvgButton, SearchEmpty } from '@opentiny/tiny-engine-common'
import { useLayout, useEnv } from '@opentiny/tiny-engine-meta-register'
import ModelSetting, { openModelSettingPanel, closeModelSettingPanel } from './components/ModelSetting.vue'
import { getModelList, deleteModel, getModelSql, getModelSqlById } from './composable/useModelManager'
defineProps({
title: {
type: String,
default: '模型管理'
},
fixedPanels: {
type: Array
}
})
const emit = defineEmits(['close'])
const { PLUGIN_NAME } = useLayout()
const { VITE_ORIGIN } = useEnv()
const TinyIconSearch = IconSearch()
const selectedModel = ref(null)
const models = ref([])
const localKeyword = ref('')
const showExportSql = ref(false)
const panelState = reactive({
emitEvent: emit
})
provide('panelState', panelState)
const filteredModels = computed(() => {
if (!localKeyword.value) return models.value
return models.value.filter(
(model) =>
(model.nameCn || '').toLowerCase().includes(localKeyword.value.toLowerCase()) ||
(model.description || '').toLowerCase().includes(localKeyword.value.toLowerCase())
)
})
const pluginPanelClosed = () => {
emit('close')
closeModelSettingPanel()
}
const selectModel = (model) => {
if (model.parameters?.length > 0) {
model.parameters.forEach((item) => {
item.isModel = false
if (item.type === 'Enum') {
item.options = typeof item.options === 'string' ? JSON.parse(item.options) : item.options || []
}
if (item.type === 'ModelRef') {
item.isModel = true
item.defaultValue = Number(item.defaultValue)
}
})
}
selectedModel.value = model
openModelSettingPanel()
}
const handleAddModel = () => {
selectedModel.value = {
id: null,
nameCn: '',
nameEn: '',
modelUrl: `${VITE_ORIGIN}platform-center/api/model-data`,
description: '',
parameters: []
}
openModelSettingPanel()
}
const handleDeleteModel = async (model) => {
const name = model.nameCn || model.nameEn || model.id
try {
const result = await Modal.confirm({
title: '提示',
message: `确认删除模型「${name}」吗?该操作不可恢复。`
})
const confirmed = result === 'confirm' || result?.action === 'confirm' || result === true || result === undefined
if (!confirmed) return
await deleteModel(model.id)
const index = models.value.findIndex((m) => m.id === model.id)
if (index > -1) {
models.value.splice(index, 1)
if (selectedModel.value?.id === model.id) selectedModel.value = null
}
} catch (error) {
}
}
const getModelLists = async () => {
const data = await getModelList({
currentPage: 1,
pageSize: 500
})
models.value = data.records || []
}
const exportModel = async (id) => {
const sqlContent = id ? await getModelSqlById(id) : await getModelSql()
const blob = new Blob([sqlContent], { type: 'text/sql' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `${id ? selectedModel.value?.nameEn : 'models'}.sql`
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
}
const editCallback = async () => {
await getModelLists()
selectedModel.value = null
closeModelSettingPanel()
}
onMounted(async () => {
await getModelLists()
})
</script>
<style lang="less" scoped>
.plugin-modelmanager {
height: 100%;
width: 280px;
.add-model {
margin: 0 12px 12px 12px;
width: calc(100% - 24px);
}
.model-manager-search {
padding: 0 12px 12px 12px;
}
.model-list {
border-top: 1px solid var(--te-model-common-border-color-divider);
padding-top: 12px;
overflow-y: scroll;
color: var(--te-common-text-primary);
font-size: var(--te-base-font-size-base);
.model-item {
box-shadow: var(--te-model-manage-input-border-color) 0, -1px;
height: 24px;
line-height: 24px;
align-items: center;
display: grid;
padding: 0 12px;
position: relative;
color: var(--te-model-manage-text-color);
cursor: pointer;
&:hover,
&.active {
background: var(--te-model-manage-tree-node-bg-color-hover);
.item-handler {
display: inline-block;
}
}
.item-label {
overflow: hidden;
text-overflow: ellipsis;
display: flex;
align-items: center;
justify-content: space-between;
color: var(--te-model-manage-draggable-text-color);
.item-name {
display: flex;
align-items: center;
}
.plugin-icon-modelmanager {
color: var(--te-model-manage-draggable-icon-color);
margin-right: 8px;
}
}
.item-handler {
height: 24px;
line-height: 24px;
display: none;
}
}
}
}
</style>