import { isEmpty, join } from "lodash"; import { Book } from "../../../model/book"; import { checkStringValueAddPrefix, checkStringValueAddSuffix, errorMessage, successMessage } from "../../Public/generalTools"; import { BookBackTaskListService } from "../../../define/db/service/Book/bookBackTaskListService"; import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService"; import { CheckFolderExistsOrCreate, CopyFileOrFolder, JoinPath } from "../../../define/Tools/file"; import { define } from "../../../define/define" import { GetImageBase64, ImageSplit } from "../../../define/Tools/image"; import MJApi from "./mjApi" import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, DialogType, MJAction, OperateBookType, TaskExecuteType } from "../../../define/enum/bookEnum"; import { DEFINE_STRING } from "../../../define/define_string"; import { MJ } from "../../../model/mj"; import { MJRespoonseType } from "../../../define/enum/mjEnum"; import { MJSetting } from "../../../model/Setting/mjSetting"; import { GeneralResponse } from "../../../model/generalResponse" import { LoggerStatus, ResponseMessageType } from "../../../define/enum/softwareEnum"; import { BookTaskService } from "../../../define/db/service/Book/bookTaskService"; import { ReverseBook } from "../Book/ReverseBook"; import { ImageStyle } from "../Book/imageStyle"; import { TaskScheduler } from "../taskScheduler"; import { BookService } from "../../../define/db/service/Book/bookService"; import { Tools } from "../../../main/tools" import { MJSettingService } from "../../../define/db/service/SoftWare/mjSettingService"; import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; import path from "path" const { v4: uuidv4 } = require('uuid') import fs from "fs" const fspromise = fs.promises export class MJOpt { bookBackTaskList: BookBackTaskListService bookTaskDetail: BookTaskDetailService reverseBook: ReverseBook; bookTaskService: BookTaskService mjApi: MJApi; mjSetting: MJSetting.MjSetting imageStyle: ImageStyle; taskScheduler: TaskScheduler; bookService: BookService tools: Tools; mjSettingService: MJSettingService; bookServiceBasic: BookServiceBasic constructor() { this.imageStyle = new ImageStyle() this.taskScheduler = new TaskScheduler() this.tools = new Tools() this.bookServiceBasic = new BookServiceBasic(); } async InitService() { if (!this.reverseBook) { this.reverseBook = new ReverseBook() } if (!this.bookBackTaskList) { this.bookBackTaskList = await BookBackTaskListService.getInstance() } if (!this.bookTaskDetail) { this.bookTaskDetail = await BookTaskDetailService.getInstance() } if (!this.mjApi) { this.mjApi = new MJApi() } if (!this.bookTaskService) { this.bookTaskService = await BookTaskService.getInstance() } if (!this.bookService) { this.bookService = await BookService.getInstance() } if (!this.mjSettingService) { this.mjSettingService = await MJSettingService.getInstance() } this.mjSetting = await this.mjApi.InitMJSetting() } /** * 返回MJ的数据到前端界面,前端界面做出相应的改变 * @param {*} data */ sendChangeMessage(data: GeneralResponse.MessageResponse, message_name = DEFINE_STRING.BOOK.MAIN_DATA_RETURN) { global.newWindow[0].win.webContents.send(message_name, data) } //#region 选择反推的提示词相关 /** * MJ反推,将反推的数据写入到GPT中 * @param bookTaskDetailId * @param index */ async SingleReverseToGptPrompt(bookTaskDetailId: string, index: number): Promise { try { await this.InitService(); let bookTaskDetail = this.bookTaskDetail.GetBookTaskDetailDataById(bookTaskDetailId) if (bookTaskDetail == null) { throw new Error("没有找到对应的数据") } let reversePrompts = bookTaskDetail.reversePrompt if (!reversePrompts || reversePrompts.length <= 0) { throw new Error("没有找到对应的反推提示词数据") } let reversePrompt = reversePrompts[index] let gptPrompt = reversePrompt.promptCN ? reversePrompt.promptCN : reversePrompt.prompt // 开始修改 this.bookTaskDetail.UpdateBookTaskDetail(bookTaskDetailId, { gptPrompt: gptPrompt }) // 保存完毕 return successMessage(gptPrompt, "反推数据写出成功", "ReverseBook_ReversePromptToGptPrompt") } catch (error) { return errorMessage("反推数据写出失败,错误信息如下:" + error.message, "ReverseBook_ReversePromptToGptPrompt") } } //#endregion //#region MJ反推相关 /** * 循环请求数据,判断反推任务是不是完成,返回数据 * @param task * @param reqRes */ async fetchWithRetry(task: TaskModal.Task, reqRes: string) { while (true) { try { // 执行你的操作 let task_res = await this.mjApi.GetMJAPITaskById(reqRes, task.id); // 判断他的状态是不是成功 if (task_res.code == 0) { // 反推失败 this.bookTaskDetail.UpdateBookTaskDetail(task.bookTaskDetailId, { status: BookTaskStatus.REVERSE_FAIL }); this.bookBackTaskList.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.FAIL, errorMessage: task_res.message }); throw new Error(`${task_res.message}`); } else { if (task_res.progress == 100) { task_res.type == MJRespoonseType.FINISHED; this.bookBackTaskList.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.DONE }); // 这边还要再处理一下数据,将反推获取的数据进行切割处理 let reversePrompt = []; if (task_res.prompt != undefined && task_res.prompt != "" && task_res.prompt != null) { let string_res = task_res.prompt.split(/(?=\d️⃣)/).map(part => part.replace(/^\d️⃣\s*/, '').trim()); reversePrompt = string_res.map((item) => { return { id: uuidv4(), bookTaskDetailId: task.bookTaskDetailId, prompt: item, promptCN: item, isSelect: false }; }); } this.bookTaskDetail.UpdateBookTaskDetail(task.bookTaskDetailId, { status: BookTaskStatus.REVERSE_DONE, reversePrompt: reversePrompt, gptPrompt: undefined }); task_res.prompt = JSON.stringify(reversePrompt); task_res.id = task.bookTaskDetailId; this.sendChangeMessage({ code: 1, type: ResponseMessageType.MJ_REVERSE, id: task.bookTaskDetailId, data: task_res }); break; } else { // 当获取的图片的进度小于100的时候,等待5秒继续监听 await new Promise(resolve => setTimeout(resolve, 5000)); } } task_res.id = task.bookTaskDetailId; this.sendChangeMessage({ code: 1, type: ResponseMessageType.MJ_REVERSE, id: task.bookTaskDetailId, data: task_res }); } catch (error) { throw error; } } }; /** * MJ反推 * @param task */ async MJImage2Text(task: TaskModal.Task): Promise { try { if (isEmpty(task.bookTaskDetailId)) { throw new Error("MJ反推,没有找到对应的分镜信息") } await this.InitService() let bookTaskDetail = this.bookTaskDetail.GetBookTaskDetailDataById(task.bookTaskDetailId); let oldImagePath = bookTaskDetail.oldImage if (isEmpty(oldImagePath)) { throw new Error(`${bookTaskDetail.name} 没有需要反推的图片`); } oldImagePath = JoinPath(define.project_path, oldImagePath) let imageBase64 = await GetImageBase64(oldImagePath) // 这个就是任务ID let reqRes = await this.mjApi.SubmitMJDescribe({ image: imageBase64, taskId: task.id }) if (reqRes == '23') { // 任务队列过多,重新提交排队 this.bookBackTaskList.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.RECONNECT, }) // throw new Error(`任务队列过多,${task.bookTaskDetailId} 重新提交排队`); return; } this.bookTaskDetail.UpdateBookTaskDetail(task.bookTaskDetailId, { status: BookTaskStatus.REVERSE }) this.sendChangeMessage({ code: 1, type: ResponseMessageType.MJ_REVERSE, id: task.bookTaskDetailId, data: { code: 1, type: MJRespoonseType.UPDATED, mjType: MJAction.DESCRIBE, category: this.mjSetting.type, message_id: reqRes, id: task.bookTaskDetailId, progress: 0, status: "success" } as MJ.MJResponseToFront }) await this.fetchWithRetry(task, reqRes); } catch (error) { console.log(error.toString()) let errorMsg = "MJ反推失败,失败信息如下:" + error.toString() this.bookBackTaskList.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.FAIL, errorMessage: errorMsg }) this.sendChangeMessage({ code: 0, id: task.bookTaskDetailId, type: ResponseMessageType.MJ_REVERSE, dialogType: DialogType.NOTIFICATION, message: errorMsg, data: { code: 0, type: MJRespoonseType.UPDATED, mjType: MJAction.DESCRIBE, category: this.mjSetting.type, message_id: undefined, id: task.bookTaskDetailId, progress: 0, message: error.toString(), status: "failure" } }) return errorMessage(errorMsg, "MJReverse_MJImage2Text") } } //#endregion //#region 合并提示词相关 /** * MJ 进行合并提示词,通过类型进行判断是单个还是全部合并 * @param id 合并的ID * @param mergeType 合并的类型 */ async MergePrompt(id: string, operateBookType: OperateBookType): Promise { try { await this.InitService() let bookTaskDetail = undefined as Book.SelectBookTaskDetail[]; let bookTask = undefined as Book.SelectBookTask; if (operateBookType == OperateBookType.BOOKTASK) { bookTaskDetail = this.bookTaskDetail.GetBookTaskData({ bookTaskId: id }).data bookTask = this.bookTaskService.GetBookTaskDataById(id); // 判断是不是有为空的 let emptyName = [] as string[] for (let i = 0; i < bookTaskDetail.length; i++) { const element = bookTaskDetail[i]; if (isEmpty(element.gptPrompt)) { emptyName.push(element.name) } } if (emptyName.length > 0) { throw new Error(`${emptyName.join(',')} 的提示词为空,请先推理`) } } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { let tempBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id); if (isEmpty(tempBookTaskDetail.gptPrompt)) { throw new Error("当前分镜没有推理提示词,请先生成") } bookTaskDetail = [tempBookTaskDetail] bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail[0].bookTaskId); } else { throw new Error("未知的合并类型") } // 获取合并的排序 let imageBaseSetting = JSON.parse(await fspromise.readFile(define.img_base, 'utf-8')); // 没有就直接报错 let promptSort = imageBaseSetting.prompt_sort; // 没有就直接报错 if (!promptSort) { throw new Error("未找到提示词排序,请先设置") } // let suffixParam = imageBaseSetting.mj_config.image_suffix ; // 没有就直接报错 let mjSettingDb = this.mjSettingService.GetMjSetting({}).data if (mjSettingDb.length <= 0) { throw new Error("请先添加MJ配置") } let suffixParam = mjSettingDb[0].imageSuffix // let styleString = ''; // 拿到所有的风格 let styleArr = await this.imageStyle.GetAllImageStyleList(bookTask.imageStyle, bookTask.customizeImageStyle); let result = [] for (let i = 0; i < bookTaskDetail.length; i++) { const element = bookTaskDetail[i]; let promptStr = ''; for (let i = 0; i < promptSort.length; i++) { const element = promptSort[i]; promptStr += `${'${' + element.value + '}'} ` } console.log(promptStr) // TODO 后面需要完善人物和场景的提示词 let characterString = '' // 人物 let sceneString = "" // 场景 // 获取当前的自定义风格的垫图字符串 let styleString = "" let style_url = '' let sw = undefined styleArr.forEach((item) => { if (item.type && item.type == 'style_main') { if (sw == undefined) { sw = item.sref_sw } if (!isEmpty(item.image_url)) { let url = item.image_url ? item.image_url : '' style_url += ' ' + url } if (item.prompt) { styleString += item.prompt + ',' } } else { styleString += item.english_style + ',' } }) style_url = checkStringValueAddPrefix(style_url, '--sref ') if (sw != undefined) { style_url = checkStringValueAddSuffix(style_url, ` --sw ${sw}`) } let cref_url = '' promptStr = promptStr.replace('${style}', styleString) promptStr = promptStr.replace('${character}', characterString) promptStr = promptStr.replace('${scene}', sceneString) promptStr = promptStr.replace( '${prompt}', checkStringValueAddSuffix(element.gptPrompt, ',') ) // 判断是不是需要加前后缀 if (bookTask.prefixPrompt) { promptStr = checkStringValueAddSuffix(bookTask.prefixPrompt, ',') + promptStr } if (bookTask.prefixPrompt) { promptStr = checkStringValueAddSuffix(promptStr, ',') + bookTask.prefixPrompt } promptStr = ' ' + promptStr; promptStr += ` ${cref_url} ${style_url}${suffixParam}` // 修改数据库数据 this.bookTaskDetail.UpdateBookTaskDetail(element.id, { prompt: promptStr }) // 写回数据 result.push({ id: element.id, prompt: promptStr }) } return successMessage(result, "MJ模式合并数据成功", "MJOpt_MergePrompt") } catch (error) { return errorMessage("MJ合并提示词失败,错误信息如下:" + error.message, "MJOpt_MergePrompt") } } //#endregion //#region MJ生成图片相关 /** * 单个生成图片,将任务添加到队列中 * @param id 要添加的ID * @param operateBookType 操作的类型 */ async AddMJGenerateImageTask(id: string, operateBookType: OperateBookType): Promise { try { await this.InitService() let bookTaskDetail = undefined as Book.SelectBookTaskDetail[]; // let bookTask = undefined as Book.SelectBookTask; if (operateBookType == OperateBookType.BOOKTASK) { bookTaskDetail = this.bookTaskDetail.GetBookTaskData({ bookTaskId: id }).data // bookTask = this.bookTaskService.GetBookTaskDataById(id); } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { bookTaskDetail = [this.bookTaskDetail.GetBookTaskDetailDataById(id)] // bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail[0].bookTaskId); } else if (operateBookType == OperateBookType.UNDERBOOKTASK) { let thisBookTaskDetail = this.bookTaskDetail.GetBookTaskDetailDataById(id); if (thisBookTaskDetail == null) { throw new Error("没有找到对应的数据") } // 获取批次的所有数据 bookTaskDetail = this.bookTaskDetail.GetBookTaskData({ bookTaskId: thisBookTaskDetail.bookTaskId }).data // bookTask = this.bookTaskService.GetBookTaskDataById(thisBookTaskDetail.bookTaskId); bookTaskDetail = bookTaskDetail.filter((item) => item.no >= thisBookTaskDetail.no) // 需要包含自己 } else { throw new Error("MJOpt_AddGenerateImageTask,未知的操作类型") } // 将被锁定的数据过滤掉 bookTaskDetail = bookTaskDetail.filter((item) => item.imageLock == false) if (bookTaskDetail.length <= 0) { throw new Error("没有找到可以生成图片的分镜,可能是已经被锁定") } // 将任务添加到队列中 for (let i = 0; i < bookTaskDetail.length; i++) { const element = bookTaskDetail[i]; let taskRes = await this.bookBackTaskList.AddBookBackTask(element.bookId, BookBackTaskType.MJ_IMAGE, TaskExecuteType.AUTO, element.bookTaskId, element.id ); if (taskRes.code == 0) { throw new Error(taskRes.message) } // 添加返回日志 await this.taskScheduler.AddLogToDB(element.bookId, BookBackTaskType.MJ_IMAGE, `添加 ${element.name} MJ生成视频任务成功`, element.bookTaskId, LoggerStatus.SUCCESS) } // 全部完毕 return successMessage(null, "MJ添加生成图片任务成功", "MJOpt_AddGenerateImageTask") } catch (error) { return errorMessage("MJ添加生成图片任务失败,错误信息如下:" + error.message, "MJOpt_AddGenerateImageTask") } } /** * 循环请求数据,判断反推任务是不是完成,返回数据 * @param task * @param reqRes */ async FetchImageTask(task: TaskModal.Task, reqRes: string, book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetail: Book.SelectBookTaskDetail) { while (true) { try { // 执行你的操作 let task_res = await this.mjApi.GetMJAPITaskById(reqRes, task.id); task_res.id = task.bookTaskDetailId; // 判断他的状态是不是成功 if (task_res.code == 0) { // 生图失败 this.bookTaskDetail.UpdateBookTaskDetail(task.bookTaskDetailId, { status: BookTaskStatus.IMAGE_FAIL, }); this.bookTaskDetail.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { mjApiUrl: this.mjApi.imagineUrl, progress: 0, category: this.mjApi.mjSetting.type, imageClick: task_res.image_click, imageShow: task_res.image_show, messageId: task_res.message_id, action: MJAction.IMAGINE, status: 'error', message: task_res.message }) this.bookBackTaskList.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.FAIL, errorMessage: task_res.message }); this.sendChangeMessage({ code: 0, type: ResponseMessageType.MJ_IMAGE, id: task.bookTaskDetailId, data: { ...task_res, status: "error" }, message: task_res.message }); return; // throw new Error(`${task_res.message}`); } else { if (task_res.progress == 100) { task_res.type == MJRespoonseType.FINISHED; console.log(task.id, "22222") this.bookBackTaskList.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.DONE }); // 下载图片 let imagePath = path.join(book.bookFolderPath, `data\\MJOriginalImage\\${task_res.message_id}.png`); await CheckFolderExistsOrCreate(path.dirname(imagePath)) await this.tools.downloadFileUrl(task_res.image_click, imagePath) // 进行图片裁剪 let imageRes = await ImageSplit(imagePath, bookTaskDetail.name, path.join(book.bookFolderPath, 'data\\MJOriginalImage')); if (imageRes && imageRes.length < 4) { throw new Error("图片裁剪失败") } // 修改数据库数据,将图片保存到对应的文件夹中 let firstImage = imageRes[0]; if (book.type == BookType.ORIGINAL) { await CopyFileOrFolder(firstImage, path.join(book.bookFolderPath, `tmp\\input\\${bookTaskDetail.name}.png`)); } let out_file = path.join(bookTask.imageFolder, `${bookTaskDetail.name}.png`) await CopyFileOrFolder(firstImage, out_file); task_res.outImagePath = firstImage; task_res.subImagePath = imageRes; task_res.id = task.bookTaskDetailId; // 修改分镜的数据 this.bookTaskDetail.UpdateBookTaskDetail(task.bookTaskDetailId, { outImagePath: path.relative(define.project_path, out_file), subImagePath: imageRes.map((item) => path.relative(define.project_path, item)) }) this.bookTaskDetail.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { mjApiUrl: this.mjApi.imagineUrl, progress: 100, category: this.mjApi.mjSetting.type, imageClick: task_res.image_click, imageShow: task_res.image_show, messageId: task_res.message_id, action: MJAction.IMAGINE, status: task_res.status, }) this.sendChangeMessage({ code: 1, type: ResponseMessageType.MJ_IMAGE, id: task.bookTaskDetailId, data: task_res }); break; } } // 这边也要修改数据 task_res.id = task.bookTaskDetailId; this.bookTaskDetail.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { mjApiUrl: this.mjApi.imagineUrl, progress: task_res.progress, category: this.mjApi.mjSetting.type, imageClick: task_res.image_click, imageShow: task_res.image_show, messageId: task_res.message_id, action: MJAction.IMAGINE, status: task_res.status, message: task_res.message }) task_res.outImagePath = task_res.image_path; this.sendChangeMessage({ code: 1, type: ResponseMessageType.MJ_IMAGE, id: task.bookTaskDetailId, data: task_res }); // 当获取的图片的进度小于100的时候,等待5秒继续监听 await new Promise(resolve => setTimeout(resolve, 5000)); } catch (error) { throw error; } } }; /** * MJ生成图片 * @param task */ async MJImagine(task: TaskModal.Task): Promise { try { if (isEmpty(task.bookTaskDetailId)) { throw new Error("MJ出图,没有找到对应的分镜信息") } await this.InitService() let bookTaskDetail = this.bookTaskDetail.GetBookTaskDetailDataById(task.bookTaskDetailId); if (bookTaskDetail == null) { throw new Error("没有找到对应的分镜信息") } let book = this.bookService.GetBookDataById(bookTaskDetail.bookId) if (book == null) { throw new Error("没有找到对应的小说信息") } let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail.bookTaskId) if (bookTask == null) { throw new Error("没有找到对应的任务信息") } let prompt = bookTaskDetail.prompt if (isEmpty(prompt)) { throw new Error(`${bookTaskDetail.name} 没有找到对应的提示词`) } // 这个就是任务ID let reqRes = await this.mjApi.SubmitMJImagineAPI(task.id, prompt) if (reqRes == '23') { console.log(task.id, "33333") // 任务队列过多,重新提交排队 this.bookBackTaskList.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.RECONNECT, }) this.sendChangeMessage({ code: 1, type: ResponseMessageType.MJ_IMAGE, id: task.bookTaskDetailId, data: { code: 1, type: MJRespoonseType.UPDATED, mjType: MJAction.IMAGINE, category: this.mjSetting.type, message_id: '', id: task.bookTaskDetailId, progress: 0, status: "re_connect" } as MJ.MJResponseToFront }) this.bookTaskDetail.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { mjApiUrl: this.mjApi.imagineUrl, progress: 0, category: this.mjApi.mjSetting.type, imageClick: "", imageShow: "", messageId: "", action: MJAction.IMAGINE, status: "re_connect", }) // throw new Error(`任务队列过多,${task.bookTaskDetailId} 重新提交排队`); return; } this.bookTaskDetail.UpdateBookTaskDetail(task.bookTaskDetailId, { status: BookTaskStatus.IMAGE }) this.bookBackTaskList.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.RUNNING }) this.sendChangeMessage({ code: 1, type: ResponseMessageType.MJ_IMAGE, id: task.bookTaskDetailId, data: { code: 1, type: MJRespoonseType.UPDATED, mjType: MJAction.IMAGINE, category: this.mjSetting.type, message_id: reqRes, id: task.bookTaskDetailId, progress: 0, status: "submited" } as MJ.MJResponseToFront }) await this.FetchImageTask(task, reqRes, book, bookTask, bookTaskDetail); } catch (error) { console.log(error.toString()) let errorMsg = "MJ生图失败,失败信息如下:" + error.toString() console.log(task.id, "44444") this.bookBackTaskList.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.FAIL, errorMessage: errorMsg }) this.sendChangeMessage({ code: 0, id: task.bookTaskDetailId, type: ResponseMessageType.MJ_IMAGE, dialogType: DialogType.NOTIFICATION, message: errorMsg, data: { code: 0, type: MJRespoonseType.UPDATED, mjType: MJAction.IMAGINE, category: this.mjSetting.type, message_id: undefined, id: task.bookTaskDetailId, progress: 0, message: error.toString(), status: "error" } }) this.bookTaskDetail.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { mjApiUrl: this.mjApi.imagineUrl, progress: 0, category: this.mjApi.mjSetting.type, imageClick: "", imageShow: "", messageId: "", action: MJAction.IMAGINE, status: "error", message: error.toString() }) return errorMessage(errorMsg, "MJReverse_MJImage2Text") } } //#endregion }