448 lines
13 KiB
Vue
448 lines
13 KiB
Vue
<script setup lang="ts">
|
|
import type { FormInst } from 'naive-ui'
|
|
import { commonApi } from '@/api/common'
|
|
import { cloneDeep } from 'lodash-es'
|
|
import { Asterisk, Trash } from 'lucide-vue-next'
|
|
import { computed, ref, watch } from 'vue'
|
|
|
|
// 可接受的文件类型
|
|
const props = defineProps({
|
|
modelValue: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
})
|
|
|
|
const emit = defineEmits(['update:modelValue', 'nextStep', 'prevStep'])
|
|
|
|
const message = useMessage()
|
|
|
|
const acceptTypes
|
|
= '.safetensors,.ckpt,.pt,.bin,.pth,.zip,.json,.flow,.lightflow,.yaml,.yml,.onnx,.gguf,.sft'
|
|
const acceptTypesList = [
|
|
'safetensors',
|
|
'ckpt',
|
|
'pt',
|
|
'bin',
|
|
'pth',
|
|
'zip',
|
|
'json',
|
|
'flow',
|
|
'lightflow',
|
|
'yaml',
|
|
'yml',
|
|
'onnx',
|
|
'gguf',
|
|
'sft',
|
|
]
|
|
const modelVersionItem = {
|
|
objectKey: null,
|
|
isEncrypt: 0, // 0不加密
|
|
delFlag: '0', // 0代表存在 2代表删除
|
|
versionName: '', // 版本名称
|
|
modelVersionType: null, // 基础模型
|
|
versionDescription: '', // 版本描述
|
|
filePath: '', // 文件路径
|
|
fileName: '', //
|
|
sampleImagePaths: [], // 第三部的图片路径最多20张,切割
|
|
triggerWords: '', // 触发词
|
|
isPublic: null, // 权限是否公
|
|
isOnlineUse: 1, // 在线使用
|
|
allowFusion: 1, // 是否允许融合
|
|
isFree: 0, // 0免费
|
|
allowDownloadImage: 1, // 允许下载生图
|
|
allowSoftwareUse: 1, // 允许在软件旗下使用
|
|
allowCommercialUse: 1, // 是否允许商用
|
|
allowUsage: 1, // 允许模型转售或者融合出售
|
|
isExclusiveModel: 1, // 是否为独家模型这个字段
|
|
hideImageGenInfo: 0, // 隐藏图片生成信息
|
|
id: null,
|
|
}
|
|
|
|
defineExpose({
|
|
addVersion,
|
|
})
|
|
const loading = ref(false)
|
|
const localForm = computed({
|
|
get() {
|
|
return props.modelValue
|
|
},
|
|
set(value) {
|
|
emit('update:modelValue', value)
|
|
},
|
|
})
|
|
|
|
watch(
|
|
() => localForm.value,
|
|
(newVal) => {
|
|
emit('update:modelValue', newVal)
|
|
},
|
|
{ immediate: true, deep: true },
|
|
)
|
|
|
|
const formRefs = ref<(FormInst | null)[]>([])
|
|
function setFormRef(el: FormInst | null, index: number) {
|
|
if (el) {
|
|
formRefs.value[index] = el
|
|
}
|
|
}
|
|
const rules = {
|
|
versionName: {
|
|
required: true,
|
|
message: '',
|
|
trigger: 'blur',
|
|
},
|
|
modelVersionType: {
|
|
required: true,
|
|
message: '',
|
|
trigger: 'blur',
|
|
},
|
|
}
|
|
function addVersion() {
|
|
const param = cloneDeep(modelVersionItem)
|
|
localForm.value.modelVersionList.unshift(param)
|
|
}
|
|
const originalBtnList = ref([
|
|
{
|
|
label: '免费',
|
|
value: 1,
|
|
},
|
|
{
|
|
label: '会员下载',
|
|
value: 0,
|
|
},
|
|
])
|
|
|
|
async function nextStep() {
|
|
for (let i = 0; i < localForm.value.modelVersionList.length; i++) {
|
|
if (
|
|
localForm.value.modelVersionList[i].delFlag === '0'
|
|
&& localForm.value.modelVersionList[i].fileName === ''
|
|
) {
|
|
return message.error('请上传文件')
|
|
}
|
|
const regex = /[\u4E00-\u9FA5]/ // 匹配汉字的正则表达式
|
|
if (
|
|
localForm.value.modelVersionList[i].delFlag === '0'
|
|
&& !regex.test(localForm.value.modelVersionList[i].versionDescription)
|
|
) {
|
|
return message.error('请用中文填写版本介绍')
|
|
}
|
|
}
|
|
try {
|
|
const promises = formRefs.value
|
|
.filter((form): form is FormInst => form !== null)
|
|
.map(form => form.validate())
|
|
|
|
await Promise.all(promises)
|
|
emit('nextStep')
|
|
}
|
|
catch (errors) {
|
|
console.error('部分表单验证失败:', errors)
|
|
}
|
|
}
|
|
|
|
// 获取上文件的信息
|
|
const uploadRef = ref<InstanceType<typeof FileUpload> | null>(null)
|
|
// const getFileInfo = () => {
|
|
// if (uploadRef.value[0].validRequired()) {
|
|
// const fileInfo = uploadRef.value[0].getUploadInfo()
|
|
// console.log(fileInfo)
|
|
// }
|
|
// }
|
|
|
|
function handleUploadSuccess(fileInfo: {
|
|
objectKey: string
|
|
objectUrl: string
|
|
fileName: string
|
|
currentFileSize: number
|
|
hashCode: string
|
|
}) {
|
|
localForm.value.modelVersionList[uploadFileIndex.value].filePath = fileInfo.objectUrl
|
|
localForm.value.modelVersionList[uploadFileIndex.value].objectKey = fileInfo.objectKey
|
|
localForm.value.modelVersionList[uploadFileIndex.value].fileName = fileInfo.fileName
|
|
localForm.value.modelVersionList[uploadFileIndex.value].fileSize = fileInfo.currentFileSize
|
|
localForm.value.modelVersionList[uploadFileIndex.value].fileHash = fileInfo.hashCode
|
|
// message.success('文件上传成功')
|
|
// 这里可以处理文件上传成功后的逻辑
|
|
// 比如更新表单数据等
|
|
}
|
|
|
|
// 上传文件
|
|
const uploadFileIndex = ref(0)
|
|
async function triggerFileInput(index: number) {
|
|
uploadRef.value[0].triggerFileSelect()
|
|
uploadFileIndex.value = index
|
|
}
|
|
|
|
function prevStep() {
|
|
emit('prevStep')
|
|
}
|
|
|
|
const baseModelTypeList = ref([])
|
|
async function getDictType() {
|
|
try {
|
|
const res = await commonApi.dictType({ type: 'mode_version_type' })
|
|
if (res.code === 200) {
|
|
baseModelTypeList.value = res.data
|
|
}
|
|
}
|
|
catch (error) {
|
|
console.log(error)
|
|
}
|
|
}
|
|
getDictType()
|
|
|
|
function computedDelFlag() {
|
|
return localForm.value.modelVersionList.filter(item => item.delFlag === '0')
|
|
}
|
|
function onDelete(index: number) {
|
|
if (computedDelFlag().length === 1)
|
|
return
|
|
localForm.value.modelVersionList[index].delFlag = '2'
|
|
}
|
|
|
|
function handledeleteFile(item: any) {
|
|
item.filePath = ''
|
|
item.fileKey = ''
|
|
item.fileName = ''
|
|
item.fileSize = ''
|
|
item.hashCode = ''
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<template
|
|
v-for="(item, index) in localForm.modelVersionList"
|
|
:key="index"
|
|
class="bg-gray-100 p-4 mt-4 rounded-lg"
|
|
>
|
|
<div v-if="item.delFlag === '0'" class="bg-gray-100 p-4 rounded-lg mt-4 relative">
|
|
<div class="absolute -right-10 top-4 cursor-pointer">
|
|
<Trash class="cursor-pointer" @click="onDelete(index)" />
|
|
</div>
|
|
<n-form
|
|
:ref="(el) => setFormRef(el, index)"
|
|
:label-width="80"
|
|
:model="localForm.modelVersionList[index]"
|
|
:rules="rules"
|
|
size="large"
|
|
>
|
|
<n-form-item label="版本名称" path="versionName">
|
|
<n-input v-model:value="item.versionName" placeholder="请输入版本名" />
|
|
</n-form-item>
|
|
<n-form-item label="基础模型" path="modelVersionType">
|
|
<n-select
|
|
v-model:value="item.modelVersionType"
|
|
label-field="dictLabel"
|
|
value-field="dictValue"
|
|
placeholder="请选择基础模型"
|
|
:options="baseModelTypeList"
|
|
/>
|
|
</n-form-item>
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex">
|
|
上传文件 <Asterisk :size="10" color="#ff0000" class="mt-1" />
|
|
</div>
|
|
<div>
|
|
<n-checkbox
|
|
v-model:checked="item.isEncrypt"
|
|
:checked-value="1"
|
|
:unchecked-value="0"
|
|
label="是否加密"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="item.fileName"
|
|
class="flex justify-between items-center bg-white p-3 mt-2 rounded-lg"
|
|
>
|
|
<div
|
|
class="bg-[#d8e5fd] text-[12px] text-[#3162ff] w-16 h-7 rounded-lg flex justify-center items-center"
|
|
>
|
|
100%
|
|
</div>
|
|
<div class="flex-1 flex items-center line-clamp">
|
|
{{ item.fileName }}
|
|
</div>
|
|
<div>
|
|
<Trash
|
|
class="cursor-pointer"
|
|
@click="handledeleteFile(item)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<n-spin :show="loading">
|
|
<div class="upload-content">
|
|
<div
|
|
class="flex flex-col justify-center items-center w-30 h-40 border border-dashed mt-2 rounded-lg bg-white"
|
|
>
|
|
<div
|
|
class="w-24 bg-gradient-to-r from-[#2D28FF] to-[#1A7DFF] h-8 text-white rounded-sm bg-[#3162ff] cursor-pointer flex justify-center items-center"
|
|
@click="triggerFileInput(index)"
|
|
>
|
|
上传文件
|
|
</div>
|
|
<div class="my-3">
|
|
点击上传文件
|
|
</div>
|
|
<div class="text-[#999999] text-xs">
|
|
.safetensors/.ckpt/.pt/.bin/.pth/.zip/.json/.flow/.lightflow/.yaml/.yml/.onnx/.gguf/.sft
|
|
</div>
|
|
</div>
|
|
<fileUpload
|
|
ref="uploadRef"
|
|
:verify-hash="true"
|
|
:verify-name="true"
|
|
type="model"
|
|
:accept="acceptTypesList"
|
|
:required="true"
|
|
:file-size="30000"
|
|
@upload-success="handleUploadSuccess"
|
|
/>
|
|
</div>
|
|
</n-spin>
|
|
</div>
|
|
<div class="flex mt-6">
|
|
版本介绍 <Asterisk :size="10" color="#ff0000" class="mt-1" />
|
|
</div>
|
|
<div class="bg-white p-3 mt-2 rounded-lg">
|
|
<client-only>
|
|
<WangEditor v-model="item.versionDescription" />
|
|
</client-only>
|
|
</div>
|
|
<div class="mt-6">
|
|
触发词
|
|
</div>
|
|
<div class="-mb-5 text-gray-400 text-[12px]">
|
|
请输入您用来训练的单词
|
|
</div>
|
|
<n-form-item path="triggerWords">
|
|
<n-input v-model:value="item.triggerWords" placeholder="例如: 1boy" />
|
|
</n-form-item>
|
|
|
|
<!-- <div v-if="localForm.modelProduct.modelType === '0'">
|
|
<n-form-item label="采样方法" path="modelVersionType">
|
|
<n-select
|
|
v-model:value="item.modelVersionType"
|
|
label-field="dictLabel"
|
|
value-field="dictValue"
|
|
placeholder="请选择采样方法"
|
|
:options="baseModelTypeList"
|
|
/>
|
|
</n-form-item>
|
|
<div>
|
|
<n-select v-model:value="item.modelVersionType" multiple :options="baseModelTypeList" />
|
|
</div>
|
|
</div> -->
|
|
</n-form>
|
|
|
|
<div class="text-gray-400 text-[12px] my-4">
|
|
许可范围
|
|
</div>
|
|
<div class="flex flex-wrap">
|
|
<div class="w-[50%] mb-2">
|
|
<n-checkbox
|
|
v-model:checked="item.isOnlineUse"
|
|
:checked-value="1"
|
|
:unchecked-value="0"
|
|
:disabled="true"
|
|
label="允许在魔创未来在线使用"
|
|
/>
|
|
</div>
|
|
<div class="w-[50%] mb-2">
|
|
<n-checkbox
|
|
v-model:checked="item.allowDownloadImage"
|
|
:checked-value="1"
|
|
:unchecked-value="0"
|
|
label="允许下载生图"
|
|
/>
|
|
</div>
|
|
<div class="w-[50%] mb-2">
|
|
<n-checkbox
|
|
v-model:checked="item.allowSoftwareUse"
|
|
:checked-value="1"
|
|
:disabled="true"
|
|
:unchecked-value="0"
|
|
label="允许在魔创未来旗下其他产品在线使用"
|
|
/>
|
|
</div>
|
|
<div class="w-[50%] mb-2">
|
|
<n-checkbox
|
|
v-model:checked="item.allowFusion"
|
|
:checked-value="1"
|
|
:unchecked-value="0"
|
|
label="允许进行融合"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-gray-400 text-[12px] my-4">
|
|
商用许可范围
|
|
</div>
|
|
<div class="flex flex-wrap">
|
|
<div class="w-[50%] mb-2">
|
|
<n-checkbox
|
|
v-model:checked="item.allowCommercialUse"
|
|
:checked-value="1"
|
|
:unchecked-value="0"
|
|
label="生成图片可出售或用于商业目的"
|
|
/>
|
|
</div>
|
|
<div class="w-[50%] mb-2">
|
|
<n-checkbox
|
|
v-model:checked="item.allowUsage"
|
|
:checked-value="1"
|
|
:unchecked-value="0"
|
|
label="允许模型转售或融合后出售"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<div class="text-gray-400 text-[12px] my-4">
|
|
独家设置
|
|
</div>
|
|
<div class="flex items-center mb-2">
|
|
<n-checkbox
|
|
v-model:checked="item.isExclusiveModel"
|
|
:checked-value="1"
|
|
:unchecked-value="0"
|
|
/>
|
|
<div class="ml-3 text-[12px] text-gray-500">
|
|
此版本为魔创未来独家模型 *获取更多流量:独家模型规则
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<div class="flex items-center justify-center mt-5">
|
|
<div
|
|
class="flex justify-center items-center mt-5 w-[20%] mx-2 h-10 rounded-lg bg-[#f1f2f7] cursor-pointer"
|
|
@click="prevStep"
|
|
>
|
|
上一步
|
|
</div>
|
|
<div
|
|
class="flex justify-center items-center mt-5 text-white mx-2 w-[20%] h-10 rounded-lg bg-[#3162ff] cursor-pointer"
|
|
@click="nextStep"
|
|
>
|
|
下一步
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.line-clamp {
|
|
margin: 0 10px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font-size: 12px;
|
|
color: rgb(72, 71, 71);
|
|
}
|
|
</style>
|