V3.3.1
1. 初步接入 comfy UI 出图,设定工作流,导出API,直接添加到软件,选中对应的工作流调用出图 2. 文案处理模型改为选择,同步通用设置的模型列表和令牌,添加测试链接功能 3. 添加缓存区图片删除功能 4. 添加垫图/MJ提示词过滤,提示无效的垫图文件(在出图的时候和添加垫图链接的时候,主要过滤飞书链接) 5. 优化聚合推文选图逻辑,没有出过图也能拖拽 6. 优化处理软件所有的加载状态 7. 修复MJ设置初始化问题
This commit is contained in:
parent
77ee38d302
commit
6336b36ead
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "laitool",
|
||||
"version": "3.2.4",
|
||||
"version": "3.3.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "laitool",
|
||||
"version": "3.2.2",
|
||||
"version": "3.3.1",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@alicloud/alimt20181012": "^1.2.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "laitool",
|
||||
"version": "3.2.5",
|
||||
"version": "3.3.1",
|
||||
"description": "An AI tool for image processing, video processing, and other functions.",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "laitool.cn",
|
||||
|
||||
BIN
resources/image/c_s/bcd05697-e4eb-48fe-a164-a8fabafe53c9.png
Normal file
BIN
resources/image/c_s/bcd05697-e4eb-48fe-a164-a8fabafe53c9.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1011 KiB |
BIN
resources/image/c_s/f5f23903-a91b-44ed-9c13-8e089be9c546.png
Normal file
BIN
resources/image/c_s/f5f23903-a91b-44ed-9c13-8e089be9c546.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1011 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -80,6 +80,11 @@ const BOOK = {
|
||||
*/
|
||||
SD_IMAGE_GENERATE_RETURN: 'SD_IMAGE_GENERATE_RETURN',
|
||||
|
||||
/**
|
||||
* ComfyUI生图返回信息
|
||||
*/
|
||||
ComfyUI_IMAGE_GENERATE_RETURN: 'ComfyUI_IMAGE_GENERATE_RETURN',
|
||||
|
||||
/**
|
||||
* D3 出图返回信息
|
||||
*/
|
||||
@ -119,6 +124,12 @@ const BOOK = {
|
||||
/** 保存缓存区的屠图片到小说主图或者是选图区 */
|
||||
SAVE_CACHE_IMAGE_TO_DATA: "SAVE_CACHE_IMAGE_TO_DATA",
|
||||
|
||||
/** 删除缓存区中的图片 */
|
||||
DELETE_CACHE_IMAGE: "DELETE_CACHE_IMAGE",
|
||||
|
||||
/** 移动指定图片链接到主图 */
|
||||
MOVE_IMAGE_TO_MAIN_IMAGE: "MOVE_IMAGE_TO_MAIN_IMAGE",
|
||||
|
||||
//#endregion
|
||||
|
||||
COMPUTE_STORYBOARD: 'COMPUTE_STORYBOARD',
|
||||
|
||||
@ -17,6 +17,8 @@ export enum BookImageCategory {
|
||||
MJ = 'mj',
|
||||
// SD
|
||||
SD = 'sd',
|
||||
// ComfyUI
|
||||
ComfyUI = "comfyui",
|
||||
// D3
|
||||
D3 = 'd3',
|
||||
// FLUX API
|
||||
@ -75,6 +77,8 @@ export enum BookBackTaskType {
|
||||
MJ_IMAGE = 'mj_image',
|
||||
// SD 生成图片
|
||||
SD_IMAGE = 'sd_image',
|
||||
// ComfyUI 生成图片
|
||||
ComfyUI_IMAGE = 'comfyui_image',
|
||||
// flux forge 生成图片
|
||||
FLUX_FORGE_IMAGE = 'flux_forge_image',
|
||||
// flux api 生成图片
|
||||
|
||||
@ -14,6 +14,8 @@ export enum MJImageType {
|
||||
// 本地 SD
|
||||
LOCAL_SD = 'local_sd',
|
||||
|
||||
// ComfyUI
|
||||
ComfyUI = 'comfyui',
|
||||
// flux-api
|
||||
FLUX_API = 'flux-api',
|
||||
|
||||
|
||||
@ -56,5 +56,20 @@ export enum OptionKeyName {
|
||||
FLUX_APIModelList = 'FLUX_APIModelList',
|
||||
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region SD/ComfyUI
|
||||
|
||||
/**
|
||||
* ComfyUI 基础设置
|
||||
*/
|
||||
ComfyUI_SimpleSetting = "ComfyUI_SimpleSetting",
|
||||
|
||||
|
||||
/**
|
||||
* ComfyUI 工作流设置
|
||||
*/
|
||||
ComfyUI_WorkFlowSetting = "ComfyUI_WorkFlowSetting"
|
||||
|
||||
//#endregion
|
||||
}
|
||||
@ -64,6 +64,7 @@ export enum ResponseMessageType {
|
||||
REVERSE_PROMPT_TRANSLATE = 'REVERSE_PROMPT_TRANSLATE',// 反推提示词翻译
|
||||
GPT_PROMPT_TRANSLATE = 'GPT_PROMPT_TRANSLATE', // GPT提示词翻译
|
||||
MJ_IMAGE = 'MJ_IMAGE',// MJ 生成图片
|
||||
ComfyUI_IMAGE = 'ComfyUI_IMAGE',// ComfyUI 生成图片
|
||||
HD_IMAGE = 'HD_IMAGE',// HD 生成图片
|
||||
RUNWAY_VIDEO = "RUNWAY_VIDEO",// Runway生成视频
|
||||
LUMA_VIDEO = "LUMA_VIDEO",// Luma生成视频
|
||||
|
||||
@ -42,13 +42,17 @@ export const ImageSetting = {
|
||||
return {
|
||||
code: 1,
|
||||
data: [
|
||||
{
|
||||
label: 'MJ',
|
||||
value: 'mj'
|
||||
},
|
||||
{
|
||||
label: 'SD',
|
||||
value: 'sd'
|
||||
},
|
||||
{
|
||||
label: 'MJ',
|
||||
value: 'mj'
|
||||
label: 'ComfyUI',
|
||||
value: 'comfyui'
|
||||
},
|
||||
{
|
||||
label: 'D3',
|
||||
|
||||
@ -299,6 +299,12 @@ export function BookIpc() {
|
||||
/** 保存缓存区的屠图片到小说主图或者是选图区 */
|
||||
ipcMain.handle(DEFINE_STRING.BOOK.SAVE_CACHE_IMAGE_TO_DATA, async (event, bookTaskDetailId: string, imageFile: string | string[], option: string) => await bookImage.SaveCacheImageToData(bookTaskDetailId, imageFile, option))
|
||||
|
||||
/** 删除缓存区的图片 */
|
||||
ipcMain.handle(DEFINE_STRING.BOOK.DELETE_CACHE_IMAGE, async (event, bookTaskId: string, imageFile: string) => await bookImage.DeleteCacheImage(bookTaskId, imageFile))
|
||||
|
||||
/** 移动指定的图片链接到主图 */
|
||||
ipcMain.handle(DEFINE_STRING.BOOK.MOVE_IMAGE_TO_MAIN_IMAGE, async (event, bookTaskId: string, bookTaskDetailId: string, sourceImagePath: string) => await bookImage.MoveImageToMainImage(bookTaskId, bookTaskDetailId, sourceImagePath))
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
|
||||
@ -324,6 +324,9 @@ export class BookImage {
|
||||
} else if (element.imageCategory == BookImageCategory.SD) {
|
||||
taskType = BookBackTaskType.SD_IMAGE;
|
||||
responseMessageName = DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN;
|
||||
} else if (element.imageCategory == BookImageCategory.ComfyUI) {
|
||||
taskType = BookBackTaskType.ComfyUI_IMAGE;
|
||||
responseMessageName = DEFINE_STRING.BOOK.ComfyUI_IMAGE_GENERATE_RETURN;
|
||||
} else if (element.imageCategory == BookImageCategory.D3) {
|
||||
taskType = BookBackTaskType.D3_IMAGE;
|
||||
responseMessageName = DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN;
|
||||
@ -792,4 +795,108 @@ export class BookImage {
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 删除缓存区中的图片
|
||||
|
||||
/**
|
||||
* 删除缓存区中的图片
|
||||
* @param bookTaskId 指定的小说任务ID
|
||||
* @param imageFile 要删除的图片文件地址
|
||||
* @returns
|
||||
*/
|
||||
public async DeleteCacheImage(bookTaskId: string, imageFile: string) {
|
||||
try {
|
||||
imageFile = imageFile.split("?t=")[0];
|
||||
imageFile = imageFile.split("?time=")[0];
|
||||
if (imageFile.startsWith("file:/")) {
|
||||
imageFile = imageFile.replace("file:///", "");
|
||||
imageFile = imageFile.replace("file://", "");
|
||||
imageFile = imageFile.replace("file:/", "");
|
||||
}
|
||||
if (!await CheckFileOrDirExist(imageFile)) {
|
||||
throw new Error(`图片文件 ${imageFile} 不存在,请检查`)
|
||||
}
|
||||
let deleteBaseName = path.basename(imageFile).toLocaleLowerCase();
|
||||
|
||||
// 开始查找数据删除
|
||||
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId);
|
||||
// 修改缓存区数据
|
||||
let cacheImageList = bookTask.cacheImageList ?? [];
|
||||
|
||||
for (let i = 0; i < cacheImageList.length; i++) {
|
||||
const element = cacheImageList[i];
|
||||
let baseName = path.basename(element).toLocaleLowerCase();
|
||||
if (deleteBaseName == baseName) {
|
||||
cacheImageList.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 删除本地的图片
|
||||
await fs.promises.unlink(imageFile);
|
||||
// 修改
|
||||
await this.bookServiceBasic.UpdetedBookTaskData(bookTaskId, {
|
||||
cacheImageList: cacheImageList.map((item) => path.relative(define.project_path, item))
|
||||
})
|
||||
return successMessage(null, '删除选中缓存区中的图片成功', 'BookImage_DeleteCacheImage')
|
||||
|
||||
} catch (error) {
|
||||
return errorMessage('删除选中缓存区中的图片失败,错误信息如下:' + error.message, 'BookImage_DeleteCacheImage')
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 将指定的图片放到主图中
|
||||
|
||||
/**
|
||||
* 将指定的图片链接复制到主图文件夹中
|
||||
* @param bookTaskDetailId
|
||||
* @param sourceImagePath
|
||||
* @returns
|
||||
*/
|
||||
public async MoveImageToMainImage(bookTaskId: string, bookTaskDetailId: string, sourceImagePath: string) {
|
||||
try {
|
||||
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId);
|
||||
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(bookTaskDetailId);
|
||||
|
||||
// 获取小说项目的地址
|
||||
let imageFolder = bookTask.imageFolder;
|
||||
if (imageFolder == null) {
|
||||
throw new Error('没有找到对应的小说项目的图片地址,请检查')
|
||||
}
|
||||
|
||||
let currentImagePath = path.join(imageFolder, `${bookTaskDetail.name}.png`);
|
||||
// 判断地址对应的文件是不是存在
|
||||
if (await CheckFileOrDirExist(currentImagePath)) {
|
||||
await fs.promises.unlink(currentImagePath);
|
||||
}
|
||||
|
||||
sourceImagePath = sourceImagePath.split("?t=")[0];
|
||||
sourceImagePath = sourceImagePath.split("?time=")[0];
|
||||
if (sourceImagePath.startsWith("file:/")) {
|
||||
sourceImagePath = sourceImagePath.replace("file:///", "");
|
||||
sourceImagePath = sourceImagePath.replace("file://", "");
|
||||
sourceImagePath = sourceImagePath.replace("file:/", "");
|
||||
}
|
||||
sourceImagePath = path.resolve(sourceImagePath);
|
||||
if (!await CheckFileOrDirExist(sourceImagePath)) {
|
||||
throw new Error(`图片文件 ${sourceImagePath} 不存在,请检查`)
|
||||
}
|
||||
|
||||
// 复制文件,判断父文件夹是不是存在
|
||||
await CopyFileOrFolder(sourceImagePath, currentImagePath, true);
|
||||
// 修改数据库数据
|
||||
await this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetailId, {
|
||||
outImagePath: path.relative(define.project_path, currentImagePath)
|
||||
})
|
||||
// 返回数据
|
||||
return successMessage(currentImagePath + `?t=${Date.now()}`, '将指定的图片放到主图中成功', 'BookImage_MoveImageToMainImage')
|
||||
|
||||
} catch (error) {
|
||||
return errorMessage('将指定的图片放到主图中失败,错误信息如下:' + error.message, 'BookImage_MoveImageToMainImage')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
}
|
||||
|
||||
@ -286,12 +286,16 @@ class MJApi {
|
||||
|
||||
/**
|
||||
* 提交MJ API/代理模式 出图任务
|
||||
* @param taskId
|
||||
* @param taskId
|
||||
*/
|
||||
async SubmitMJImagineAPI(taskId: string, prompt: string): Promise<string> {
|
||||
|
||||
let _bookBackTaskListService = await BookBackTaskListService.getInstance()
|
||||
|
||||
// 这边校验是不是在提示词包含不正确的链接
|
||||
if (prompt.includes("feishu.cn")) {
|
||||
throw new Error("提示词里面出现了 feishu.cn 飞书的链接,请检查并复制正确的链接")
|
||||
}
|
||||
|
||||
// 提交API的出图任务
|
||||
let data = {
|
||||
botType: this.bootType,
|
||||
@ -322,7 +326,6 @@ class MJApi {
|
||||
|
||||
let resData: any = undefined;
|
||||
if (useTransfer) {
|
||||
|
||||
let url = define.lms + "/lms/Forward/SimpleTransfer"
|
||||
let transferConfig = {
|
||||
method: 'post',
|
||||
|
||||
526
src/main/Service/SD/comfyui.ts
Normal file
526
src/main/Service/SD/comfyui.ts
Normal file
@ -0,0 +1,526 @@
|
||||
import { BookBackTaskListService } from "@/define/db/service/Book/bookBackTaskListService";
|
||||
import { OptionRealmService } from "@/define/db/service/SoftWare/optionRealmService";
|
||||
import { OptionKeyName } from "@/define/enum/option";
|
||||
import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder } from "@/define/Tools/file";
|
||||
import { ValidateJson } from "@/define/Tools/validate";
|
||||
import { TaskModal } from "@/model/task";
|
||||
import { BookServiceBasic } from "@/main/Service/ServiceBasic/bookServiceBasic";
|
||||
import fs from "fs";
|
||||
import axios from "axios";
|
||||
import { isEmpty } from "lodash";
|
||||
import { BookBackTaskStatus, BookTaskStatus, BookType, MJAction } from "@/define/enum/bookEnum";
|
||||
import { GeneralResponse } from "@/model/generalResponse";
|
||||
import { DEFINE_STRING } from "@/define/define_string";
|
||||
import { ResponseMessageType } from "@/define/enum/softwareEnum";
|
||||
import { MJImageType, MJRespoonseType } from "@/define/enum/mjEnum";
|
||||
import { MJ } from "@/model/mj";
|
||||
import { SendMessageToRenderer } from "../globalService";
|
||||
import { Book } from "@/model/book/book";
|
||||
import path from 'path'
|
||||
import { Base64ToFile } from "@/define/Tools/image";
|
||||
import { define } from "@/define/define"
|
||||
|
||||
|
||||
export class ComfyUIOpt {
|
||||
bookServiceBasic: BookServiceBasic
|
||||
|
||||
constructor() {
|
||||
this.bookServiceBasic = new BookServiceBasic()
|
||||
}
|
||||
|
||||
|
||||
//#region 获取ComfyUI的设置
|
||||
/**
|
||||
* 获取ComfyUI的设置
|
||||
* @returns
|
||||
*/
|
||||
private async GetComfyUISetting(): Promise<ComfyUIModel.ComfyUISettingCollection> {
|
||||
let result = {} as ComfyUIModel.ComfyUISettingCollection;
|
||||
let optionRealmService = await OptionRealmService.getInstance()
|
||||
let comfyuiSimpleSettingOption = optionRealmService.GetOptionByKey(OptionKeyName.ComfyUI_SimpleSetting)
|
||||
if (comfyuiSimpleSettingOption == null) {
|
||||
throw new Error("未找到ComfyUI的设置,请检查是否正确设置!!");
|
||||
}
|
||||
if (!ValidateJson(comfyuiSimpleSettingOption.value)) {
|
||||
throw new Error("ComfyUI的设置不是有效的JSON格式,请检查是否正确设置!!");
|
||||
}
|
||||
result["comfyuiSimpleSetting"] = JSON.parse(comfyuiSimpleSettingOption.value);
|
||||
|
||||
let comfyuiWorkFlowSettingOption = optionRealmService.GetOptionByKey(OptionKeyName.ComfyUI_WorkFlowSetting)
|
||||
if (comfyuiWorkFlowSettingOption == null) {
|
||||
throw new Error("未找到ComfyUI的工作流设置,请检查是否正确设置!!");
|
||||
}
|
||||
if (!ValidateJson(comfyuiWorkFlowSettingOption.value)) {
|
||||
throw new Error("ComfyUI的工作流设置不是有效的JSON格式,请检查是否正确设置!!");
|
||||
}
|
||||
let comfyuiWorkFlowList = JSON.parse(comfyuiWorkFlowSettingOption.value);
|
||||
result["comfyuiWorkFlowSetting"] = comfyuiWorkFlowList;
|
||||
|
||||
if (comfyuiWorkFlowList.length <= 0) {
|
||||
throw new Error("ComfyUI的工作流设置为空,请检查是否正确设置!!");
|
||||
}
|
||||
|
||||
// 获取选中的工作流
|
||||
let selectedWorkflow = comfyuiWorkFlowList.find(item => item.id == result.comfyuiSimpleSetting.selectedWorkflow);
|
||||
if (selectedWorkflow == null) {
|
||||
throw new Error("未找到选中的工作流,请检查是否正确设置!!");
|
||||
}
|
||||
|
||||
// 判断工作流对应的文件是不是存在
|
||||
if (!await CheckFileOrDirExist(selectedWorkflow.workflowPath)) {
|
||||
throw new Error("本地未找到选中的工作流文件地址,请检查是否正确设置!!");
|
||||
}
|
||||
result["comfyuiSelectedWorkflow"] = selectedWorkflow;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region 组合ComfyUI的请求体
|
||||
/**
|
||||
* 组合ComfyUI的请求体
|
||||
* @param prompt 正向提示词
|
||||
* @param negativePrompt 反向提示词
|
||||
* @param workflowPath 工作流地址
|
||||
*/
|
||||
private async GetComfyUIAPIBody(prompt: string, negativePrompt: string, workflowPath: string): Promise<string> {
|
||||
let jsonContentString = await fs.promises.readFile(workflowPath, 'utf-8');
|
||||
if (!ValidateJson(jsonContentString)) {
|
||||
throw new Error("工作流文件内容不是有效的JSON格式,请检查是否正确设置!!");
|
||||
}
|
||||
|
||||
let jsonContent = JSON.parse(jsonContentString);
|
||||
// 判断是否是对象
|
||||
if (jsonContent !== null && typeof jsonContent === 'object' && !Array.isArray(jsonContent)) {
|
||||
// 遍历对象属性
|
||||
for (const key in jsonContent) {
|
||||
let element = jsonContent[key];
|
||||
if (element && element.class_type === 'CLIPTextEncode') {
|
||||
if (element._meta?.title === '正向提示词') {
|
||||
jsonContent[key].inputs.text = prompt;
|
||||
}
|
||||
if (element._meta?.title === '反向提示词') {
|
||||
jsonContent[key].inputs.text = negativePrompt;
|
||||
}
|
||||
}
|
||||
if (element && element.class_type === "KSampler") {
|
||||
const crypto = require('crypto');
|
||||
const buffer = crypto.randomBytes(8);
|
||||
let seed = BigInt('0x' + buffer.toString('hex'));
|
||||
|
||||
jsonContent[key].inputs.seed = seed.toString();
|
||||
} else if (element && element.class_type === "KSamplerAdvanced") {
|
||||
const crypto = require('crypto');
|
||||
const buffer = crypto.randomBytes(8);
|
||||
let seed = BigInt('0x' + buffer.toString('hex'));
|
||||
|
||||
jsonContent[key].inputs.noise_seed = seed.toString();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error("工作流文件内容不是有效的JSON对象格式,请检查是否正确设置!!");
|
||||
}
|
||||
let result = JSON.stringify({
|
||||
prompt: jsonContent
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 提交ComfyUI生成图片任务
|
||||
|
||||
private async SubmitComfyUIImagine(body: string, comfyUISettingCollection: ComfyUIModel.ComfyUISettingCollection): Promise<any> {
|
||||
|
||||
let url = comfyUISettingCollection.comfyuiSimpleSetting.requestUrl?.replace("localhost", "127.0.0.1");
|
||||
if (url.endsWith('/')) {
|
||||
url = url + "api/prompt"
|
||||
} else {
|
||||
url = url + "/api/prompt"
|
||||
}
|
||||
var config = {
|
||||
method: 'post',
|
||||
url: url,
|
||||
headers: {
|
||||
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: body
|
||||
};
|
||||
|
||||
let res = await axios(config);
|
||||
let resData = res.data;
|
||||
// 判断是不是失败
|
||||
if (resData.error) {
|
||||
let errorNode = '';
|
||||
if (resData.node_errors) {
|
||||
for (const key in resData.node_errors) {
|
||||
errorNode += key + ', ';
|
||||
}
|
||||
}
|
||||
let msg = "错误信息:" + resData.error.message + "错误节点:" + errorNode;
|
||||
throw new Error(msg);
|
||||
}
|
||||
// 没有错误 判断是不是成功
|
||||
if (resData.prompt_id && !isEmpty(resData.prompt_id)) {
|
||||
// 成功
|
||||
return resData;
|
||||
} else {
|
||||
throw new Error("未知错误,未获取到请求ID,请检查是否正确设置!!");
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 获取comfyui出图任务
|
||||
|
||||
/**
|
||||
* 获取ComfyUI出图任务
|
||||
* @param promptId
|
||||
* @param comfyUISettingCollection
|
||||
*/
|
||||
private async GetComfyUIImageTask(promptId: string, comfyUISettingCollection: ComfyUIModel.ComfyUISettingCollection): Promise<any> {
|
||||
if (isEmpty(promptId)) {
|
||||
throw new Error("未获取到请求ID,请检查是否正确设置!!");
|
||||
}
|
||||
if (isEmpty(comfyUISettingCollection.comfyuiSimpleSetting.requestUrl)) {
|
||||
throw new Error("未获取到ComfyUI的请求地址,请检查是否正确设置!!");
|
||||
}
|
||||
|
||||
let url = comfyUISettingCollection.comfyuiSimpleSetting.requestUrl?.replace("localhost", "127.0.0.1");
|
||||
if (url.endsWith('/')) {
|
||||
url = url + "api/history"
|
||||
} else {
|
||||
url = url + "/api/history"
|
||||
}
|
||||
|
||||
var config = {
|
||||
method: 'get',
|
||||
url: `${url}/${promptId}`,
|
||||
headers: {
|
||||
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)'
|
||||
}
|
||||
};
|
||||
|
||||
let res = await axios.request(config);
|
||||
let resData = res.data;
|
||||
// 判断状态是失败还是成功
|
||||
let data = resData[promptId];
|
||||
if (data == null) {
|
||||
// 还在执行中 或者是任务不存在
|
||||
return {
|
||||
progress: 0,
|
||||
status: "in_progress",
|
||||
message: "任务正在执行中"
|
||||
}
|
||||
}
|
||||
let completed = data.status?.completed;
|
||||
let outputs = data.outputs;
|
||||
if (completed && outputs) {
|
||||
let imageNames = [];
|
||||
for (const key in outputs) {
|
||||
let outputNode = outputs[key];
|
||||
if (outputNode && outputNode?.images && outputNode?.images.length > 0) {
|
||||
for (let i = 0; i < outputNode?.images.length; i++) {
|
||||
const element = outputNode?.images[i];
|
||||
imageNames.push(element.filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
progress: 100,
|
||||
status: "success",
|
||||
imageNames: imageNames
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
progress: 0,
|
||||
status: "error",
|
||||
message: "生图失败,详细失败信息看启动器控制台"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 请求下载图片
|
||||
|
||||
/**
|
||||
* 请求下载对应的图片
|
||||
* @param url
|
||||
* @param path
|
||||
*/
|
||||
private async DownloadFileUrl(imageNames: string[], comfyUISettingCollection: ComfyUIModel.ComfyUISettingCollection, book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetail: Book.SelectBookTaskDetail): Promise<{
|
||||
outImagePath: string,
|
||||
subImagePath: string[]
|
||||
}> {
|
||||
|
||||
let url = comfyUISettingCollection.comfyuiSimpleSetting.requestUrl?.replace("localhost", "127.0.0.1");
|
||||
if (url.endsWith('/')) {
|
||||
url = url + "api/view"
|
||||
} else {
|
||||
url = url + "/api/view"
|
||||
}
|
||||
let outImagePath = "";
|
||||
let subImagePath = [];
|
||||
for (let i = 0; i < imageNames.length; i++) {
|
||||
const imageName = imageNames[i];
|
||||
|
||||
var config = {
|
||||
method: 'get',
|
||||
url: `${url}?filename=${imageName}&nocache=${Date.now()}`,
|
||||
headers: {
|
||||
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)'
|
||||
},
|
||||
responseType: 'arraybuffer' as 'arraybuffer' // 明确指定类型
|
||||
};
|
||||
|
||||
let res = await axios.request(config);
|
||||
|
||||
// 检查响应状态和类型
|
||||
console.log(`图片下载状态: ${res.status}, 内容类型: ${res.headers['content-type']}`);
|
||||
|
||||
// 确保得到的是图片数据
|
||||
if (!res.headers['content-type']?.includes('image/')) {
|
||||
console.error(`响应不是图片: ${res.headers['content-type']}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
let resData = res.data;
|
||||
console.log(resData);
|
||||
|
||||
let SdOriginalImage = path.join(book.bookFolderPath, 'data/SdOriginalImage');
|
||||
await CheckFolderExistsOrCreate(SdOriginalImage);
|
||||
let outputFolder = bookTask.imageFolder;
|
||||
await CheckFolderExistsOrCreate(outputFolder);
|
||||
let inputFolder = path.join(book.bookFolderPath, 'tmp/input')
|
||||
await CheckFolderExistsOrCreate(inputFolder);
|
||||
|
||||
|
||||
// 包含info信息的图片地址
|
||||
let infoImgPath = path.join(SdOriginalImage, `info_${bookTaskDetail.name}_${new Date().getTime()}_${i}.png`)
|
||||
// 不包含info信息的图片地址
|
||||
let imgPath = path.join(SdOriginalImage, `${bookTaskDetail.name}_${new Date().getTime()}_${i}.png`)
|
||||
|
||||
// 直接将二进制数据写入文件
|
||||
await fs.promises.writeFile(infoImgPath, Buffer.from(resData));
|
||||
|
||||
// 这边去图片信息
|
||||
// await DeleteFileExifData(path.join(define.package_path, 'exittool/exiftool.exe'), infoImgPath, imgPath);
|
||||
|
||||
if (i == 0) {
|
||||
// 复制到对应的文件夹里面
|
||||
let outPath = path.join(outputFolder, `${bookTaskDetail.name}.png`)
|
||||
await CopyFileOrFolder(infoImgPath, outPath)
|
||||
outImagePath = outPath
|
||||
}
|
||||
subImagePath.push(infoImgPath)
|
||||
}
|
||||
console.log(outImagePath);
|
||||
console.log(subImagePath);
|
||||
|
||||
// 将获取的数据返回
|
||||
return {
|
||||
outImagePath: outImagePath,
|
||||
subImagePath: subImagePath
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 获取出图任务
|
||||
|
||||
async FetchImageTask(task: TaskModal.Task, promptId: string, book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetail: Book.SelectBookTaskDetail, comfyUISettingCollection: ComfyUIModel.ComfyUISettingCollection) {
|
||||
while (true) {
|
||||
try {
|
||||
let resData = await this.GetComfyUIImageTask(promptId, comfyUISettingCollection);
|
||||
|
||||
// 判断他的状态是不是成功
|
||||
if (resData.status == 'error') {
|
||||
// 生图失败
|
||||
await this.bookServiceBasic.UpdateBookTaskDetail(task.bookTaskDetailId, {
|
||||
status: BookTaskStatus.IMAGE_FAIL,
|
||||
});
|
||||
let errorMsg = `MJ生成图片失败,失败信息如下:${resData.message}`
|
||||
await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, {
|
||||
mjApiUrl: comfyUISettingCollection.comfyuiSimpleSetting.requestUrl,
|
||||
progress: 100,
|
||||
category: MJImageType.ComfyUI,
|
||||
imageClick: "",
|
||||
imageShow: "",
|
||||
messageId: promptId,
|
||||
action: MJAction.IMAGINE,
|
||||
status: 'error',
|
||||
message: errorMsg
|
||||
})
|
||||
await this.bookServiceBasic.UpdateTaskStatus({
|
||||
id: task.id,
|
||||
status: BookBackTaskStatus.FAIL,
|
||||
errorMessage: errorMsg
|
||||
});
|
||||
global.newWindow[0].win.webContents.send(task.messageName, {
|
||||
code: 0,
|
||||
message: errorMsg,
|
||||
data: {
|
||||
status: 'error',
|
||||
message: errorMsg,
|
||||
id: task.bookTaskDetailId
|
||||
}
|
||||
})
|
||||
return;
|
||||
} else if (resData.status == 'in_progress') {
|
||||
// 生图中
|
||||
await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, {
|
||||
mjApiUrl: comfyUISettingCollection.comfyuiSimpleSetting.requestUrl,
|
||||
progress: 0,
|
||||
category: MJImageType.ComfyUI,
|
||||
imageClick: "",
|
||||
imageShow: "",
|
||||
messageId: promptId,
|
||||
action: MJAction.IMAGINE,
|
||||
status: 'running',
|
||||
message: "任务正在执行中"
|
||||
})
|
||||
|
||||
global.newWindow[0].win.webContents.send(task.messageName, {
|
||||
code: 1,
|
||||
message: "running",
|
||||
data: {
|
||||
status: 'running',
|
||||
message: "任务正在执行中",
|
||||
id: task.bookTaskDetailId
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
||||
let res = await this.DownloadFileUrl(resData.imageNames, comfyUISettingCollection, book, bookTask, bookTaskDetail);
|
||||
console.log(res);
|
||||
|
||||
// 修改数据库数据
|
||||
// 修改数据库
|
||||
await this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetail.id, {
|
||||
outImagePath: path.relative(define.project_path, res.outImagePath),
|
||||
subImagePath: res.subImagePath.map((item) => path.relative(define.project_path, item))
|
||||
})
|
||||
await this.bookServiceBasic.UpdateTaskStatus({
|
||||
id: task.id,
|
||||
status: BookBackTaskStatus.DONE
|
||||
});
|
||||
|
||||
await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, {
|
||||
mjApiUrl: comfyUISettingCollection.comfyuiSimpleSetting.requestUrl,
|
||||
progress: 100,
|
||||
category: MJImageType.ComfyUI,
|
||||
imageClick: "",
|
||||
imageShow: "",
|
||||
messageId: promptId,
|
||||
action: MJAction.IMAGINE,
|
||||
status: 'success',
|
||||
message: "ComfyUI 生成图片成功"
|
||||
})
|
||||
|
||||
global.newWindow[0].win.webContents.send(task.messageName, {
|
||||
code: 1,
|
||||
message: "ComfyUI 生成图片成功",
|
||||
data: {
|
||||
status: 'success',
|
||||
message: 'ComfyUI 生成图片成功',
|
||||
id: task.bookTaskDetailId,
|
||||
outImagePath: res.outImagePath + "?t=" + new Date().getTime(),
|
||||
subImagePath: res.subImagePath.map((item) => item + "?t=" + new Date().getTime())
|
||||
}
|
||||
})
|
||||
break;
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region ComfyUI 生成图片
|
||||
/**
|
||||
* ComfyUI 生成图片
|
||||
* @param task
|
||||
*/
|
||||
async ComfyUIImageGenerate(task: TaskModal.Task) {
|
||||
try {
|
||||
let comfyUISettingCollection = await this.GetComfyUISetting();
|
||||
|
||||
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId);
|
||||
let book = await this.bookServiceBasic.GetBookDataById(bookTaskDetail.bookId)
|
||||
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskDetail.bookTaskId)
|
||||
|
||||
let prompt = bookTaskDetail.prompt;
|
||||
let negativePrompt = comfyUISettingCollection.comfyuiSimpleSetting.negativePrompt;
|
||||
|
||||
// 开始组合请求体
|
||||
let body = await this.GetComfyUIAPIBody(prompt, negativePrompt, comfyUISettingCollection.comfyuiSelectedWorkflow.workflowPath);
|
||||
|
||||
// 开始发送请求
|
||||
let resData = await this.SubmitComfyUIImagine(body, comfyUISettingCollection);
|
||||
|
||||
// 修改任务状态
|
||||
await this.bookServiceBasic.UpdateBookTaskDetail(task.bookTaskDetailId, {
|
||||
status: BookTaskStatus.IMAGE
|
||||
})
|
||||
await this.bookServiceBasic.UpdateTaskStatus({
|
||||
id: task.id,
|
||||
status: BookBackTaskStatus.RUNNING
|
||||
})
|
||||
global.newWindow[0].win.webContents.send(task.messageName, {
|
||||
code: 0,
|
||||
message: "任务已提交",
|
||||
data: {
|
||||
status: 'submited',
|
||||
message: '任务已提交',
|
||||
id: task.bookTaskDetailId
|
||||
}
|
||||
})
|
||||
|
||||
await this.FetchImageTask(task, resData.prompt_id, book, bookTask, bookTaskDetail, comfyUISettingCollection);
|
||||
|
||||
} catch (error) {
|
||||
|
||||
let errorMsg = "ComfyUI 生图失败,失败信息如下:" + error.toString()
|
||||
await this.bookServiceBasic.UpdateTaskStatus({
|
||||
id: task.id,
|
||||
status: BookBackTaskStatus.FAIL,
|
||||
errorMessage: errorMsg
|
||||
})
|
||||
await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, {
|
||||
mjApiUrl: "",
|
||||
progress: 0,
|
||||
category: MJImageType.ComfyUI,
|
||||
imageClick: "",
|
||||
imageShow: "",
|
||||
messageId: "",
|
||||
action: MJAction.IMAGINE,
|
||||
status: "error",
|
||||
message: errorMsg,
|
||||
})
|
||||
|
||||
global.newWindow[0].win.webContents.send(task.messageName, {
|
||||
code: 0,
|
||||
message: errorMsg,
|
||||
data: {
|
||||
status: 'error',
|
||||
message: errorMsg,
|
||||
id: task.bookTaskDetailId
|
||||
}
|
||||
})
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
@ -257,6 +257,10 @@ export class SDOpt {
|
||||
} else {
|
||||
url = url + "/sdapi/v1/txt2img"
|
||||
}
|
||||
|
||||
// 替换url中的localhost为127.0.0.1
|
||||
url = url.replace('localhost', '127.0.0.1');
|
||||
|
||||
if (!isEmpty(sdSetting.webui.prompt)) {
|
||||
prompt = sdSetting.webui.prompt + ', ' + prompt
|
||||
}
|
||||
@ -345,8 +349,8 @@ export class SDOpt {
|
||||
messageId: subImagePath.join(','),
|
||||
action: MJAction.IMAGINE,
|
||||
status: "success",
|
||||
subImagePath: subImagePath,
|
||||
outImagePath: outImagePath,
|
||||
subImagePath: subImagePath + "?t=" + new Date().getTime(),
|
||||
outImagePath: subImagePath.map((item) => item + "?t=" + new Date().getTime()),
|
||||
message: "SD生成图片成功"
|
||||
}
|
||||
await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, resp)
|
||||
|
||||
@ -9,6 +9,7 @@ import { Base64ToFile, GetImageBase64 } from "../../define/Tools/image";
|
||||
import { BookBackTaskStatus } from "../../define/enum/bookEnum";
|
||||
import { MJAction, MJImageType } from "../../define/enum/mjEnum";
|
||||
import axios from "axios";
|
||||
import { TaskModal } from "@/model/task";
|
||||
export class D3Opt {
|
||||
bookServiceBasic: BookServiceBasic
|
||||
gptService: GptService
|
||||
|
||||
@ -8,6 +8,7 @@ import { GeneralResponse } from '../../../model/generalResponse'
|
||||
import { DEFINE_STRING } from '../../../define/define_string'
|
||||
import { MJOpt } from '../MJ/mj'
|
||||
import { SDOpt } from '../SD/sd'
|
||||
import { ComfyUIOpt } from '../SD/comfyui'
|
||||
import { D3Opt } from '../d3'
|
||||
import { FluxOpt } from '../Flux/flux'
|
||||
import { AsyncQueue } from '../../quene'
|
||||
@ -39,6 +40,8 @@ export class TaskManager {
|
||||
intervalId: any; // 用于存储 setInterval 的 ID
|
||||
mjOpt: MJOpt
|
||||
sdOpt: SDOpt
|
||||
comfyUIOpt: ComfyUIOpt
|
||||
|
||||
d3Opt: D3Opt
|
||||
fluxOpt: FluxOpt
|
||||
|
||||
@ -50,6 +53,7 @@ export class TaskManager {
|
||||
this.reverseBook = new ReverseBook();
|
||||
this.mjOpt = new MJOpt();
|
||||
this.sdOpt = new SDOpt();
|
||||
this.comfyUIOpt = new ComfyUIOpt();
|
||||
this.d3Opt = new D3Opt()
|
||||
this.softWareServiceBasic = new SoftWareServiceBasic();
|
||||
this.fluxOpt = new FluxOpt()
|
||||
@ -254,6 +258,17 @@ export class TaskManager {
|
||||
}, `${batch}_${task.id}`, batch, `${batch}_${task.id}_${new Date().getTime()}`, this.bookServiceBasic.SetMessageNameTaskToFail)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Comfy UI生图任务添加到内存任务中
|
||||
* @param task
|
||||
*/
|
||||
async AddComfyUIImage(task: TaskModal.Task) {
|
||||
let batch = task.messageName;
|
||||
global.requestQuene.enqueue(async () => {
|
||||
await this.comfyUIOpt.ComfyUIImageGenerate(task);
|
||||
}, `${batch}_${task.id}`, batch, `${batch}_${task.id}_${new Date().getTime()}`, this.bookServiceBasic.SetMessageNameTaskToFail)
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步添加D3图像生成任务
|
||||
*
|
||||
@ -350,6 +365,9 @@ export class TaskManager {
|
||||
case BookBackTaskType.SD_IMAGE:
|
||||
this.AddSDImage(task);
|
||||
break;
|
||||
case BookBackTaskType.ComfyUI_IMAGE:
|
||||
this.AddComfyUIImage(task);
|
||||
break;
|
||||
case BookBackTaskType.D3_IMAGE:
|
||||
this.AddD3Image(task);
|
||||
break;
|
||||
|
||||
43
src/model/comfyui.d.ts
vendored
Normal file
43
src/model/comfyui.d.ts
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
declare namespace ComfyUIModel {
|
||||
|
||||
/** ComfyUI的基础设置的模型 */
|
||||
interface ComfyUI_SimpleSettingModel {
|
||||
/** 请求地址 */
|
||||
requestUrl: string,
|
||||
/** 选择的工作流 */
|
||||
selectedWorkflow: string,
|
||||
/** 反向提示词 */
|
||||
negativePrompt: string,
|
||||
}
|
||||
|
||||
/** ComfyUI 工作流设置的模型 */
|
||||
interface ComfyUI_WorkFlowSettingModel {
|
||||
/** 设置的ID */
|
||||
id: string,
|
||||
/** 自定义的名字 */
|
||||
name: string,
|
||||
/** 工作流的地址 */
|
||||
workflowPath: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* ComfyUI的设置集合
|
||||
*/
|
||||
interface ComfyUISettingCollection {
|
||||
/**
|
||||
* ComfyUI的基础设置
|
||||
*/
|
||||
comfyuiSimpleSetting: ComfyUI_SimpleSettingModel,
|
||||
/**
|
||||
* ComfyUI的工作流集合
|
||||
*/
|
||||
comfyuiWorkFlowSetting: Array<ComfyUI_WorkFlowSettingModel>,
|
||||
|
||||
/**
|
||||
* 当前选中的工作流
|
||||
*/
|
||||
comfyuiSelectedWorkflow: ComfyUI_WorkFlowSettingModel
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
12
src/model/option/option.d.ts
vendored
12
src/model/option/option.d.ts
vendored
@ -109,9 +109,15 @@ declare namespace OptionModel {
|
||||
/** 超时时间,单位 毫秒 */
|
||||
timeOut: number
|
||||
}
|
||||
|
||||
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region SD/MJ
|
||||
|
||||
/** ComfyUI的基础设置的模型 */
|
||||
interface ComfyUI_SimpleSettingModel extends ComfyUIModel.ComfyUI_SimpleSettingModel { };
|
||||
|
||||
/** ComfyUI 工作流设置的模型 */
|
||||
interface ComfyUI_WorkFlowSettingModel extends ComfyUIModel.ComfyUI_WorkFlowSettingModel { };
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
@ -194,6 +194,11 @@ const book = {
|
||||
/** 保存缓存区的屠图片到小说主图或者是选图区 */
|
||||
SaveCacheImageToData: async (bookTaskDetailId: string, imageFile: string | string[], option: string) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.SAVE_CACHE_IMAGE_TO_DATA, bookTaskDetailId, imageFile, option),
|
||||
|
||||
/** 删除缓存区中的图片 */
|
||||
DeleteCacheImage: async (bookTaskId: string, imageFile: string) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.DELETE_CACHE_IMAGE, bookTaskId, imageFile),
|
||||
|
||||
/** 移动指定的图片链接到主图 */
|
||||
MoveImageToMainImage: async (bookTaskId: string, bookTaskDetailId: string, sourceImagePath: string) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.MOVE_IMAGE_TO_MAIN_IMAGE, bookTaskId, bookTaskDetailId, sourceImagePath),
|
||||
//#endregion
|
||||
|
||||
//#region 一键反推的单个任务
|
||||
|
||||
@ -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>
|
||||
313
src/renderer/src/components/Setting/SDSetting/ComfyUISetting.vue
Normal file
313
src/renderer/src/components/Setting/SDSetting/ComfyUISetting.vue
Normal file
@ -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>
|
||||
@ -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',
|
||||
|
||||
@ -31,6 +31,16 @@ export type OptionStoreModel = {
|
||||
/** MJ 基础设置 */
|
||||
[OptionKeyName.MJ_GlobalSetting]: MJSettingModel.MJ_GlobalSettingModel;
|
||||
//#endregion
|
||||
|
||||
//#region SD/ComfyUI
|
||||
|
||||
/** ComfyUI的基础设置 */
|
||||
[OptionKeyName.ComfyUI_SimpleSetting]: OptionModel.ComfyUI_SimpleSettingModel;
|
||||
|
||||
/** COmfyUI的工作流设置 */
|
||||
[OptionKeyName.ComfyUI_WorkFlowSetting]: Array<OptionModel.ComfyUI_WorkFlowSettingModel>;
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
export const useOptionStore = defineStore('option', {
|
||||
@ -104,7 +114,13 @@ export const useOptionStore = defineStore('option', {
|
||||
mj_remoteSimpleSetting: {
|
||||
useTransfer: false
|
||||
}
|
||||
}
|
||||
},
|
||||
[OptionKeyName.ComfyUI_SimpleSetting]: {
|
||||
requestUrl: "",
|
||||
negativePrompt: "",
|
||||
selectedWorkflow: null
|
||||
},
|
||||
[OptionKeyName.ComfyUI_WorkFlowSetting]: []
|
||||
|
||||
} as unknown as OptionStoreModel),
|
||||
getters: {
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"./package.json"
|
||||
, "src/renderer/src/components/Book/Components/.vue" ]
|
||||
"./package.json",
|
||||
"src/renderer/src/components/Book/Components/.vue"
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user