V 2.2.7 lama iopaint 去水印

This commit is contained in:
2024-06-06 13:12:04 +08:00
parent 8b011856c2
commit f27ec08724
39 changed files with 2495 additions and 547 deletions
+230 -170
View File
@@ -1,190 +1,250 @@
<template>
<n-tabs animated default-value="inGetFrame">
<n-tab-pane size="large" name="draftGetFrame" tab="剪映草稿抽帧">
<n-spin :show="show">
<template #description>
正在抽帧
</template>
<n-space vertical>
<div style="color: red;">
注意该项是使用剪映内的草稿进行分镜
</div>
<n-select v-model:value="selectedValue" filterable placeholder="选择草稿" :options="options" />
<n-button round type="primary" @click="getFrameFunc">开始抽帧</n-button>
</n-space>
</n-spin>
<n-divider />
</n-tab-pane>
<n-tab-pane name="inGetFrame" tab="软件内抽帧">
<div style="color: red; font-size: large;">
注意该功能需要对环境要求较高环境需要自行安装第一次执行需要下载模型需要魔法<span class="url_class" @click="OpenTeachDoc">环境安装教程</span>
<br>
分割敏感度值越小分割越精细值越大分割越粗糙
</div>
<n-form style="margin-top: 50px;" ref="formRef" label-placement="left" inline :model="frameValue"
:rules="rules" size="medium">
<n-form-item label="分割敏感度" path="sensitivity" style="width: 300px;">
<n-slider v-model:value=frameValue.sensitivity show-tooltip />
</n-form-item>
<n-form-item label="分镜视频地址" path="video_path">
<n-input v-model:value="frameValue.video_path" placeholder="选择或输入要分镜的视频地址" style="width: 300px;" />
<n-button type="primary" @click="GetVideoFile">
选择文件(MP4)
</n-button>
</n-form-item>
<n-form-item>
<n-button type="info" @click="StartStoryboarding" :loading="storyLoading">
开始分镜
</n-button>
</n-form-item>
</n-form>
<n-divider />
</n-tab-pane>
</n-tabs>
<n-code style="padding: 0; font-size: small;" :code="code" language="js" word-wrap />
<n-tabs animated default-value="inGetFrame">
<n-tab-pane size="large" name="draftGetFrame" tab="剪映草稿抽帧">
<n-spin :show="show">
<template #description> 正在抽帧 </template>
<n-space vertical>
<div style="color: red">注意该项是使用剪映内的草稿进行分镜</div>
<n-select
v-model:value="selectedValue"
filterable
placeholder="选择草稿"
:options="options"
/>
<div>
<n-button round type="primary" @click="getFrameFunc">开始抽帧</n-button>
<n-button style="margin-left: 10px" round type="primary" @click="openExportFolder"
>打开抽帧文件夹</n-button
>
</div>
</n-space>
</n-spin>
<n-divider />
</n-tab-pane>
<n-tab-pane name="inGetFrame" tab="软件内抽帧">
<div style="color: red; font-size: large">
注意该功能需要对环境要求较高环境需要自行安装第一次执行需要下载模型需要魔法<span
class="url_class"
@click="OpenTeachDoc"
>环境安装教程</span
>
<br />
分割敏感度值越小分割越精细值越大分割越粗糙
</div>
<n-form
style="margin-top: 50px"
ref="formRef"
label-placement="left"
inline
:model="frameValue"
:rules="rules"
size="medium"
>
<n-form-item label="分割敏感度" path="sensitivity" style="width: 300px">
<n-slider v-model:value="frameValue.sensitivity" show-tooltip />
</n-form-item>
<n-form-item label="分镜视频地址" path="video_path">
<n-input
v-model:value="frameValue.video_path"
placeholder="选择或输入要分镜的视频地址"
style="width: 300px"
/>
<n-button type="primary" @click="GetVideoFile"> 选择文件(MP4) </n-button>
</n-form-item>
<n-form-item>
<n-button type="info" @click="StartStoryboarding" :loading="storyLoading">
开始分镜
</n-button>
</n-form-item>
<n-form-item>
<n-button type="info" @click="openExportFolder" :loading="storyLoading">
打开抽帧文件夹
</n-button>
</n-form-item>
</n-form>
<n-divider />
</n-tab-pane>
</n-tabs>
<n-code style="padding: 0; font-size: small" :code="code" language="js" word-wrap />
</template>
<script>
import { defineComponent, ref, onMounted, toRaw } from "vue";
import { NSelect, NButton, useMessage, NSpin, NDivider, NCode, NTabs, NTabPane, NSpace, NForm, NFormItem, NSlider, NInput } from "naive-ui"
import { DEFINE_STRING } from "../../../../define/define_string.js"
import { defineComponent, ref, onMounted, toRaw } from 'vue'
import {
NSelect,
NButton,
useMessage,
NSpin,
NDivider,
NCode,
NTabs,
NTabPane,
NSpace,
NForm,
NFormItem,
NSlider,
NInput
} from 'naive-ui'
import { DEFINE_STRING } from '../../../../define/define_string.js'
export default defineComponent({
components: {
NSelect, NButton, NSpin, NDivider, NCode, NTabs, NTabPane, NSpace, NForm, NFormItem, NSlider, NInput
},
setup() {
let options = [];
let out_dir = ref(null);
let selectedValue = ref(null);
let show = ref(false);
let code = ref("");
const message = useMessage();
let storyLoading = ref(false);
components: {
NSelect,
NButton,
NSpin,
NDivider,
NCode,
NTabs,
NTabPane,
NSpace,
NForm,
NFormItem,
NSlider,
NInput
},
setup() {
let options = []
let out_dir = ref(null)
let selectedValue = ref(null)
let show = ref(false)
let code = ref('')
const message = useMessage()
let storyLoading = ref(false)
let frameValue = ref({
sensitivity: 30,
video_path: null
let frameValue = ref({
sensitivity: 30,
video_path: null
})
onMounted(() => {
window.api.getDraftFileList((value) => {
value.forEach((element) => {
let obj = {
label: element,
value: element
}
options.push(obj)
})
onMounted(() => {
window.api.getDraftFileList((value) => {
value.forEach(element => {
let obj = {
label: element,
value: element
}
options.push(obj)
});
})
window.api.setEventListen([DEFINE_STRING.GET_FRAME_RETUN], (value) => {
if (value.code == 0) {
message.error(value.message);
code.value = code.value + "\n" + value.data;
return;
}
// 完成
if (value.type == 0) {
storyLoading.value = false;
message.success("分镜抽帧完成");
}
code.value = code.value + "\n" + value.data;
})
})
async function getFrameFunc(e) {
out_dir = window.config.project_path
if (selectedValue.value == null || selectedValue.value == undefined) {
message.error("请选择剪映草稿和输出草稿")
return;
}
show.value = true
// 抽帧
await window.api.getFrame([selectedValue.value, out_dir], (value) => {
if (value.code == 0) {
message.error("抽帧失败");
code.value = value.message;
return;
}
message.success("抽帧成功");
code.value = value.data;
show.value = false;
})
})
window.api.setEventListen([DEFINE_STRING.GET_FRAME_RETUN], (value) => {
if (value.code == 0) {
message.error(value.message)
code.value = code.value + '\n' + value.data
return
}
function selectExportFolder(e) {
window.api.selectFolder(null, (value) => {
if (value.length <= 0) {
message.error("必须选择输出文件夹位置");
return;
}
out_dir.value = value[0]
})
// 完成
if (value.type == 0) {
storyLoading.value = false
message.success('分镜抽帧完成')
}
code.value = code.value + '\n' + value.data
})
})
/**
* 选择指定的视频文件
*/
async function GetVideoFile() {
await window.api.SelectFile(['mp4'], (value) => {
debugger
if (value.code == 0) {
message.error(value.message);
return;
}
frameValue.value.video_path = value.value;
});
}
/**
* 开始分镜执行分镜任务
*/
async function StartStoryboarding() {
storyLoading.value = true;
if (frameValue.value.video_path == null) {
message.error("选择分镜的视频地址");
return;
}
debugger
if (toRaw(frameValue.value).video_path.split('.')[toRaw(frameValue.value).video_path.split('.').length - 1].toUpperCase() != "MP4") {
message.error("目前只支持MP4格式");
return;
}
await window.api.StartStoryboarding(toRaw(frameValue.value))
}
/**
* 打开环境安装网站
*/
function OpenTeachDoc() {
window.api.OpenUrl("https://pvwu1oahp5m.feishu.cn/docx/VrBVd2KUDosmNfxat3OceWuInjd?from=from_copylink");
}
return {
selectedValue,
options,
out_dir,
getFrameFunc,
selectExportFolder,
show,
code,
frameValue,
GetVideoFile,
StartStoryboarding,
OpenTeachDoc,
storyLoading
async function getFrameFunc(e) {
out_dir = window.config.project_path
if (selectedValue.value == null || selectedValue.value == undefined) {
message.error('请选择剪映草稿和输出草稿')
return
}
show.value = true
// 抽帧
await window.api.getFrame([selectedValue.value, out_dir], (value) => {
if (value.code == 0) {
message.error('抽帧失败')
code.value = value.message
return
}
message.success('抽帧成功')
code.value = value.data
show.value = false
})
}
});
function selectExportFolder(e) {
window.api.selectFolder(null, (value) => {
if (value.length <= 0) {
message.error('必须选择输出文件夹位置')
return
}
out_dir.value = value[0]
})
}
/**
* 选择指定的视频文件
*/
async function GetVideoFile() {
await window.api.SelectFile(['mp4'], (value) => {
debugger
if (value.code == 0) {
message.error(value.message)
return
}
frameValue.value.video_path = value.value
})
}
/**
* 开始分镜执行分镜任务
*/
async function StartStoryboarding() {
storyLoading.value = true
if (frameValue.value.video_path == null) {
message.error('选择分镜的视频地址')
return
}
debugger
if (
toRaw(frameValue.value)
.video_path.split('.')
[toRaw(frameValue.value).video_path.split('.').length - 1].toUpperCase() != 'MP4'
) {
message.error('目前只支持MP4格式')
return
}
await window.api.StartStoryboarding(toRaw(frameValue.value))
}
/**
* 打开环境安装网站
*/
function OpenTeachDoc() {
window.api.OpenUrl(
'https://pvwu1oahp5m.feishu.cn/docx/VrBVd2KUDosmNfxat3OceWuInjd?from=from_copylink'
)
}
function openExportFolder() {
window.api.OpenFolder("input_crop")
}
return {
selectedValue,
options,
out_dir,
getFrameFunc,
selectExportFolder,
show,
code,
frameValue,
GetVideoFile,
StartStoryboarding,
OpenTeachDoc,
storyLoading,
openExportFolder
}
}
})
</script>
<style>
.url_class {
color: blue;
color: blue;
}
.url_class:hover {
color: brown;
cursor: pointer;
color: brown;
cursor: pointer;
}
</style>
</style>
@@ -1,99 +1,123 @@
<template>
<n-spin :show="show">
<template #description>
正在反推
</template>
<n-button type="info" @click="PushBackPrompt"> 开始反推 </n-button>
<n-button type="error" style="margin-left: 50px;" @click="DeleteBadPrompt"> 一键去除所有不想要的值 </n-button>
<n-button type="warning" style="margin-left: 50px;" @click="ManagePrompt"> 管理不想要的值 </n-button>
<n-divider />
</n-spin>
<n-code style="padding: 0;" :code="code" language="js" word-wrap />
<n-spin :show="show">
<template #description> 正在反推 </template>
<n-button type="info" @click="RemoveWather"> 去水印 </n-button>
<n-button type="info" style="margin-left: 20px" @click="PushBackPrompt"> 开始反推 </n-button>
<n-button type="error" style="margin-left: 20px" @click="DeleteBadPrompt">
一键去除所有不想要的值
</n-button>
<n-button type="warning" style="margin-left: 20px" @click="ManagePrompt">
管理不想要的值
</n-button>
<n-divider />
</n-spin>
<n-code style="padding: 0" :code="code" language="js" word-wrap />
</template>
<script>
import { defineComponent, ref, onMounted, h, toRaw } from "vue";
import { NButton, useMessage, useDialog, NDivider, NCode, NSpin } from "naive-ui"
import { DEFINE_STRING } from "../../../../define/define_string";
import ManageBadPrompt from "../Components/ManageBadPrompt.vue"
import { defineComponent, ref, onMounted, h, toRaw } from 'vue'
import { NButton, useMessage, useDialog, NDivider, NCode, NSpin } from 'naive-ui'
import { DEFINE_STRING } from '../../../../define/define_string'
import ManageBadPrompt from '../Components/ManageBadPrompt.vue'
import GetWaterMask from '../Watermark/GetWaterMask.vue'
export default defineComponent({
components: {
NButton, NDivider, NCode, NSpin
},
setup() {
let dialog = useDialog();
let code = ref("");
let show = ref(false);
let promprRef = ref();
let message = useMessage();
components: {
NButton,
NDivider,
NCode,
NSpin
},
setup() {
let dialog = useDialog()
let code = ref('')
let show = ref(false)
let promprRef = ref()
let message = useMessage()
// 开始反推
async function PushBackPrompt() {
show.value = true;
await window.api.PushBackPrompt((value) => {
if (value.code == 0) {
message.error("反推报错");
code.value = value.message;
return;
}
message.success("反推成功");
code.value = value.data;
show.value = false;
})
}
/**
* 管理不想要的值
*/
async function ManagePrompt() {
// 弹窗,管理不想要的值
dialog.create({
showIcon: false,
title: "管理不想要的值",
content: () => h(ManageBadPrompt, { ref: promprRef }),
style: `width : 650px;`,
maskClosable: false,
positiveText: "确定",
negativeText: "取消",
onPositiveClick: async () => {
console.log(toRaw(promprRef.value.tags));
// 将数据写入
await window.api.SaveBadPrompt(toRaw(promprRef.value.tags), (value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message);
return false;
}
message.success("添加成功");
})
return true;
}
})
}
/**
* 一键删除不想要的值
*/
async function DeleteBadPrompt() {
await window.api.DeleteBadPrompt((value) => {
debugger
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("去除成功");
})
}
return {
code,
show,
PushBackPrompt,
ManagePrompt,
DeleteBadPrompt
// 开始反推
async function PushBackPrompt() {
show.value = true
await window.api.PushBackPrompt((value) => {
if (value.code == 0) {
message.error('反推报错')
code.value = value.message
return
}
message.success('反推成功')
code.value = value.data
show.value = false
})
}
/**
* 管理不想要的值
*/
async function ManagePrompt() {
// 弹窗,管理不想要的值
dialog.create({
showIcon: false,
title: '管理不想要的值',
content: () => h(ManageBadPrompt, { ref: promprRef }),
style: `width : 650px;`,
maskClosable: false,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
console.log(toRaw(promprRef.value.tags))
// 将数据写入
await window.api.SaveBadPrompt(toRaw(promprRef.value.tags), (value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
return false
}
message.success('添加成功')
})
return true
}
})
}
/**
* 一键删除不想要的值
*/
async function DeleteBadPrompt() {
await window.api.DeleteBadPrompt((value) => {
debugger
console.log(value)
if (value.code == 0) {
message.error(value.message)
return
}
message.success('去除成功')
})
}
async function RemoveWather() {
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = window.innerWidth * 0.7
let dialogHeight = window.innerHeight * 0.9
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () => h(GetWaterMask, { width: dialogWidth, height: dialogHeight }),
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
maskClosable: false,
onClose: () => {}
})
}
return {
code,
show,
PushBackPrompt,
ManagePrompt,
DeleteBadPrompt,
RemoveWather
}
}
})
</script>
</script>
@@ -0,0 +1,50 @@
<template>
<n-progress type="circle" :percentage="percentage">
<span style="text-align: center; font-size: 20px">{{ text }}</span>
</n-progress>
</template>
<script>
import { ref, h, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
import { NProgress, useMessage } from 'naive-ui'
export default defineComponent({
components: {
NProgress
},
props: ['tatol', 'current'],
setup(props) {
let text = ref('等待中')
let percentage = ref(0)
let tatol = ref(props.tatol ? props.tatol : 1)
let current = ref(props.current ? props.current : 0)
// 更新数据
function modifyTotal(value) {
tatol.value = value
if (!value || value == 0) {
text.value = '等待中'
}
}
function modifyCurrent(value) {
current.value = value ? value : 0
percentage.value = Math.round((current.value / (tatol.value ? tatol.value : 1)) * 100)
if (tatol.value && tatol.value > 0) {
text.value = percentage.value + '%'
}
}
onMounted(async () => {})
return {
text,
percentage,
tatol,
current,
modifyTotal,
modifyCurrent
}
}
})
</script>
@@ -63,7 +63,7 @@ export default defineComponent({
let data = ref(props.initData)
let AnalyzeCharacter = ref(props.Character)
let tagTreeData = ref(props.treeData)
let auto_save = ref(window.config.auto_save ? window.config.auto_save : false)
let auto_save = ref(window.config?.auto_save ? window.config.auto_save : false)
watch(
() => props.initData,
@@ -1,274 +1,318 @@
<template>
<div>
<n-tabs class="card-tabs" default-value="signin" size="large" animated pane-wrapper-style="margin: 0 -4px"
pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;">
<n-tab-pane name="signin" tab="GPT服务商">
<n-form ref="formRef" :model="gpt_select_value" :rules="rules">
<n-form-item label="选择自定义的GPT服务商">
<n-select style="width: 300px;" :options="gpt_options"
v-model:value="gpt_select_key"></n-select>
<n-button color="#e18a3b" style="margin-left: 10px;" @click="EditOption">编辑</n-button>
<n-button color="#984f31" style="margin-left: 10px;" @click="AddOption">添加</n-button>
<n-button type="error" style="margin-left: 10px;" @click="DeleteOption">删除</n-button>
</n-form-item>
<n-form-item path="label" label="GPT 名称">
<n-input v-model:value="gpt_select_value.label"></n-input>
</n-form-item>
<n-form-item path="value" label="GPT请求网址(写全)">
<n-input v-model:value="gpt_select_value.gpt_url"></n-input>
</n-form-item>
<div style="text-align: right;">
<n-button type="primary" @click="SaveGptOption">保存</n-button>
</div>
</n-form>
</n-tab-pane>
<n-tab-pane name="signup" tab="GPT模型">
<n-form ref="modelFormRef" :model="gpt_model_select_value" :rules="modelRules">
<n-form-item label="选择自定义的GPT模型">
<n-select style="width: 300px;" :options="gpt_model_options"
v-model:value="gpt_model_select_key"></n-select>
<n-button color="#e18a3b" style="margin-left: 10px;" @click="EditModelOption">编辑</n-button>
<n-button color="#984f31" style="margin-left: 10px;" @click="AddModelOption">添加</n-button>
<n-button type="error" style="margin-left: 10px;" @click="DeleteModelOption">删除</n-button>
</n-form-item>
<n-form-item path="label" label="GPT 模型名称">
<n-input v-model:value="gpt_model_select_value.label"></n-input>
</n-form-item>
<n-form-item path="value" label="GPT 模型值">
<n-input v-model:value="gpt_model_select_value.value"></n-input>
</n-form-item>
<div style="text-align: right;">
<n-button type="primary" @click="SaveGptModelOption">保存</n-button>
</div>
</n-form>
</n-tab-pane>
</n-tabs>
</div>
<div>
<n-tabs
class="card-tabs"
default-value="signin"
size="large"
animated
pane-wrapper-style="margin: 0 -4px"
pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;"
>
<n-tab-pane name="signin" tab="GPT服务商">
<n-form ref="formRef" :model="gpt_select_value" :rules="rules">
<n-form-item label="选择自定义的GPT服务商">
<n-select
style="width: 300px"
:options="gpt_options"
v-model:value="gpt_select_key"
></n-select>
<n-button color="#e18a3b" style="margin-left: 10px" @click="EditOption">编辑</n-button>
<n-button color="#984f31" style="margin-left: 10px" @click="AddOption">添加</n-button>
<n-button type="error" style="margin-left: 10px" @click="DeleteOption">删除</n-button>
</n-form-item>
<n-form-item path="label" label="GPT 名称">
<n-input v-model:value="gpt_select_value.label"></n-input>
</n-form-item>
<n-form-item path="value" label="GPT请求网址(写全)">
<n-input v-model:value="gpt_select_value.gpt_url"></n-input>
</n-form-item>
<div style="text-align: right">
<n-button type="primary" @click="SaveGptOption">保存</n-button>
</div>
</n-form>
</n-tab-pane>
<n-tab-pane name="signup" tab="GPT模型">
<n-form ref="modelFormRef" :model="gpt_model_select_value" :rules="modelRules">
<n-form-item label="选择自定义的GPT模型">
<n-select
style="width: 300px"
:options="gpt_model_options"
v-model:value="gpt_model_select_key"
></n-select>
<n-button color="#e18a3b" style="margin-left: 10px" @click="EditModelOption"
>编辑</n-button
>
<n-button color="#984f31" style="margin-left: 10px" @click="AddModelOption"
>添加</n-button
>
<n-button type="error" style="margin-left: 10px" @click="DeleteModelOption"
>删除</n-button
>
</n-form-item>
<n-form-item path="label" label="GPT 模型名称">
<n-input v-model:value="gpt_model_select_value.label"></n-input>
</n-form-item>
<n-form-item path="value" label="GPT 模型值">
<n-input v-model:value="gpt_model_select_value.value"></n-input>
</n-form-item>
<div style="text-align: right">
<n-button type="primary" @click="SaveGptModelOption">保存</n-button>
</div>
</n-form>
</n-tab-pane>
</n-tabs>
</div>
</template>
<script>
import { ref, toRaw, onMounted, defineComponent } from "vue"
import { NForm, NFormItem, NInput, NInputNumber, useDialog, NButton, useMessage, NSelect, NTabs, NTabPane } from "naive-ui"
import { cloneDeep } from "lodash";
import { ref, toRaw, onMounted, defineComponent } from 'vue'
import {
NForm,
NFormItem,
NInput,
NInputNumber,
useDialog,
NButton,
useMessage,
NSelect,
NTabs,
NTabPane
} from 'naive-ui'
import { cloneDeep } from 'lodash'
export default defineComponent({
components: {
NForm, NFormItem, NInput, NInputNumber, NButton, NSelect, NTabs, NTabPane
},
setup() {
let message = useMessage();
let gpt_model_options = ref([]);
let gpt_options = ref([]);
let gpt_select_value = ref({});
let gpt_select_key = ref("");
let gpt_model_select_value = ref({});
let gpt_model_select_key = ref("");
let formRef = ref(null);
let modelFormRef = ref(null);
components: {
NForm,
NFormItem,
NInput,
NInputNumber,
NButton,
NSelect,
NTabs,
NTabPane
},
setup() {
let message = useMessage()
let gpt_model_options = ref([])
let gpt_options = ref([])
let gpt_select_value = ref({})
let gpt_select_key = ref('')
let gpt_model_select_value = ref({})
let gpt_model_select_key = ref('')
let formRef = ref(null)
let modelFormRef = ref(null)
/**
* 加载GPT的配置信息
*/
async function InitGptOptions() {
await window.api.getGptBusinessOption("dynamic", (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
if (value.data.length == 0) {
message.error("请先添加自定义GPT服务商");
return;
}
gpt_options.value = value.data;
gpt_select_key.value = value.data[0].value;
gpt_select_value.value = {
label: null,
value: null
};
})
await window.api.getGptModelOption("dynamic", (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
if (value.data.length == 0) {
message.error("请先添加自定义GPT模型");
return;
}
gpt_model_options.value = value.data;
gpt_model_select_key.value = value.data[0].value;
gpt_model_select_value.value = {
label: null,
value: null
}
})
/**
* 加载GPT的配置信息
*/
async function InitGptOptions() {
await window.api.getGptBusinessOption('dynamic', (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
onMounted(async () => {
await InitGptOptions();
})
/**
* 保存当前的GPT配置信息
*/
async function SaveGptOption(e) {
e.preventDefault();
formRef.value?.validate(async (errors) => {
if (errors) {
message.error("请检查必填字段");
return
}
// 保存逻辑
await window.api.SaveDynamicGptOption([JSON.stringify(toRaw(gpt_select_value.value)), "gpt_options"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("添加成功");
await InitGptOptions();
})
});
if (value.data.length == 0) {
message.error('请先添加自定义GPT服务商')
return
}
/**
* 将当前选中的GPT配置信息,写入下面已编辑
*/
async function EditOption() {
let temp = gpt_options.value.find(x => x.value == gpt_select_key.value);
gpt_select_value.value = cloneDeep(temp);
gpt_options.value = value.data
gpt_select_key.value = value.data[0].value
gpt_select_value.value = {
label: null,
value: null,
gpt_url: null
}
})
/**
* 将当前选中的GPT模型配置信息,写入下面已编辑
*/
async function EditModelOption() {
let temp = gpt_model_options.value.find(x => x.value == gpt_model_select_key.value);
gpt_model_select_value.value = cloneDeep(temp);
await window.api.getGptModelOption('dynamic', (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
/**
* 删除当前选中的GPT服务商信息
*/
async function DeleteOption() {
let temp = gpt_options.value.find(x => x.value == gpt_select_key.value);
let id = temp.id;
await window.api.DeleteDynamicGptOption([id, "gpt_options"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("删除成功");
await InitGptOptions();
})
if (value.data.length == 0) {
message.error('请先添加自定义GPT模型')
return
}
/**
* 删除当前选中的GPT服务商信息
*/
async function DeleteModelOption() {
let temp = gpt_model_options.value.find(x => x.value == gpt_model_select_key.value);
let id = temp.id;
await window.api.DeleteDynamicGptOption([id, "gpt_model_options"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("删除成功");
await InitGptOptions();
})
}
/**
* 保存当前的GPT配置信息
*/
async function SaveGptModelOption(e) {
e.preventDefault();
modelFormRef.value?.validate(async (errors) => {
if (errors) {
message.error("请检查必填字段");
return
}
// 保存逻辑
await window.api.SaveDynamicGptOption([JSON.stringify(toRaw(gpt_model_select_value.value)), "gpt_model_options"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("添加成功");
await InitGptOptions();
})
});
}
/**
* 添加一个GPT服务商,只是清楚当前的数据
*/
async function AddOption() {
gpt_select_value.value = {
label: null,
value: null
};
}
/**
* 添加一个GPT服务商,只是清楚当前的数据
*/
async function AddModelOption() {
gpt_model_select_value.value = {
label: null,
value: null
};
}
let ruleObj = (errorMessage) => {
return [{
required: true,
validator(rule, value) {
if (value == null || value == "")
return new Error(errorMessage);
return true;
},
trigger: ["input", "blur"]
}]
}
let rules = {
label: ruleObj("必填GPT名称"),
gpt_url: ruleObj("必填GPT请求网址"),
};
let modelRules = {
label: ruleObj("必填GPT名称"),
value: ruleObj("必填GPT请求网址"),
};
return {
gpt_model_options,
gpt_options,
gpt_select_value,
gpt_model_select_value,
gpt_select_key,
gpt_model_select_key,
SaveGptOption,
EditOption,
DeleteOption,
AddOption,
SaveGptModelOption,
rules,
modelRules,
formRef,
modelFormRef,
EditModelOption,
AddModelOption,
DeleteModelOption
gpt_model_options.value = value.data
gpt_model_select_key.value = value.data[0].value
gpt_model_select_value.value = {
label: null,
value: null
}
})
}
})
onMounted(async () => {
await InitGptOptions()
})
/**
* 保存当前的GPT配置信息
*/
async function SaveGptOption(e) {
e.preventDefault()
formRef.value?.validate(async (errors) => {
if (errors) {
message.error('请检查必填字段')
return
}
// 保存逻辑
await window.api.SaveDynamicGptOption(
[JSON.stringify(toRaw(gpt_select_value.value)), 'gpt_options'],
async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加成功')
await InitGptOptions()
}
)
})
}
/**
* 将当前选中的GPT配置信息,写入下面已编辑
*/
async function EditOption() {
let temp = gpt_options.value.find((x) => x.value == gpt_select_key.value)
gpt_select_value.value = cloneDeep(temp)
}
/**
* 将当前选中的GPT模型配置信息,写入下面已编辑
*/
async function EditModelOption() {
let temp = gpt_model_options.value.find((x) => x.value == gpt_model_select_key.value)
gpt_model_select_value.value = cloneDeep(temp)
}
/**
* 删除当前选中的GPT服务商信息
*/
async function DeleteOption() {
let temp = gpt_options.value.find((x) => x.value == gpt_select_key.value)
let id = temp.id
await window.api.DeleteDynamicGptOption([id, 'gpt_options'], async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('删除成功')
await InitGptOptions()
})
}
/**
* 删除当前选中的GPT服务商信息
*/
async function DeleteModelOption() {
let temp = gpt_model_options.value.find((x) => x.value == gpt_model_select_key.value)
let id = temp.id
await window.api.DeleteDynamicGptOption([id, 'gpt_model_options'], async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('删除成功')
await InitGptOptions()
})
}
/**
* 保存当前的GPT配置信息
*/
async function SaveGptModelOption(e) {
e.preventDefault()
modelFormRef.value?.validate(async (errors) => {
if (errors) {
message.error('请检查必填字段')
return
}
// 保存逻辑
await window.api.SaveDynamicGptOption(
[JSON.stringify(toRaw(gpt_model_select_value.value)), 'gpt_model_options'],
async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加成功')
await InitGptOptions()
}
)
})
}
/**
* 添加一个GPT服务商,只是清楚当前的数据
*/
async function AddOption() {
gpt_select_value.value = {
label: null,
value: null,
gpt_url: null
}
}
/**
* 添加一个GPT服务商,只是清楚当前的数据
*/
async function AddModelOption() {
gpt_model_select_value.value = {
label: null,
value: null
}
}
let ruleObj = (errorMessage) => {
return [
{
required: true,
validator(rule, value) {
if (value == null || value == '') return new Error(errorMessage)
return true
},
trigger: ['input', 'blur']
}
]
}
let rules = {
label: ruleObj('必填GPT名称'),
gpt_url: ruleObj('必填GPT请求网址')
}
let modelRules = {
label: ruleObj('必填GPT名称'),
value: ruleObj('必填GPT请求网址')
}
return {
gpt_model_options,
gpt_options,
gpt_select_value,
gpt_model_select_value,
gpt_select_key,
gpt_model_select_key,
SaveGptOption,
EditOption,
DeleteOption,
AddOption,
SaveGptModelOption,
rules,
modelRules,
formRef,
modelFormRef,
EditModelOption,
AddModelOption,
DeleteModelOption
}
}
})
</script>
<style scoped>
.card-tabs .n-tabs-nav--bar-type {
padding-left: 4px;
padding-left: 4px;
}
</style>
</style>
@@ -14,7 +14,7 @@
</n-form-item>
<n-form-item label="出图方式(文生图、图生图)" path="type">
<n-select
style="width: 250px"
style="width: 250px"
v-model:value="formValue.type"
:options="modelOption"
placeholder="选择出图方式"
@@ -0,0 +1,555 @@
<template>
<div style="margin-top: 30px">
<div style="display: flex; margin-right: 10px">
<ImageFileSelect :modifyImage="modifyImage"></ImageFileSelect>
<div style="width: 250px; margin-left: 10px">
<n-checkbox size="small" v-model:checked="mask_setting.isRemote"> 使用iopaint </n-checkbox>
<!-- <div>
<n-checkbox v-model:checked="mask_setting.isRemote"> 软件内去除 </n-checkbox>
</div> -->
<div style="display: flex; justify-content: flex-end; align-items: flex-end">
<n-input
size="tiny"
style="margin-top: 5px"
v-model:value="mask_setting.localUrl"
placeholder="请输入本地iopaint地址"
></n-input>
<n-button type="success" @click="SaveMaskConfig" size="tiny">保存</n-button>
</div>
<div style="margin-top: 5px">
<span class="url_class" @click="OpenTeach('lama')">lama安装教程</span>
<span class="url_class" @click="OpenTeach('iopaint')" style="margin-left: 5px"
>iopaint安装教程</span
>
</div>
</div>
</div>
<div
style="margin-top: 10px; display: flex; justify-content: center; align-items: center"
id="cnvas_ss"
>
<!-- <canvas ref="imageCanvas" /> -->
<canvas
ref="imageCanvas"
:style="{
cursor: 'grab',
clipPath: `inset(0 ${sliderPos}% 0 0)`,
transition: `clip-path 300ms`
}"
@mousemove="draw"
@mouseup="stopDrawing"
@mousedown="startDrawing"
/>
</div>
<div style="margin-top: 10px; display: flex; justify-content: center; align-items: center">
<n-slider v-model:value="radius" :step="1" style="width: 120px" />
<n-button style="margin-left: 10px" @click="undo" type="success">回撤</n-button>
<n-button style="margin-left: 10px" @click="Check" type="success">查看效果</n-button>
<n-button style="margin-left: 10px" @click="SaveMask" type="success">保存蒙板</n-button>
<n-button style="margin-left: 10px" @click="ViewMaskImage" type="success">查看蒙板</n-button>
<n-popover trigger="hover">
<template #trigger>
<n-button style="margin-left: 10px" @click="BatchMask" type="success"
>开始批量处理</n-button
>
</template>
<span>批量处理的图片的尺寸要和上面的蒙板一致</span>
</n-popover>
</div>
</div>
</template>
<script>
import { defineComponent, ref, onMounted, h, watch, toRaw } from 'vue'
import {
NButton,
useMessage,
NInput,
NTag,
NImage,
NCheckbox,
NSlider,
NPopover,
NProgress,
useDialog,
useDialogReactiveList
} from 'naive-ui'
import ImageFileSelect from './ImageFileSelect.vue'
import ProgressDialog from '../Components/ProgressDialog.vue'
import { DEFINE_STRING } from '../../../../define/define_string'
import { isEmpty } from 'lodash'
export default defineComponent({
components: {
NButton,
NInput,
NTag,
ImageFileSelect,
NImage,
NCheckbox,
NSlider,
NPopover,
NProgress
},
props: ['width', 'height'],
setup(props) {
let message = useMessage()
let dialog = useDialog()
const imageCanvas = ref(null)
let imageContext = ref(null)
let height = ref(props.height)
let width = ref(props.width)
let isDrawing = false
let radius = ref(20) // 圆的半径
let points = [] // 用于保存鼠标的移动轨迹
let canvasHistory = [] // 用于保存画布的历史记录
let step = 0
let mask_setting = ref({
isRemote: true,
localUrl: null,
mask_path: null
})
let progressDialogRef = ref(null)
let total = ref(0)
let current = ref(0)
let dp
onMounted(async () => {
if (imageCanvas.value) {
const ctx = imageCanvas.value.getContext('2d', { willReadFrequently: true })
if (ctx) {
imageContext.value = ctx
}
}
let div = document.getElementById('cnvas_ss')
div.style.height = height.value - 220 + 'px'
await window.api.GetDefineConfigJsonByProperty(
JSON.stringify(['img_base', 'mask_setting', false, null]),
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
// 判断是不是有数据
if (value.data) {
mask_setting.value = Object.assign(mask_setting.value, value.data)
}
}
)
window.api.setEventListen([DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE_RESULT], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
total.value = value.data.total
current.value = value.data.current
if (progressDialogRef.value == null) {
return
}
progressDialogRef.value?.modifyTotal(total.value)
progressDialogRef.value?.modifyCurrent(current.value)
if (value.data.total == value.data.current) {
// 销毁进度组件
setTimeout(() => {
if (dp) {
dp.destroy()
}
}, 1000)
}
})
})
let hasImage = false
/**
* 获取图片的高度并且计算适应的宽高
* @param image
*/
function getCurrentWidthHeight(image) {
debugger
let image_width = 512
let image_height = 512
// 设置图片的大小
if (image) {
image_width = image.width
image_height = image.height
}
// 判断图片大小,获取当前的传入的window,判断当前的宽度和高度是不是超出了
// let min_size = Math.min(width.value,height.value);
let image_height_scale = (height.value - 220) / image_height
let image_width_scale = (width.value - 220) / image_width
let scale = Math.min(image_height_scale, image_width_scale)
return [image_width * scale, image_height * scale]
}
/**
* 选择图片初始化
* @param image_d
*/
function modifyImage(image_d) {
let [image_width, image_height] = getCurrentWidthHeight(image_d)
imageContext.value.canvas.width = image_width
imageContext.value.canvas.height = image_height
imageContext.value.drawImage(image_d, 0, 0, image_width, image_height)
canvasHistory = []
canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
hasImage = true
}
/**
* 停止画
*/
function stopDrawing() {
debugger
isDrawing = false
canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
console.log(canvasHistory)
step++
}
/**
* 计算点和点直接的所有像素点
* @param x0
* @param y0
* @param x1
* @param y1
*/
function bresenhamLine(x0, y0, x1, y1) {
let dx = Math.abs(x1 - x0)
let dy = Math.abs(y1 - y0)
let sx = x0 < x1 ? 1 : -1
let sy = y0 < y1 ? 1 : -1
let err = dx - dy
let points = []
while (true) {
points.push({ x: x0, y: y0 })
if (x0 === x1 && y0 === y1) break
let e2 = 2 * err
if (e2 > -dy) {
err -= dy
x0 += sx
}
if (e2 < dx) {
err += dx
y0 += sy
}
}
return points
}
/**
* 填充颜色
* @param event
*/
function draw(event) {
requestAnimationFrame(() => {
const rect = imageContext.value.canvas.getBoundingClientRect()
const x = event.clientX - rect.left
const y = event.clientY - rect.top
if (isDrawing && hasImage) {
// 获取上一个鼠标位置
const lastPoint = points[points.length - 1]
// 使用 Bresenham's line algorithm 计算两个点之间的所有像素点
const linePoints = bresenhamLine(lastPoint.x, lastPoint.y, x, y)
for (const point of linePoints) {
// 在每个像素点的周围绘制一个圆
imageContext.value.beginPath()
imageContext.value.arc(point.x, point.y, radius.value, 0, Math.PI * 2, false)
imageContext.value.fillStyle = 'rgba(255, 255, 0)'
imageContext.value.fill()
}
// 将当前鼠标位置添加到 points 数组中
points.push({ x, y })
}
})
}
/**
* 开始话
* @param event
*/
function startDrawing(event) {
isDrawing = true
const rect = imageContext.value.canvas.getBoundingClientRect()
points.push({ x: event.clientX - rect.left, y: event.clientY - rect.top })
}
/**
* 回撤
*/
async function undo() {
debugger
if (step <= 0) {
// 如果没有可以撤销的步骤,就清空画布
imageContext.value.clearRect(
0,
0,
imageContext.value.canvas.width,
imageContext.value.canvas.height
)
canvasHistory = []
} else {
step--
let canvasPic = new Image()
canvasHistory.pop()
canvasPic.src = canvasHistory[step]
canvasPic.onload = function () {
imageContext.value.clearRect(
0,
0,
imageContext.value.canvas.width,
imageContext.value.canvas.height
)
imageContext.value.drawImage(canvasPic, 0, 0)
}
}
}
/**
* 获取当前的蒙板
*/
function getCanvasMaskBase64() {
// 处理最后的图片
const imageData = imageContext.value.getImageData(
0,
0,
imageContext.value.canvas.width,
imageContext.value.canvas.height
)
const newImageData = imageContext.value.createImageData(imageData.width, imageData.height)
for (let i = 0; i < imageData.data.length; i += 4) {
if (
imageData.data[i] === 255 &&
imageData.data[i + 1] === 255 &&
imageData.data[i + 2] === 0
) {
// 如果这个像素点的颜色是 rgba(255, 255, 0, 255),就将它设置为白色
newImageData.data[i] = newImageData.data[i + 1] = newImageData.data[i + 2] = 255
newImageData.data[i + 3] = 255
} else {
// 否则,就将它设置为黑色
newImageData.data[i] = newImageData.data[i + 1] = newImageData.data[i + 2] = 0
newImageData.data[i + 3] = 255
}
}
// // 创建一个新的 Canvas 对象
const newCanvas = document.createElement('canvas')
newCanvas.width = imageData.width
newCanvas.height = imageData.height
const newContext = newCanvas.getContext('2d')
// 将新的 ImageData 对象绘制到新的 Canvas 对象上
newContext.putImageData(newImageData, 0, 0)
// 将新的 Canvas 对象转换为一个 base64 编码的图片 URL
const base64 = newCanvas.toDataURL('image/png')
return base64
}
/**
* 查看效果
*/
async function Check() {
// 判断当前是不是有蒙板
if (canvasHistory.length <= 1) {
message.error('请先绘制蒙板')
return
}
let base64 = getCanvasMaskBase64()
let baseUrl = mask_setting.value.localUrl
if (mask_setting.value.isRemote) {
}
let data = {
image: canvasHistory[0],
mask: base64,
type: 'arrayBuffer'
}
await window.img.ProcessImage(JSON.stringify(data), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
debugger
const arrayBuffer = value.data
const blob = new Blob([arrayBuffer], { type: 'image/png' })
const reader = new FileReader()
reader.onloadend = function () {
let img = new Image()
img.src = reader.result
img.onload = function () {
imageContext.value.drawImage(img, 0, 0)
canvasHistory.push(imageCanvas.value.toDataURL())
step++
}
}
reader.readAsDataURL(blob)
})
}
async function SaveMask() {
debugger
// 判断当前是不是又蒙板
if (canvasHistory.length <= 1) {
message.error('请先绘制蒙板')
return
}
let base64 = getCanvasMaskBase64()
// 将base64 保存到本地
await window.img.Base64ToFile(
JSON.stringify([base64, `data/mask/mask_${new Date().getTime()}.png`]),
async (value) => {
debugger
if (value.code == 0) {
message.error(value.message)
return
}
mask_setting.value.mask_path = value.data
await SaveMaskConfig()
message.success('蒙板保存成功')
}
)
}
/**
* 保存蒙板配置信息
*/
async function SaveMaskConfig() {
// 判断是不是iopaint,是的话没有localUrl就报错
if (mask_setting.value.isRemote && isEmpty(mask_setting.value.localUrl)) {
message.error('请输入iopaint地址')
return
}
await window.api.SaveDefineConfigJsonByProperty(
JSON.stringify(['img_base', 'mask_setting', toRaw(mask_setting.value), false]),
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
}
)
}
/**
*查看蒙板
*/
async function ViewMaskImage() {
window.system.OpenFile(mask_setting.value.mask_path)
}
/**
* 开始批量处理
*/
async function BatchMask() {
// 判断当前是不是有配置模板
if (mask_setting.value.mask_path == null) {
message.error('请先绘制蒙板')
return
}
// 开始处理
// 弹窗,管理不想要的值
dp = dialog.create({
name: 'progressDialog',
showIcon: false,
content: () =>
h(ProgressDialog, {
ref: progressDialogRef,
total: total,
current: current
}),
style: `width : 180px; opacity: 0.8;;`,
maskClosable: false,
closable: false,
onPositiveClick: async () => {}
})
// 提交处理任务,默认处理文件夹下面的所有的PNG图片
await window.img.BatchProcessImage('tmp/input_crop', (value) => {
debugger
// 不管返回什么,这边都关掉
setTimeout(() => {
if (dp) {
dp.destroy()
}
}, 1000)
if (value.code == 0) {
message.error(value.message)
return
}
window.api.showGlobalMessageDialog(value)
})
}
function OpenTeach(type) {
switch (type) {
case 'lama':
window.api.OpenUrl(
'https://rvgyir5wk1c.feishu.cn/docx/RZYCdG7ZpoKsIzxBEzccNEIFn8f#QOIRdJQvEouJB7xv0Xqcx3zInyb'
)
break
case 'iopaint':
window.api.OpenUrl(
'https://rvgyir5wk1c.feishu.cn/docx/RZYCdG7ZpoKsIzxBEzccNEIFn8f#MLoUdn5cfo6NJTxL0xTcUGyLn43'
)
default:
break
}
}
return {
modifyImage,
imageCanvas,
height,
width,
stopDrawing,
startDrawing,
draw,
undo,
Check,
SaveMask,
mask_setting,
SaveMaskConfig,
ViewMaskImage,
radius,
BatchMask,
progressDialogRef,
total,
current,
OpenTeach
}
}
})
</script>
<style>
.url_class {
color: #e18a3b;
}
.url_class:hover {
color: brown;
cursor: pointer;
}
</style>
@@ -0,0 +1,55 @@
<template>
<n-upload :show-file-list="false" directory-dnd :default-upload="false" @change="selectImage">
<n-upload-dragger>
<div style="margin-bottom: 12px">
<n-icon size="48" :depth="3">
<archive-icon />
</n-icon>
</div>
<n-text style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </n-text>
</n-upload-dragger>
</n-upload>
</template>
<script>
import { defineComponent, ref, onMounted, h, toRaw, watch } from 'vue'
import { NButton, useMessage, NInput, NTag, NUpload, NUploadDragger } from 'naive-ui'
export default defineComponent({
components: {
NButton,
NInput,
NTag,
NUpload,
NUploadDragger
},
props: ['modifyImage'],
setup(props) {
let message = useMessage()
let image = ref(props.image)
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
const img = new Image()
img.onload = () => resolve(img)
img.onerror = reject
img.src = reader.result
}
reader.onerror = (error) => reject(error)
reader.readAsDataURL(file)
})
}
async function selectImage(value) {
let img = await getBase64(value.file.file)
props.modifyImage(img)
}
return {
selectImage,
image
}
}
})
</script>