V3.3.1
1. 初步接入 comfy UI 出图,设定工作流,导出API,直接添加到软件,选中对应的工作流调用出图 2. 文案处理模型改为选择,同步通用设置的模型列表和令牌,添加测试链接功能 3. 添加缓存区图片删除功能 4. 添加垫图/MJ提示词过滤,提示无效的垫图文件(在出图的时候和添加垫图链接的时候,主要过滤飞书链接) 5. 优化聚合推文选图逻辑,没有出过图也能拖拽 6. 优化处理软件所有的加载状态 7. 修复MJ设置初始化问题
This commit is contained in:
+39
-20
@@ -1,25 +1,42 @@
|
||||
<template>
|
||||
<n-spin style="z-index: 1000 !important" :show="softwareStore.spin.spinning">
|
||||
<template #description> {{ softwareStore.spin.tip }} </template>
|
||||
<n-config-provider
|
||||
:hljs="hljs"
|
||||
:theme="softwareStore.globalSetting.theme == 'dark' ? darkTheme : null"
|
||||
>
|
||||
<n-message-provider>
|
||||
<n-modal-provider>
|
||||
<n-dialog-provider>
|
||||
<n-notification-provider>
|
||||
<RouterView></RouterView>
|
||||
</n-notification-provider>
|
||||
</n-dialog-provider>
|
||||
</n-modal-provider>
|
||||
</n-message-provider>
|
||||
</n-config-provider>
|
||||
</n-spin>
|
||||
<n-config-provider
|
||||
:hljs="hljs"
|
||||
:theme="softwareStore.globalSetting.theme == 'dark' ? darkTheme : null"
|
||||
>
|
||||
<n-message-provider>
|
||||
<n-modal-provider>
|
||||
<n-dialog-provider>
|
||||
<n-notification-provider>
|
||||
<RouterView></RouterView>
|
||||
</n-notification-provider>
|
||||
</n-dialog-provider>
|
||||
</n-modal-provider>
|
||||
</n-message-provider>
|
||||
</n-config-provider>
|
||||
|
||||
<n-modal
|
||||
v-model:show="softwareStore.spin.spinning"
|
||||
:mask-closable="false"
|
||||
:close-on-esc="true"
|
||||
transform-origin="center"
|
||||
:show-icon="false"
|
||||
style="background: transparent; box-shadow: none; width: auto"
|
||||
content-style="padding: 0; background: transparent;"
|
||||
:bordered="false"
|
||||
:mask="false"
|
||||
>
|
||||
<n-spin :color="'#18a058'">
|
||||
<template #description>
|
||||
<span :style="{ color: '#18a058', fontSize: '16px' }">
|
||||
{{ softwareStore.spin.tip }}
|
||||
</span>
|
||||
</template>
|
||||
</n-spin>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, onMounted } from 'vue'
|
||||
import { defineComponent, onMounted } from 'vue'
|
||||
import hljs from 'highlight.js/lib/core'
|
||||
import javascript from 'highlight.js/lib/languages/javascript'
|
||||
import {
|
||||
@@ -29,7 +46,8 @@ import {
|
||||
NModalProvider,
|
||||
darkTheme,
|
||||
NNotificationProvider,
|
||||
NSpin
|
||||
NSpin,
|
||||
NModal
|
||||
} from 'naive-ui'
|
||||
import { useSoftwareStore } from '../../stores/software'
|
||||
import { SoftColor } from '../../define/enum/softwareEnum'
|
||||
@@ -42,7 +60,8 @@ export default defineComponent({
|
||||
NMessageProvider,
|
||||
NNotificationProvider,
|
||||
NModalProvider,
|
||||
NSpin
|
||||
NSpin,
|
||||
NModal
|
||||
},
|
||||
setup() {
|
||||
let softwareStore = useSoftwareStore()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { OptionKeyName, OptionType } from '@/define/enum/option'
|
||||
import { useOptionStore } from '@/stores/option'
|
||||
import SDFluxCommon from './SDFluxCommon'
|
||||
import { OptionModel } from '@/model/option/option'
|
||||
|
||||
/**
|
||||
* 初始化特殊字符数据,用于文案的相关处理
|
||||
@@ -44,7 +45,6 @@ async function InitSpecialCharacters() {
|
||||
* @returns
|
||||
*/
|
||||
async function InitTTSGlobalSetting() {
|
||||
debugger
|
||||
let optionStore = useOptionStore()
|
||||
let initData = {
|
||||
selectModel: "edge-tts",
|
||||
@@ -96,7 +96,6 @@ async function InitTTSGlobalSetting() {
|
||||
* @returns
|
||||
*/
|
||||
async function InitFluxModelList(isMust: boolean = false): Promise<Array<{ label: string, value: string }>> {
|
||||
debugger
|
||||
let initData = []
|
||||
if (!isMust) {
|
||||
let FLUX_APIModelListData = await window.options.GetOptionByKey(OptionKeyName.FLUX_APIModelList);
|
||||
@@ -136,9 +135,78 @@ async function InitFluxModelList(isMust: boolean = false): Promise<Array<{ label
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 ComfyUI 的一些基础设置
|
||||
* @returns
|
||||
*/
|
||||
async function InitComfyUISetting() {
|
||||
let optionStore = useOptionStore()
|
||||
let initSimpleSetting: OptionModel.ComfyUI_SimpleSettingModel = {
|
||||
requestUrl: "",
|
||||
selectedWorkflow: null,
|
||||
negativePrompt: ""
|
||||
};
|
||||
let initWorkFlowSetting: Array<OptionModel.ComfyUI_WorkFlowSettingModel> = [];
|
||||
|
||||
// 初始化基础设置
|
||||
let ComfyUI_SimpleSetting = await window.options.GetOptionByKey(
|
||||
OptionKeyName.ComfyUI_SimpleSetting
|
||||
)
|
||||
if (ComfyUI_SimpleSetting.code == 0) {
|
||||
// 获取失败
|
||||
window.api.showGlobalMessageDialog(ComfyUI_SimpleSetting)
|
||||
return
|
||||
}
|
||||
|
||||
if (ComfyUI_SimpleSetting.data == null) {
|
||||
// 没有数据初始化
|
||||
let saveRes = await window.options.ModifyOptionByKey(
|
||||
OptionKeyName.ComfyUI_SimpleSetting,
|
||||
JSON.stringify(initSimpleSetting),
|
||||
OptionType.JOSN
|
||||
)
|
||||
if (saveRes.code == 0) {
|
||||
window.api.showGlobalMessageDialog(saveRes)
|
||||
return
|
||||
} else {
|
||||
optionStore.ComfyUI_SimpleSetting = initSimpleSetting
|
||||
}
|
||||
} else {
|
||||
optionStore.ComfyUI_SimpleSetting = JSON.parse(ComfyUI_SimpleSetting.data.value)
|
||||
}
|
||||
|
||||
// 初始化工作流设置
|
||||
let ComfyUI_WorkFlowSetting = await window.options.GetOptionByKey(
|
||||
OptionKeyName.ComfyUI_WorkFlowSetting
|
||||
)
|
||||
if (ComfyUI_WorkFlowSetting.code == 0) {
|
||||
// 获取失败
|
||||
window.api.showGlobalMessageDialog(ComfyUI_WorkFlowSetting)
|
||||
return
|
||||
}
|
||||
|
||||
if (ComfyUI_WorkFlowSetting.data == null) {
|
||||
// 没有数据初始化
|
||||
let saveRes = await window.options.ModifyOptionByKey(
|
||||
OptionKeyName.ComfyUI_WorkFlowSetting,
|
||||
JSON.stringify(initWorkFlowSetting),
|
||||
OptionType.JOSN
|
||||
)
|
||||
if (saveRes.code == 0) {
|
||||
window.api.showGlobalMessageDialog(saveRes)
|
||||
return
|
||||
} else {
|
||||
optionStore.ComfyUI_WorkFlowSetting = initWorkFlowSetting
|
||||
}
|
||||
} else {
|
||||
optionStore.ComfyUI_WorkFlowSetting = JSON.parse(ComfyUI_WorkFlowSetting.data.value)
|
||||
}
|
||||
}
|
||||
|
||||
let InitCommon = {
|
||||
InitSpecialCharacters,
|
||||
InitTTSGlobalSetting,
|
||||
InitFluxModelList
|
||||
InitFluxModelList,
|
||||
InitComfyUISetting
|
||||
}
|
||||
export default InitCommon
|
||||
|
||||
@@ -26,8 +26,10 @@
|
||||
v-for="(image, index) in item.imagePaths"
|
||||
:key="image.id"
|
||||
:id="image.id"
|
||||
:bookname="item.bookTaskName"
|
||||
class="image-container"
|
||||
@click="SelectImage(image, index)"
|
||||
@contextmenu="handleContextMenu"
|
||||
>
|
||||
<n-image
|
||||
:width="150"
|
||||
@@ -43,6 +45,16 @@
|
||||
</n-image-group>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
<n-dropdown
|
||||
placement="bottom-start"
|
||||
trigger="manual"
|
||||
:x="x"
|
||||
:y="y"
|
||||
:options="options"
|
||||
:show="showDropdown"
|
||||
:on-clickoutside="onClickoutside"
|
||||
@select="handleSelect"
|
||||
/>
|
||||
<div style="margin-top: 15px; display: flex; justify-content: flex-end">
|
||||
<n-button type="info" @click="SaveImageToData">选择完成</n-button>
|
||||
</div>
|
||||
@@ -51,12 +63,23 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { NImageGroup, NImage, NSpin, NButton, useMessage, NCollapse, NCollapseItem } from 'naive-ui'
|
||||
import { onMounted, ref, nextTick, h } from 'vue'
|
||||
import {
|
||||
NImageGroup,
|
||||
NImage,
|
||||
NSpin,
|
||||
NButton,
|
||||
useMessage,
|
||||
NCollapse,
|
||||
NCollapseItem,
|
||||
NDropdown,
|
||||
NIcon
|
||||
} from 'naive-ui'
|
||||
import { useReverseManageStore } from '@/stores/reverseManage'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { MJImageType } from '@/define/enum/mjEnum'
|
||||
import { MJAction } from '@/define/enum/bookEnum'
|
||||
import { TrashBinOutline } from '@vicons/ionicons5'
|
||||
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let props = defineProps({
|
||||
@@ -71,6 +94,94 @@ let show = ref(true)
|
||||
let defaultExpandedNames = ref([])
|
||||
|
||||
let selectImageIds = ref([])
|
||||
let deleteImage = ref(null)
|
||||
|
||||
let showDropdown = ref(false)
|
||||
let x = ref(0)
|
||||
let y = ref(0)
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: () => h('span', { style: { color: 'red' } }, `删除`),
|
||||
key: 'delete',
|
||||
icon: () => h(NIcon, { color: 'red' }, { default: () => h(TrashBinOutline) })
|
||||
}
|
||||
]
|
||||
|
||||
async function handleSelect(key) {
|
||||
showDropdown.value = false
|
||||
if (key == 'delete') {
|
||||
if (deleteImage.value == null) {
|
||||
message.error('未找到对应的被删除的图片数据,请检查!!')
|
||||
return
|
||||
}
|
||||
// 开始删除
|
||||
let res = await window.book.DeleteCacheImage(
|
||||
deleteImage.value.booktaskId,
|
||||
deleteImage.value.imagePath
|
||||
)
|
||||
if (res.code != 1) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
// 删除成功
|
||||
// 动态的修改数据
|
||||
let currentBookIndex = data.value.findIndex((x) => x.bookTaskId == deleteImage.value.booktaskId)
|
||||
if (currentBookIndex != -1) {
|
||||
let currentBooks = data.value[currentBookIndex]
|
||||
let findIndex = currentBooks.imagePaths.findIndex((x) => x.id == deleteImage.value.id)
|
||||
if (findIndex != -1) {
|
||||
currentBooks.imagePaths.splice(findIndex, 1)
|
||||
}
|
||||
|
||||
if (currentBooks.imagePaths <= 0) {
|
||||
data.value.splice(currentBookIndex, 1)
|
||||
}
|
||||
}
|
||||
message.success(res.message)
|
||||
} else {
|
||||
message.error('未找到对应的操作,请检查')
|
||||
}
|
||||
}
|
||||
function handleContextMenu(e) {
|
||||
e.preventDefault()
|
||||
showDropdown.value = false
|
||||
debugger
|
||||
let bookname = e.target.getAttribute('bookname')
|
||||
if (bookname == null) {
|
||||
message.error('未找到对应的批次数据名称,请检查')
|
||||
return
|
||||
}
|
||||
let imageId = e.target.id
|
||||
if (imageId == null) {
|
||||
message.error('未找到对应的图片ID,请检查')
|
||||
return
|
||||
}
|
||||
let currentBooks = data.value.find((x) => x.bookTaskName == bookname)
|
||||
if (currentBooks == null) {
|
||||
message.error('未找到对应的批次数据,请检查')
|
||||
return
|
||||
}
|
||||
let cacheImageList = currentBooks.imagePaths
|
||||
if (cacheImageList.length <= 0) {
|
||||
message.error('未找到对应的图片数据,请检查')
|
||||
return
|
||||
}
|
||||
let imageIndex = cacheImageList.findIndex((x) => x.id == imageId)
|
||||
if (imageIndex == -1) {
|
||||
message.error('未找到对应的图片数据,请检查')
|
||||
return
|
||||
}
|
||||
deleteImage.value = { ...cacheImageList[imageIndex], booktaskId: currentBooks.bookTaskId }
|
||||
nextTick().then(() => {
|
||||
showDropdown.value = true
|
||||
x.value = e.clientX
|
||||
y.value = e.clientY
|
||||
})
|
||||
}
|
||||
function onClickoutside() {
|
||||
showDropdown.value = false
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
@@ -80,7 +191,6 @@ onMounted(async () => {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
console.log('GetAllBookTaskImageCache', res.data)
|
||||
// 将指定ID的数据放在最前面
|
||||
const specifiedId = reverseManageStore.selectBookTask.id // 替换为你的指定ID
|
||||
res.data.sort((a, b) =>
|
||||
@@ -215,7 +325,6 @@ async function SaveImageToData() {
|
||||
selectImagePaths[0],
|
||||
props.type
|
||||
)
|
||||
console.log('SaveCacheImageToData', res)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
|
||||
@@ -201,6 +201,21 @@ export default defineComponent({
|
||||
? characterData.value.show_image.split('?t')[0]
|
||||
: null
|
||||
// 开始保存
|
||||
|
||||
if (characterData.value.image_url != null && !isEmpty(characterData.value.image_url)) {
|
||||
//
|
||||
// 删除前面的空格
|
||||
let trimmedUrl = characterData.value.image_url.trim()
|
||||
if (trimmedUrl.startsWith('--cref')) {
|
||||
message.error('风格垫图地址不能以 --cref 开头,请删除!!!')
|
||||
return
|
||||
}
|
||||
if (trimmedUrl.includes('feishu.cn')) {
|
||||
message.error('垫图链接不能包含 feishu.cn 飞书的链接,请正确的复制图片链接!!!')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
await window.mj.SaveTagPropertyData(
|
||||
[JSON.stringify(characterData.value), 'character_tags'],
|
||||
(value) => {
|
||||
|
||||
@@ -36,7 +36,10 @@
|
||||
</n-tooltip>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<div style="color: red;">注意:风格提示词描述(英文) 才是实际合并的提示词数据,请和出图的提示词语言保持一致(中文或英文)</div>
|
||||
<div style="color: red">
|
||||
注意:风格提示词描述(英文)
|
||||
才是实际合并的提示词数据,请和出图的提示词语言保持一致(中文或英文)
|
||||
</div>
|
||||
<n-form-item label="风格提示词描述(英文)" path="prompt">
|
||||
<n-input
|
||||
type="textarea"
|
||||
@@ -89,7 +92,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, h, onMounted, defineComponent, toRaw, watch } from 'vue'
|
||||
import { ref, onMounted, defineComponent, toRaw, watch } from 'vue'
|
||||
import {
|
||||
NImage,
|
||||
useMessage,
|
||||
@@ -145,15 +148,32 @@ export default defineComponent({
|
||||
async function SaveStyleTag(e) {
|
||||
e.preventDefault()
|
||||
formRef.value?.validate(async (errors) => {
|
||||
debugger
|
||||
if (errors) {
|
||||
message.error('请检查必填字段')
|
||||
return
|
||||
}
|
||||
|
||||
styleData.value['type'] = 'style_main'
|
||||
styleData.value['show_image'] = styleData.value.show_image
|
||||
? styleData.value.show_image.split('?t')[0]
|
||||
: null
|
||||
// 直接保存
|
||||
// 判断链接是不是有效的
|
||||
|
||||
if (styleData.value.image_url != null && !isEmpty(styleData.value.image_url)) {
|
||||
//
|
||||
// 删除前面的空格
|
||||
let trimmedUrl = styleData.value.image_url.trim()
|
||||
if (trimmedUrl.startsWith('--sref')) {
|
||||
message.error('风格垫图地址不能以 --sref 开头,请删除!!!')
|
||||
return
|
||||
}
|
||||
if (trimmedUrl.includes('feishu.cn')) {
|
||||
message.error('垫图链接不能包含 feishu.cn 飞书的链接,请正确的复制图片链接!!!')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 开始保存
|
||||
await window.mj.SaveTagPropertyData(
|
||||
[JSON.stringify(styleData.value), 'style_tags'],
|
||||
@@ -248,7 +268,6 @@ export default defineComponent({
|
||||
}
|
||||
imageLoading.value = true
|
||||
await window.sd.txt2img(JSON.stringify([d]), async (value) => {
|
||||
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
imageLoading.value = false
|
||||
|
||||
@@ -292,12 +292,8 @@ async function imageDragStart(e) {
|
||||
|
||||
// 图片拖拽结束事件
|
||||
async function imageDragDrop(e) {
|
||||
debugger
|
||||
console.log('end', e.target, dragTarget)
|
||||
// 判断原始数据是不是为空
|
||||
// if (dragTarget == null) {
|
||||
// message.error('只能拖拽到当前行的图片上')
|
||||
// return
|
||||
// }
|
||||
// 判断结束位置是不是主图片
|
||||
if (e.target.getAttribute('mainImage') != '1') {
|
||||
message.error('图片只能拖拽到主图片上')
|
||||
@@ -307,19 +303,33 @@ async function imageDragDrop(e) {
|
||||
.split('?')[0]
|
||||
.replace(/^file:\/\/\//, '')
|
||||
.replace(/^\//, '')
|
||||
let target_img = e.target.src
|
||||
.split('?')[0]
|
||||
.replace(/^file:\/\/\//, '')
|
||||
.replace(/^\//, '')
|
||||
// let target_img = e.target.src
|
||||
// .split('?')[0]
|
||||
// .replace(/^file:\/\/\//, '')
|
||||
// .replace(/^\//, '')
|
||||
// 修改主图片
|
||||
await window.api.DownloadImageFile([source_img, 'replace', target_img], (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
data.value.outImagePath =
|
||||
e.target.src.split('?')[0].replaceAll('\\', '/') + '?time=' + new Date().getTime()
|
||||
})
|
||||
|
||||
let res = await window.book.MoveImageToMainImage(
|
||||
data.value.bookTaskId,
|
||||
data.value.id,
|
||||
decodeURI(source_img)
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
data.value.outImagePath = res.data
|
||||
|
||||
message.success(res.message)
|
||||
|
||||
// await window.api.DownloadImageFile([source_img, 'replace', target_img], (value) => {
|
||||
// if (value.code == 0) {
|
||||
// message.error(value.message)
|
||||
// return
|
||||
// }
|
||||
// data.value.outImagePath =
|
||||
// e.target.src.split('?')[0].replaceAll('\\', '/') + '?time=' + new Date().getTime()
|
||||
// })
|
||||
}
|
||||
|
||||
// 图片拖拽进入事件
|
||||
@@ -542,6 +552,7 @@ function renderIcon(icon) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let mainImageOptions = []
|
||||
function GetMainImageOptions() {
|
||||
let t = [
|
||||
|
||||
@@ -137,7 +137,7 @@ export default defineComponent({
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
|
||||
type = 'sd_merge'
|
||||
} else {
|
||||
type = 'mj_merge'
|
||||
type = 'sd_merge'
|
||||
}
|
||||
}
|
||||
// 开始进行合并,调用不同的合并模式
|
||||
|
||||
@@ -82,7 +82,7 @@ async function MergePrompt(key) {
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
|
||||
type = 'sd_merge'
|
||||
} else {
|
||||
type = 'mj_merge'
|
||||
type = 'sd_merge'
|
||||
}
|
||||
}
|
||||
// 开始进行合并,调用不同的合并模式
|
||||
@@ -108,6 +108,7 @@ async function MergePrompt(key) {
|
||||
|
||||
// 单个出图
|
||||
async function SingleGenerateImage() {
|
||||
message.info('1')
|
||||
let res = undefined
|
||||
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
|
||||
res = await window.mj.AddMJGenerateImageTask(
|
||||
@@ -125,6 +126,16 @@ async function SingleGenerateImage() {
|
||||
data.value.id,
|
||||
DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN
|
||||
)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.ComfyUI) {
|
||||
// 直接添加SD出图任务
|
||||
res = await window.task.AddBookBackTask(
|
||||
reverseManageStore.selectBook.id,
|
||||
BookBackTaskType.ComfyUI_IMAGE,
|
||||
TaskExecuteType.AUTO,
|
||||
reverseManageStore.selectBookTask.id,
|
||||
data.value.id,
|
||||
DEFINE_STRING.BOOK.ComfyUI_IMAGE_GENERATE_RETURN
|
||||
)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.D3) {
|
||||
res = await window.task.AddBookBackTask(
|
||||
reverseManageStore.selectBook.id,
|
||||
@@ -226,6 +237,20 @@ async function UnderGenerateImage() {
|
||||
})
|
||||
}
|
||||
res = await window.task.AddMultiBookBackTask(tasksList)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.ComfyUI) {
|
||||
let tasksList = []
|
||||
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||
const element = bookTaskDetails[i]
|
||||
tasksList.push({
|
||||
bookId: reverseManageStore.selectBook.id,
|
||||
type: BookBackTaskType.ComfyUI_IMAGE,
|
||||
executeType: TaskExecuteType.AUTO,
|
||||
bookTaskId: reverseManageStore.selectBookTask.id,
|
||||
bookTaskDetailId: element.id,
|
||||
messageName: DEFINE_STRING.BOOK.ComfyUI_IMAGE_GENERATE_RETURN
|
||||
})
|
||||
}
|
||||
res = await window.task.AddMultiBookBackTask(tasksList)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.D3) {
|
||||
let tasksList = []
|
||||
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||
|
||||
@@ -806,6 +806,9 @@ async function AddFluxImageAll(type, cover) {
|
||||
} else if (type == BookImageCategory.D3) {
|
||||
messageName = DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN
|
||||
taskType = BookBackTaskType.D3_IMAGE
|
||||
} else if (type == BookImageCategory.ComfyUI) {
|
||||
messageName = DEFINE_STRING.BOOK.ComfyUI_IMAGE_GENERATE_RETURN
|
||||
taskType = BookBackTaskType.ComfyUI_IMAGE
|
||||
} else if (type == BookImageCategory.SD) {
|
||||
messageName = DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN
|
||||
taskType = BookBackTaskType.SD_IMAGE
|
||||
@@ -899,6 +902,8 @@ async function GenerateImageAll(cover = true) {
|
||||
await AddFluxImageAll(BookImageCategory.FLUX_API, cover)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.D3) {
|
||||
await AddFluxImageAll(BookImageCategory.D3, cover)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.ComfyUI) {
|
||||
await AddFluxImageAll(BookImageCategory.ComfyUI, cover)
|
||||
} else {
|
||||
message.error('不支持的生图类型')
|
||||
return
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
|
||||
import { useMessage, NTabPane, NTabs, NCard } from 'naive-ui'
|
||||
import MJSettingHome from '../../Setting/MJSetting/MJSettingHome.vue'
|
||||
import SDSetting from '../../Setting/SDSetting.vue'
|
||||
import SDSetting from '../../Setting/SDSetting/SDSetting.vue'
|
||||
import VideoGenerateSetting from '../../Setting/VideoGenerateSetting.vue'
|
||||
import WatermarkSetting from '../../Setting/WatermarkSetting.vue'
|
||||
import SubtitleSetting from '../../Setting/SubtitleSetting.vue'
|
||||
|
||||
@@ -73,6 +73,11 @@ onMounted(async () => {
|
||||
OriginalSDImageResponseReturn(value)
|
||||
})
|
||||
|
||||
// 监听ComfyUI出图返回的数据
|
||||
window.api.setEventListen([DEFINE_STRING.BOOK.ComfyUI_IMAGE_GENERATE_RETURN], (value) => {
|
||||
OriginalSDImageResponseReturn(value)
|
||||
})
|
||||
|
||||
// 监听FLUX API 出图返回的数据
|
||||
window.api.setEventListen([DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN], (value) =>
|
||||
OriginalSDImageResponseReturn(value)
|
||||
@@ -178,6 +183,7 @@ onUnmounted(() => {
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.ComfyUI_IMAGE_GENERATE_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN)
|
||||
})
|
||||
|
||||
@@ -130,7 +130,7 @@ async function MergePrompt(type = null) {
|
||||
let image_generate_category = reverseManageStore.selectBookTask.imageCategory
|
||||
let mergeType = 'mj_merge'
|
||||
if (!type) {
|
||||
if (image_generate_category == 'sd') {
|
||||
if (image_generate_category == 'sd' || image_generate_category == 'comfyui') {
|
||||
mergeType = 'sd_merge'
|
||||
} else if (image_generate_category == 'mj') {
|
||||
mergeType = 'mj_merge'
|
||||
|
||||
@@ -137,6 +137,16 @@ async function SingleGenerateImage() {
|
||||
row.value.id,
|
||||
DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN
|
||||
)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.ComfyUI) {
|
||||
// 直接添加SD出图任务
|
||||
res = await window.task.AddBookBackTask(
|
||||
reverseManageStore.selectBook.id,
|
||||
BookBackTaskType.ComfyUI_IMAGE,
|
||||
TaskExecuteType.AUTO,
|
||||
reverseManageStore.selectBookTask.id,
|
||||
row.value.id,
|
||||
DEFINE_STRING.BOOK.ComfyUI_IMAGE_GENERATE_RETURN
|
||||
)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.D3) {
|
||||
res = await window.task.AddBookBackTask(
|
||||
reverseManageStore.selectBook.id,
|
||||
@@ -238,6 +248,20 @@ async function NextGenerateImage() {
|
||||
})
|
||||
}
|
||||
res = await window.task.AddMultiBookBackTask(tasksList)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.ComfyUI) {
|
||||
let tasksList = []
|
||||
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||
const element = bookTaskDetails[i]
|
||||
tasksList.push({
|
||||
bookId: reverseManageStore.selectBook.id,
|
||||
type: BookBackTaskType.ComfyUI_IMAGE,
|
||||
executeType: TaskExecuteType.AUTO,
|
||||
bookTaskId: reverseManageStore.selectBookTask.id,
|
||||
bookTaskDetailId: element.id,
|
||||
messageName: DEFINE_STRING.BOOK.ComfyUI_IMAGE_GENERATE_RETURN
|
||||
})
|
||||
}
|
||||
res = await window.task.AddMultiBookBackTask(tasksList)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.D3) {
|
||||
let tasksList = []
|
||||
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||
|
||||
@@ -387,6 +387,9 @@ async function AddFluxImageAll(type, cover) {
|
||||
} else if (type == BookImageCategory.FLUX_API) {
|
||||
messageName = DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN
|
||||
taskType = BookBackTaskType.FLUX_API_IMAGE
|
||||
} else if (type == BookImageCategory.ComfyUI) {
|
||||
messageName = DEFINE_STRING.BOOK.ComfyUI_IMAGE_GENERATE_RETURN
|
||||
taskType = BookBackTaskType.ComfyUI_IMAGE
|
||||
} else if (type == BookImageCategory.D3) {
|
||||
messageName = DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN
|
||||
taskType = BookBackTaskType.D3_IMAGE
|
||||
@@ -473,6 +476,8 @@ async function GenerateImageAll(cover = true) {
|
||||
await AddFluxImageAll(BookImageCategory.FLUX_API, cover)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.D3) {
|
||||
await AddFluxImageAll(BookImageCategory.D3, cover)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.ComfyUI) {
|
||||
await AddFluxImageAll(BookImageCategory.ComfyUI, cover)
|
||||
} else {
|
||||
message.error('不支持的生图类型')
|
||||
return
|
||||
|
||||
@@ -78,7 +78,7 @@ function OriginalMJImageResponseReturn(value) {
|
||||
|
||||
// 原创SD生图返回的数据进行修改
|
||||
function OriginalSDImageResponseReturn(value) {
|
||||
console.log('OriginalSDImageResponseReturn', value)
|
||||
console.log('OriginalSDImageResponseReturn111111', value)
|
||||
// 开始修改
|
||||
if (value.data) {
|
||||
let dataIndex = reverseManageStore.selectBookTaskDetail.findIndex(
|
||||
@@ -193,6 +193,10 @@ onMounted(async () => {
|
||||
OriginalSDImageResponseReturn(value)
|
||||
})
|
||||
|
||||
window.api.setEventListen([DEFINE_STRING.BOOK.ComfyUI_IMAGE_GENERATE_RETURN], (value) => {
|
||||
OriginalSDImageResponseReturn(value)
|
||||
})
|
||||
|
||||
window.api.setEventListen([DEFINE_STRING.BOOK.RUNWAY_IMAGE_TO_VIDEO_RETURN], (value) => {
|
||||
VideoImageToVideoReturn(value)
|
||||
})
|
||||
@@ -205,6 +209,7 @@ onUnmounted(() => {
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.ComfyUI_IMAGE_GENERATE_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.RUNWAY_IMAGE_TO_VIDEO_RETURN)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,42 +1,136 @@
|
||||
<template>
|
||||
<n-space vertical>
|
||||
<n-card title="LAI API 设置">
|
||||
<div style="display: flex">
|
||||
<n-input
|
||||
v-model:value="optionStore.CW_AISetting.laiapi.gpt_url"
|
||||
style="margin-right: 10px"
|
||||
type="text"
|
||||
placeholder="请输入GPT URL"
|
||||
/>
|
||||
<n-input
|
||||
v-model:value="optionStore.CW_AISetting.laiapi.api_key"
|
||||
style="margin-right: 10px"
|
||||
type="text"
|
||||
placeholder="请输入API KEY"
|
||||
/>
|
||||
<n-input
|
||||
v-model:value="optionStore.CW_AISetting.laiapi.model"
|
||||
type="text"
|
||||
placeholder="请输入Model"
|
||||
/>
|
||||
<n-spin :show="loading">
|
||||
<n-card title="LAI API 设置">
|
||||
<div style="display: flex">
|
||||
<n-input
|
||||
v-model:value="optionStore.CW_AISetting.laiapi.gpt_url"
|
||||
style="margin-right: 10px; flex: 1"
|
||||
type="text"
|
||||
placeholder="请输入GPT URL"
|
||||
/>
|
||||
<n-input
|
||||
v-model:value="optionStore.CW_AISetting.laiapi.api_key"
|
||||
style="margin-right: 10px; flex: 1"
|
||||
type="text"
|
||||
placeholder="请输入API KEY"
|
||||
/>
|
||||
<n-select
|
||||
v-model:value="optionStore.CW_AISetting.laiapi.model"
|
||||
:options="gpt_model_options"
|
||||
placeholder="请选择Model"
|
||||
style="flex: 1"
|
||||
/>
|
||||
<n-button
|
||||
type="info"
|
||||
style="margin-left: 10px; white-space: nowrap"
|
||||
@click="testConnection"
|
||||
>
|
||||
测试连接
|
||||
</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
<div style="display: flex; justify-content: flex-end; margin: 20px">
|
||||
<n-button
|
||||
type="info"
|
||||
style="margin-right: 20px; white-space: nowrap"
|
||||
@click="syncGeneralSettings"
|
||||
>
|
||||
同步通用设置Key
|
||||
</n-button>
|
||||
<n-button type="info" @click="SaveAISetting">保存</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
<div style="display: flex; justify-content: flex-end; margin: 20px">
|
||||
<n-button type="primary" @click="SaveAISetting">保存</n-button>
|
||||
</div>
|
||||
</n-spin>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useMessage, useDialog, NCard, NSpace, NInput, NSelect, NButton } from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useMessage, useDialog, NCard, NSpace, NInput, NSelect, NButton, NSpin } from 'naive-ui'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { useOptionStore } from '@/stores/option'
|
||||
import { OptionKeyName, OptionType } from '@/define/enum/option'
|
||||
let gpt_model_options = ref([])
|
||||
let loading = ref(false)
|
||||
let optionStore = useOptionStore()
|
||||
|
||||
let message = useMessage()
|
||||
let dialog = useDialog()
|
||||
|
||||
onMounted(async () => {
|
||||
await window.api.getGptModelOption('all', (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
gpt_model_options.value = value.data
|
||||
console.log(gpt_model_options.value)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* 测试GPT链接是不是可以成功
|
||||
*/
|
||||
async function testConnection() {
|
||||
loading.value = true
|
||||
// 测试链接
|
||||
await window.api.TestGPTConnection(
|
||||
JSON.stringify({
|
||||
gpt_business: optionStore.CW_AISetting.laiapi.gpt_url + '/v1/chat/completions',
|
||||
gpt_key: optionStore.CW_AISetting.laiapi.api_key,
|
||||
gpt_model: optionStore.CW_AISetting.laiapi.model
|
||||
}),
|
||||
(value) => {
|
||||
loading.value = false
|
||||
if (value.code == 0) {
|
||||
window.api.showGlobalMessageDialog({
|
||||
code: 0,
|
||||
message: 'GPT链接失败,错误信息: ' + value.message
|
||||
})
|
||||
return
|
||||
}
|
||||
window.api.showGlobalMessageDialog({
|
||||
code: 1,
|
||||
message: 'gpt链接测试成功,请保存数据后使用!!!'
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步通用设置中的数据信息
|
||||
*/
|
||||
async function syncGeneralSettings() {
|
||||
// Create confirmation dialog before sync
|
||||
const syncDialog = dialog.warning({
|
||||
title: '同步确认',
|
||||
content: '确认要同步通用设置中的API Key和Model吗?这将覆盖当前设置。',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onNegativeClick: () => {
|
||||
message.info('已取消同步')
|
||||
return true
|
||||
},
|
||||
onPositiveClick: async () => {
|
||||
syncDialog.destroy()
|
||||
|
||||
// Get global settings
|
||||
let globalSetting = window.config
|
||||
if (!globalSetting) {
|
||||
message.error('未找到全局通用设置,请检查通用设置!')
|
||||
return
|
||||
}
|
||||
// Sync settings
|
||||
optionStore.CW_AISetting.laiapi.api_key = globalSetting.gpt_key
|
||||
optionStore.CW_AISetting.laiapi.model = globalSetting.gpt_model
|
||||
window.api.showGlobalMessageDialog({
|
||||
code: 1,
|
||||
message: '已同步通用设置,请测试成功后保存后使用!'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function SaveAISetting() {
|
||||
let da = dialog.warning({
|
||||
title: '提示',
|
||||
|
||||
@@ -110,6 +110,7 @@ export default defineComponent({
|
||||
* 单句生图
|
||||
*/
|
||||
async function SingleGenerateImage() {
|
||||
message.info("3")
|
||||
await window.api.OriginalSDImageGenerate([JSON.stringify([row]), true, false], (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
|
||||
@@ -112,6 +112,7 @@ export default defineComponent({
|
||||
*/
|
||||
async function SingleGenerateImage() {
|
||||
// 检查当前数据是不是有被锁定
|
||||
message.info("5")
|
||||
if (row.value.imageLock) {
|
||||
message.error('当前数据已经被锁定,无法生图')
|
||||
return
|
||||
|
||||
@@ -160,6 +160,7 @@ import { v4 as uuidv4 } from 'uuid'
|
||||
import MJDefine from '@/main/Service/MJ/mjDefine'
|
||||
import { OptionKeyName, OptionType } from '@/define/enum/option'
|
||||
import { ValidateJson } from '@/define/Tools/validate'
|
||||
import { useOptionStore } from '@/stores/option'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -196,6 +197,7 @@ export default defineComponent({
|
||||
})
|
||||
let request_model_options = ref([])
|
||||
let mj_speed_options = ref([])
|
||||
let optionStore = useOptionStore()
|
||||
|
||||
/**
|
||||
* 初始化GPT的option
|
||||
@@ -238,6 +240,7 @@ export default defineComponent({
|
||||
* 初始化MJ的option
|
||||
*/
|
||||
async function InitMjOptions() {
|
||||
debugger
|
||||
let mjSettingData = await window.options.GetOptionByKey(OptionKeyName.MJ_GlobalSetting)
|
||||
if (mjSettingData.code == 0) {
|
||||
throw new Error('加载MJ设置失败,失败原因如下:' + mjSettingData.message)
|
||||
@@ -250,6 +253,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
mj_globalSetting = JSON.parse(mjSettingData.data.value)
|
||||
mj_globalSetting = Object.assign(optionStore.MJ_GlobalSetting, mj_globalSetting)
|
||||
let mjSimpleSetting = mj_globalSetting.mj_simpleSetting
|
||||
|
||||
mjSetting.value = Object.assign(mjSetting.value, mjSimpleSetting)
|
||||
@@ -319,8 +323,6 @@ export default defineComponent({
|
||||
throw new Error(value.message)
|
||||
}
|
||||
})
|
||||
|
||||
debugger
|
||||
// 保存MJ的基础设置
|
||||
let saveMjRes = await window.options.ModifyOptionByKey(
|
||||
OptionKeyName.MJ_GlobalSetting,
|
||||
|
||||
@@ -66,7 +66,6 @@ async function InitMJSettingData() {
|
||||
optionStore.MJ_GlobalSetting = JSON.parse(mjRes.data.value)
|
||||
message.success('加载MJSetting信息成功')
|
||||
}
|
||||
|
||||
await TimeDelay(1000)
|
||||
} catch (error) {
|
||||
window.api.showGlobalMessageDialog({
|
||||
@@ -82,8 +81,6 @@ async function InitMJSettingData() {
|
||||
* 保存MJ配置
|
||||
*/
|
||||
async function SaveMjSetting() {
|
||||
// 在保存的时候要进行数据保存
|
||||
debugger
|
||||
// 通过选择的请求模式判断哪些是要校验的
|
||||
let request_model = optionStore.MJ_GlobalSetting.mj_simpleSetting.requestModel
|
||||
if (request_model == MJImageType.API_MJ) {
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<div class="add-comfy-ui-workflow">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="100"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<n-form-item label="名称" path="name">
|
||||
<n-input v-model:value="formValue.name" placeholder="输入工作流名称" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="工作流文件" path="jsonFile">
|
||||
<n-input v-model:value="formValue.workflowPath" placeholder="选择工作流API文件" readonly />
|
||||
<n-button @click="triggerFileSelect" type="primary" style="margin-left: 20px">
|
||||
选择文件
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<div class="button-group">
|
||||
<n-space justify="end" align="center">
|
||||
<n-button @click="checkWorkflowFile" :disabled="!jsonContent" secondary>
|
||||
检查工作流文件
|
||||
</n-button>
|
||||
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="saveWorkflow"
|
||||
:disabled="!formValue.name || !formValue.workflowPath"
|
||||
>
|
||||
{{ isEdit ? '更新' : '保存' }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
ref="fileInput"
|
||||
accept=".json"
|
||||
style="display: none"
|
||||
@change="handleFileUpload"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { NForm, NFormItem, NInput, NButton, NSpace, useMessage } from 'naive-ui'
|
||||
import { useOptionStore } from '@/stores/option'
|
||||
import { OptionKeyName, OptionType } from '@/define/enum/option'
|
||||
|
||||
const fileInput = ref(null)
|
||||
const jsonContent = ref(null)
|
||||
const formRef = ref(null)
|
||||
|
||||
const message = useMessage()
|
||||
const optionStore = useOptionStore()
|
||||
|
||||
const formValue = ref({
|
||||
id: null,
|
||||
name: '',
|
||||
workflowPath: null
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: {
|
||||
required: true,
|
||||
message: '请输入工作流名称',
|
||||
trigger: 'blur'
|
||||
},
|
||||
jsonFile: {
|
||||
required: true,
|
||||
message: '请选择选择工作流API文件',
|
||||
trigger: 'change'
|
||||
}
|
||||
}
|
||||
|
||||
// 接收props - 如果是编辑模式会传入workflowData
|
||||
const props = defineProps({
|
||||
workflowData: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const isEdit = ref(false)
|
||||
|
||||
// 初始化表单数据
|
||||
onMounted(() => {
|
||||
if (props.workflowData) {
|
||||
isEdit.value = true
|
||||
formValue.value = {
|
||||
id: props.workflowData.id,
|
||||
name: props.workflowData.name,
|
||||
workflowPath: props.workflowData.workflowPath
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
async function saveWorkflow() {
|
||||
try {
|
||||
// 检查名称是否重复(编辑模式下需要排除自身)
|
||||
const isDuplicate = optionStore.ComfyUI_WorkFlowSetting.some(
|
||||
(item) =>
|
||||
item.name === formValue.value.name && (!isEdit.value || item.id !== formValue.value.id)
|
||||
)
|
||||
|
||||
if (isDuplicate) {
|
||||
message.error('工作流名称已存在,请重新输入')
|
||||
return
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
// 更新现有工作流
|
||||
const index = optionStore.ComfyUI_WorkFlowSetting.findIndex(
|
||||
(item) => item.id === formValue.value.id
|
||||
)
|
||||
|
||||
if (index !== -1) {
|
||||
optionStore.ComfyUI_WorkFlowSetting[index] = {
|
||||
id: formValue.value.id,
|
||||
name: formValue.value.name,
|
||||
workflowPath: formValue.value.workflowPath
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 添加新工作流
|
||||
optionStore.ComfyUI_WorkFlowSetting.push({
|
||||
id: crypto.randomUUID(),
|
||||
name: formValue.value.name,
|
||||
workflowPath: formValue.value.workflowPath
|
||||
})
|
||||
}
|
||||
// 保存到数据库
|
||||
let res = await window.options.ModifyOptionByKey(
|
||||
OptionKeyName.ComfyUI_WorkFlowSetting,
|
||||
JSON.stringify(optionStore.ComfyUI_WorkFlowSetting),
|
||||
OptionType.JOSN
|
||||
)
|
||||
|
||||
if (res.code == 1) {
|
||||
message.success(isEdit.value ? '更新成功' : '保存成功')
|
||||
// 重置表单
|
||||
if (!isEdit.value) {
|
||||
formValue.value = {
|
||||
name: '',
|
||||
workflowPath: null,
|
||||
id: null
|
||||
}
|
||||
jsonContent.value = null
|
||||
}
|
||||
} else {
|
||||
message.error((isEdit.value ? '更新' : '保存') + '失败,失败原因:' + res.message)
|
||||
}
|
||||
} catch (error) {
|
||||
message.error((isEdit.value ? '更新' : '保存') + '失败,失败原因:' + error.message)
|
||||
}
|
||||
}
|
||||
|
||||
const triggerFileSelect = () => {
|
||||
fileInput.value.click()
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择并读取json文件
|
||||
* @param event
|
||||
*/
|
||||
const handleFileUpload = (event) => {
|
||||
const file = event.target.files[0]
|
||||
if (!file) return
|
||||
formValue.value.workflowPath = file.path
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
jsonContent.value = JSON.parse(e.target.result)
|
||||
} catch (error) {
|
||||
console.error('解析JSON失败:', error)
|
||||
message.error('无法解析JSON文件,请确保文件格式正确')
|
||||
jsonContent.value = null
|
||||
formValue.value.jsonFile = null
|
||||
}
|
||||
}
|
||||
|
||||
reader.readAsText(file)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查工作流文件是不是正确
|
||||
*/
|
||||
async function checkWorkflowFile() {
|
||||
if (formValue.value.workflowPath == null) {
|
||||
message.error('请先选择工作流文件')
|
||||
return
|
||||
}
|
||||
if (!jsonContent.value) {
|
||||
message.error('工作流文件内容为空,请选择工作流文件')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(jsonContent.value)
|
||||
let hasPositivePrompt = false
|
||||
let hasNegativePrompt = false
|
||||
|
||||
// 检查是不是有正向提示词和反向提示词
|
||||
let elements = []
|
||||
|
||||
// 处理不同格式的ComfyUI工作流
|
||||
if (Array.isArray(jsonContent.value)) {
|
||||
message.error('工作流文件的格式不正确,请检查工作流文件')
|
||||
} else if (typeof jsonContent.value === 'object') {
|
||||
// ComfyUI工作流通常将节点保存在nodes属性中
|
||||
if (jsonContent.value.nodes) {
|
||||
elements = Object.values(jsonContent.value.nodes)
|
||||
} else {
|
||||
elements = Object.values(jsonContent.value)
|
||||
}
|
||||
}
|
||||
|
||||
for (const element of elements) {
|
||||
if (element && element.class_type === 'CLIPTextEncode') {
|
||||
if (element._meta?.title === '正向提示词') {
|
||||
hasPositivePrompt = true
|
||||
}
|
||||
if (element._meta?.title === '反向提示词') {
|
||||
hasNegativePrompt = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPositivePrompt || !hasNegativePrompt) {
|
||||
message.error(
|
||||
'工作流文件缺少正向提示词或反向提示词,请检查工作流文件,把对应的文本编码模块的标题改为正向提示词和反向提示词!!'
|
||||
)
|
||||
return
|
||||
} else {
|
||||
message.success('工作流文件检查成功通过')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.add-comfy-ui-workflow {
|
||||
padding: 20px;
|
||||
max-width: 600px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<div class="comfy-ui-setting">
|
||||
<div class="form-section">
|
||||
<h3 class="section-title">ComfyUI 基础设置</h3>
|
||||
<n-form inline :model="optionStore.ComfyUI_SimpleSetting" class="inline-form">
|
||||
<n-form-item label="请求地址" path="requestUrl">
|
||||
<n-input
|
||||
v-model:value="optionStore.ComfyUI_SimpleSetting.requestUrl"
|
||||
placeholder="输入请求地址"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="当前工作流" path="selectedWorkflow">
|
||||
<n-select
|
||||
placeholder="选择工作流"
|
||||
v-model:value="optionStore.ComfyUI_SimpleSetting.selectedWorkflow"
|
||||
:options="
|
||||
optionStore.ComfyUI_WorkFlowSetting.map((workflow) => ({
|
||||
label: workflow.name,
|
||||
value: workflow.id
|
||||
}))
|
||||
"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="反向提示词" path="negativePrompt">
|
||||
<n-input
|
||||
v-model:value="optionStore.ComfyUI_SimpleSetting.negativePrompt"
|
||||
style="width: 300px"
|
||||
placeholder="输入反向提示词"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-button type="info" style="margin-left: 8px" @click="SaveComfyUISimpleSetting">
|
||||
保存设置
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</div>
|
||||
<div style="color: red">
|
||||
<p>注意:</p>
|
||||
<p>1 Comfy UI的工作流中正向提示词和反向提示必须为 <strong>Clip文本编码</strong> 节点</p>
|
||||
<p>2 标题必须对应 <strong>正向提示词和反向提示词</strong></p>
|
||||
<p>
|
||||
3 图像输出节点必须是 <strong>保存图像</strong> 节点,<strong
|
||||
>采样器只支持简单 K采样器和K采样器(高级)</strong
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<div class="action-bar">
|
||||
<n-button type="primary" @click="handleAdd">添加</n-button>
|
||||
</div>
|
||||
<div ref="tableContainer" class="table-container">
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
:data="optionStore.ComfyUI_WorkFlowSetting"
|
||||
:bordered="true"
|
||||
:single-line="false"
|
||||
:max-height="tableHeight"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, h, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import {
|
||||
NDataTable,
|
||||
NButton,
|
||||
useDialog,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NInput,
|
||||
useMessage,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NPopconfirm,
|
||||
NIcon
|
||||
} from 'naive-ui'
|
||||
// 导入图标
|
||||
import { useSoftwareStore } from '../../../../../stores/software'
|
||||
import { useOptionStore } from '../../../../../stores/option'
|
||||
import InitCommon from '../../../common/initCommon'
|
||||
import AddComfyUIWorkflow from './AddComfyUIWorkflow.vue'
|
||||
import { OptionKeyName, OptionType } from '@/define/enum/option'
|
||||
|
||||
const softwareStore = useSoftwareStore()
|
||||
const optionStore = useOptionStore()
|
||||
const dialog = useDialog()
|
||||
let message = useMessage()
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '加载设置中...'
|
||||
await InitCommon.InitComfyUISetting()
|
||||
message.success('加载 ComfyUI 设置成功')
|
||||
} catch (error) {
|
||||
message.error('加载设置失败,失败原因:' + error.message)
|
||||
} finally {
|
||||
softwareStore.spin.spinning = false
|
||||
}
|
||||
|
||||
// 初始化表格高度并监听窗口大小变化
|
||||
updateTableHeight()
|
||||
window.addEventListener('resize', updateTableHeight)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除事件监听器
|
||||
window.removeEventListener('resize', updateTableHeight)
|
||||
})
|
||||
|
||||
async function SaveComfyUISimpleSetting() {
|
||||
try {
|
||||
// 判断请求地址是不是 http 或者是 https 开头
|
||||
if (
|
||||
!optionStore.ComfyUI_SimpleSetting.requestUrl.startsWith('http://') &&
|
||||
!optionStore.ComfyUI_SimpleSetting.requestUrl.startsWith('https://')
|
||||
) {
|
||||
message.error('请求地址必须以 http 或者 https 开头')
|
||||
return
|
||||
}
|
||||
|
||||
// 判断当前选中的工作流ID是不是在下面的表格数据中存在
|
||||
if (
|
||||
!optionStore.ComfyUI_WorkFlowSetting.some(
|
||||
(workflow) => workflow.id === optionStore.ComfyUI_SimpleSetting.selectedWorkflow
|
||||
)
|
||||
) {
|
||||
message.error('当前选中的工作流不存在,请重新选择')
|
||||
return
|
||||
}
|
||||
|
||||
// 开始保存
|
||||
let res = await window.options.ModifyOptionByKey(
|
||||
OptionKeyName.ComfyUI_SimpleSetting,
|
||||
JSON.stringify(optionStore.ComfyUI_SimpleSetting),
|
||||
OptionType.JOSN
|
||||
)
|
||||
if (res.code == 1) {
|
||||
message.success('保存设置成功')
|
||||
} else {
|
||||
message.error('保存设置失败,失败原因:' + res.message)
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('保存ComfyUI通用设置失败,失败原因:' + error.message)
|
||||
}
|
||||
}
|
||||
|
||||
// 表格高度自适应相关
|
||||
const tableContainer = ref(null)
|
||||
const tableHeight = ref(300) // 默认高度
|
||||
const minTableHeight = 200 // 最小高度
|
||||
|
||||
// 更新表格高度的函数
|
||||
const updateTableHeight = () => {
|
||||
nextTick(() => {
|
||||
if (!tableContainer.value) return
|
||||
// 获取容器在视口中的位置信息
|
||||
const containerRect = tableContainer.value.getBoundingClientRect()
|
||||
// 考虑容器已经存在的内容(表单和说明等)的高度
|
||||
const existingContentHeight = containerRect.top
|
||||
// 获取视口高度
|
||||
const viewportHeight = window.innerHeight
|
||||
// 计算表格可用的高度
|
||||
const bottomPadding = 60 // 底部预留间距
|
||||
const availableHeight = viewportHeight - existingContentHeight - bottomPadding
|
||||
// 设置表格高度,不小于最小高度
|
||||
tableHeight.value = Math.max(availableHeight, minTableHeight)
|
||||
})
|
||||
}
|
||||
|
||||
// Table columns
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '工作流路径',
|
||||
key: 'workflowPath'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
render(row) {
|
||||
return h(
|
||||
'div',
|
||||
{},
|
||||
{
|
||||
default: () => [
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'info',
|
||||
secondary: true,
|
||||
onClick: () => handleEdit(row)
|
||||
},
|
||||
{
|
||||
default: () => '编辑'
|
||||
}
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'error',
|
||||
secondary: true,
|
||||
onClick: () => handleRemove(row),
|
||||
style: { marginLeft: '16px' }
|
||||
},
|
||||
{
|
||||
default: () => '删除'
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const handleAdd = () => {
|
||||
dialog.info({
|
||||
title: '添加工作流',
|
||||
showIcon: false,
|
||||
maskClosable: false,
|
||||
style: { width: '600px' },
|
||||
content: () => h(AddComfyUIWorkflow)
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑工作流
|
||||
const handleEdit = (row) => {
|
||||
dialog.info({
|
||||
title: '编辑工作流',
|
||||
showIcon: false,
|
||||
maskClosable: false,
|
||||
style: { width: '600px' },
|
||||
content: () => h(AddComfyUIWorkflow, { workflowData: row })
|
||||
})
|
||||
}
|
||||
|
||||
// 删除工作流
|
||||
const handleRemove = (row) => {
|
||||
dialog.warning({
|
||||
title: '确认删除',
|
||||
content: `确定要删除工作流"${row.name}"吗?`,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
debugger
|
||||
// 从工作流列表中删除该项
|
||||
const index = optionStore.ComfyUI_WorkFlowSetting.findIndex((item) => item.id === row.id)
|
||||
if (index == -1) {
|
||||
message.error('删除失败: 未找到该工作流')
|
||||
return
|
||||
}
|
||||
optionStore.ComfyUI_WorkFlowSetting.splice(index, 1)
|
||||
|
||||
// 如果删除的是当前选中的工作流,清空选择
|
||||
if (optionStore.ComfyUI_SimpleSetting.selectedWorkflow === row.id) {
|
||||
optionStore.ComfyUI_SimpleSetting.selectedWorkflow = ''
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
let res = await window.options.ModifyOptionByKey(
|
||||
OptionKeyName.ComfyUI_WorkFlowSetting,
|
||||
JSON.stringify(optionStore.ComfyUI_WorkFlowSetting),
|
||||
OptionType.JOSN
|
||||
)
|
||||
|
||||
if (res.code == 0) {
|
||||
message.error('删除失败,失败原因:' + res.message)
|
||||
return
|
||||
}
|
||||
|
||||
res = await window.options.ModifyOptionByKey(
|
||||
OptionKeyName.ComfyUI_SimpleSetting,
|
||||
JSON.stringify(optionStore.ComfyUI_SimpleSetting),
|
||||
OptionType.JOSN
|
||||
)
|
||||
|
||||
if (res.code == 1) {
|
||||
message.success('删除成功')
|
||||
} else {
|
||||
message.error('删除失败,失败原因:' + res.message)
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('删除失败: ' + error.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.description {
|
||||
margin-bottom: 16px;
|
||||
font-size: large;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
+59
-34
@@ -62,24 +62,18 @@
|
||||
v-model:value="formValue.denoising_strength"
|
||||
placeholder="输入重绘幅度"
|
||||
/>
|
||||
<n-checkbox
|
||||
style="margin-left: 30px"
|
||||
size="large"
|
||||
v-model:checked="formValue.adetailer"
|
||||
label="是否开启修脸/修手"
|
||||
/>
|
||||
</n-form-item>
|
||||
</div>
|
||||
<n-form-item label="采样方式" path="sampler_name">
|
||||
<n-select
|
||||
style="width: 250px"
|
||||
v-model:value="formValue.sampler_name"
|
||||
:options="samplers_options"
|
||||
placeholder="选择采样方式"
|
||||
/>
|
||||
</n-form-item>
|
||||
<div style="display: flex">
|
||||
<n-form-item label="迭代步数" path="steps">
|
||||
<n-form-item label="采样方式" path="sampler_name">
|
||||
<n-select
|
||||
style="width: 250px"
|
||||
v-model:value="formValue.sampler_name"
|
||||
:options="samplers_options"
|
||||
placeholder="选择采样方式"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="迭代步数" path="steps" style="margin-left: 10px">
|
||||
<n-input-number
|
||||
style="width: 150px"
|
||||
v-model:value="formValue.steps"
|
||||
@@ -98,19 +92,37 @@
|
||||
/>
|
||||
</n-form-item>
|
||||
</div>
|
||||
<n-form-item label="图片分辨率" path="resolution">
|
||||
<n-input-number
|
||||
:show-button="false"
|
||||
v-model:value="formValue.width"
|
||||
placeholder="输入迭代步数"
|
||||
/>
|
||||
<span style="margin: 0 10px"> * </span>
|
||||
<n-input-number
|
||||
:show-button="false"
|
||||
v-model:value="formValue.height"
|
||||
placeholder="输入迭代步数"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<div style="display: flex">
|
||||
<n-form-item label="图片分辨率" path="resolution">
|
||||
<n-input-number
|
||||
:show-button="false"
|
||||
v-model:value="formValue.width"
|
||||
placeholder="宽"
|
||||
style="width: 100px"
|
||||
/>
|
||||
<span style="margin: 0 10px"> * </span>
|
||||
<n-input-number
|
||||
:show-button="false"
|
||||
v-model:value="formValue.height"
|
||||
placeholder="高"
|
||||
style="width: 100px"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item>
|
||||
<n-checkbox
|
||||
style="margin-left: 30px"
|
||||
size="large"
|
||||
v-model:checked="formValue.adetailer"
|
||||
label="是否开启修脸/修手"
|
||||
/>
|
||||
<n-button style="margin-left: 10px" type="primary" @click="AdetailerSetting"
|
||||
>修手/脸设置</n-button
|
||||
>
|
||||
</n-form-item>
|
||||
</div>
|
||||
|
||||
<div style="display: flex">
|
||||
<n-form-item
|
||||
style="width: 300px"
|
||||
@@ -151,14 +163,14 @@
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="ADetailer setting" tab="ADetailer 设置">
|
||||
<SDADetailerSetting />
|
||||
<n-tab-pane name="ComfyUI API Setting" tab="ComfyUI 设置">
|
||||
<ComfyUISetting />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineComponent, ref, h, onMounted, toRaw } from 'vue'
|
||||
import { ref, h, onMounted, toRaw } from 'vue'
|
||||
import {
|
||||
NForm,
|
||||
NFormItem,
|
||||
@@ -169,10 +181,13 @@ import {
|
||||
NSelect,
|
||||
NTabs,
|
||||
NTabPane,
|
||||
NCheckbox
|
||||
NCheckbox,
|
||||
useDialog
|
||||
} from 'naive-ui'
|
||||
import SDADetailerSetting from '../Components/SDADetailerSetting.vue'
|
||||
import InitCommon from '../../common/initCommon'
|
||||
import SDADetailerSetting from './SDADetailerSetting.vue'
|
||||
import InitCommon from '../../../common/initCommon'
|
||||
import ComfyUISetting from './ComfyUISetting.vue'
|
||||
let dialog = useDialog()
|
||||
|
||||
let flux_model_options = ref([])
|
||||
|
||||
@@ -332,4 +347,14 @@ async function LoadSDServiceData() {
|
||||
message.success('加载成功')
|
||||
})
|
||||
}
|
||||
|
||||
async function AdetailerSetting() {
|
||||
dialog.info({
|
||||
title: 'ADetailer模型设置',
|
||||
showIcon: false,
|
||||
maskClosable: false,
|
||||
style: { width: '600px' },
|
||||
content: () => h(SDADetailerSetting)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -61,7 +61,7 @@ const routes = [
|
||||
{
|
||||
path: '/sd_setting',
|
||||
name: 'sd_setting',
|
||||
component: () => import('./components/Setting/SDSetting.vue')
|
||||
component: () => import('./components/Setting/SDSetting/SDSetting.vue')
|
||||
},
|
||||
{
|
||||
path: '/copywriting',
|
||||
|
||||
Reference in New Issue
Block a user