mcwl-pc/app/components/publishModel/EditVersion.vue

438 lines
14 KiB
Vue

<script setup lang="ts">
import { commonApi } from "@/api/common";
import { uploadImagesInBatches } from "@/utils/uploadImg.ts";
import { cloneDeep } from 'lodash-es';
import { Asterisk, Trash } from "lucide-vue-next";
import type { FormInst } from "naive-ui";
import { computed, ref, watch } from "vue";
const message = useMessage()
// 可接受的文件类型
const props = defineProps({
modelValue: {
type: Object,
required: true,
},
});
const emit = defineEmits(["update:modelValue", "nextStep", "prevStep"]);
const acceptTypes =
".safetensors,.ckpt,.pt,.bin,.pth,.zip,.json,.flow,.lightflow,.yaml,.yml,.onnx,.gguf,.sft";
const modelVersionItem = {
delFlag: '0', // 0代表存在 2代表删除
versionName: "", // 版本名称
modelVersionType: null, // 基础模型
versionDescription: "", // 版本描述
filePath: "", // 文件路径
fileName: "", //
sampleImagePaths:[], // 第三部的图片路径最多20张,切割
triggerWords: "", // 触发词
isPublic: 1, // 权限是否公
isOnlineUse:1, //在线使用
allowFusion: 1, // 是否允许融合
isFree: 0, // 0免费
allowDownloadImage: 1, // 允许下载生图
allowSoftwareUse: 1, // 允许在软件旗下使用
allowCommercialUse: 1, // 是否允许商用
allowUsage: 1, // 允许模型转售或者融合出售
isExclusiveModel: 1, // 是否为独家模型这个字段
hideImageGenInfo:0, //隐藏图片生成信息
id:null
};
const isPublicList = [
{
label: "公开",
value: 1,
},
{
label: "仅自己可见",
value: 0,
},
];
defineExpose({
addVersion,
});
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,
},
]);
function handleIsFree(index: number, value: number) {
localForm.value.modelVersionList[index].isFree = value;
}
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 uploadFileIndex = ref(0);
const fileInput = ref<HTMLInputElement | null>(null);
function triggerFileInput(index: number) {
(fileInput.value as HTMLInputElement)?.click();
uploadFileIndex.value = index;
}
async function handleFileChange(event: Event) {
const target = event.target as HTMLInputElement;
const files = target.files;
if (files && files.length > 0) {
try{
const res = await uploadImagesInBatches(files);
localForm.value.modelVersionList[uploadFileIndex.value].filePath = res[0].url;
localForm.value.modelVersionList[uploadFileIndex.value].fileName = res[0].fileName;
}catch(err){
console.log(err);
}
}
target.value = "";
}
function prevStep() {
emit("prevStep");
}
const baseModelTypeList = ref([]);
async function getDictType() {
try {
const res = await commonApi.dictType({ type: "mode_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'
}
</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">
上传文件 <Asterisk :size="10" color="#ff0000" class="mt-1" />
</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="(item.fileName = ''), (item.filePath = '')"
/>
</div>
</div>
<div v-else>
<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>
</div>
</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> -->
<div class="">权限设置</div>
<div class="mt-1 mb-2 text-gray-400 text-[12px]">可见范围</div>
<div>
<n-radio-group v-model:value="item.isPublic" name="radiogroup">
<n-space>
<n-radio
v-for="(isPublicItem, isPublicIndex) in isPublicList"
:key="isPublicIndex"
:value="isPublicItem.value"
>
{{ isPublicItem.label }}
</n-radio>
</n-space>
</n-radio-group>
</div>
</n-form>
<div v-if="item.isPublic === 1">
<div class="mt-4 mb-1 text-gray-400 text-[12px]">付费设置</div>
<div class="flex justify-center items-center bg-white h-10 rounded-lg">
<div
v-for="(subItem, subIndex) in originalBtnList"
:key="subIndex"
:style="{
backgroundColor:
item.isFree === subItem.value ? 'rgba(49, 98, 255, 0.1)' : '#fff',
color: item.isFree === subItem.value ? '#3162ff' : '#000',
}"
class="flex-1 rounded-lg h-full flex items-center justify-center cursor-pointer"
@click="handleIsFree(index, subItem.value)"
>
{{ subItem.label }}
</div>
</div>
<div class="mt-1 mb-2 text-gray-500 text-[12px]">
选择会员专属或会员下载视为您已经阅读
<span class="text-[#3162ff] cursor-pointer underline"
>《会员模型许可协议》</span
>
并同意其中条款
</div>
<div v-if="item.isFree === 1" class="text-[12px]">
<div>会员下载模型</div>
<div class="text-gray-500">
下载模型需购买会员,在线生图对所有人开放,无生图次数限制;会员下载的模型版本生成图片默认可商用。
</div>
</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.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>
<input
ref="fileInput"
type="file"
class="hidden"
:accept="acceptTypes"
@change="handleFileChange"
/>
</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>