438 lines
14 KiB
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>
|