diff --git a/package-lock.json b/package-lock.json index 26b2b6b..6c27789 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "laitool", - "version": "3.0.4", + "version": "3.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "laitool", - "version": "3.0.4", + "version": "3.1.1", "hasInstallScript": true, "dependencies": { "@alicloud/alimt20181012": "^1.2.0", @@ -12283,4 +12283,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index f6280de..a1720a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "laitool", - "version": "3.0.4", + "version": "3.1.1", "description": "An AI tool for image processing, video processing, and other functions.", "main": "./out/main/index.js", "author": "laitool.cn", diff --git a/resources/image/c_s/1236e3d6-c07f-4093-9bb1-a8d73401889a.png b/resources/image/c_s/1236e3d6-c07f-4093-9bb1-a8d73401889a.png new file mode 100644 index 0000000..3aec420 Binary files /dev/null and b/resources/image/c_s/1236e3d6-c07f-4093-9bb1-a8d73401889a.png differ diff --git a/resources/image/c_s/6d4245d0-18b8-4cc3-8827-af93d7de6c72.png b/resources/image/c_s/6d4245d0-18b8-4cc3-8827-af93d7de6c72.png new file mode 100644 index 0000000..3aec420 Binary files /dev/null and b/resources/image/c_s/6d4245d0-18b8-4cc3-8827-af93d7de6c72.png differ diff --git a/resources/image/c_s/a5ca6031-62a5-4a05-9d2a-1b7dc857e7fe.png b/resources/image/c_s/a5ca6031-62a5-4a05-9d2a-1b7dc857e7fe.png new file mode 100644 index 0000000..3aec420 Binary files /dev/null and b/resources/image/c_s/a5ca6031-62a5-4a05-9d2a-1b7dc857e7fe.png differ diff --git a/resources/image/c_s/a899bc65-317d-496d-a359-cbd3fb747c50.png b/resources/image/c_s/a899bc65-317d-496d-a359-cbd3fb747c50.png new file mode 100644 index 0000000..3aec420 Binary files /dev/null and b/resources/image/c_s/a899bc65-317d-496d-a359-cbd3fb747c50.png differ diff --git a/resources/image/c_s/f2e95c70-2267-4f7f-a93a-b095f7846604.png b/resources/image/c_s/f2e95c70-2267-4f7f-a93a-b095f7846604.png new file mode 100644 index 0000000..3aec420 Binary files /dev/null and b/resources/image/c_s/f2e95c70-2267-4f7f-a93a-b095f7846604.png differ diff --git a/resources/scripts/db/book.realm.lock b/resources/scripts/db/book.realm.lock index 2be42e7..eff58b5 100644 Binary files a/resources/scripts/db/book.realm.lock and b/resources/scripts/db/book.realm.lock differ diff --git a/resources/scripts/db/software.realm b/resources/scripts/db/software.realm index 5ec3fa8..57b6dd7 100644 Binary files a/resources/scripts/db/software.realm and b/resources/scripts/db/software.realm differ diff --git a/resources/scripts/db/software.realm.lock b/resources/scripts/db/software.realm.lock index cd65c2d..f6d4476 100644 Binary files a/resources/scripts/db/software.realm.lock and b/resources/scripts/db/software.realm.lock differ diff --git a/resources/scripts/db/tts.realm.lock b/resources/scripts/db/tts.realm.lock index 8ddc843..c6f32a4 100644 Binary files a/resources/scripts/db/tts.realm.lock and b/resources/scripts/db/tts.realm.lock differ diff --git a/src/define/Tools/common.ts b/src/define/Tools/common.ts index 10e6639..de20486 100644 --- a/src/define/Tools/common.ts +++ b/src/define/Tools/common.ts @@ -37,3 +37,33 @@ export async function RetryWithBackoff(fn: () => Promise, retries: number } throw new Error('所有重试失败'); // 理论上不会到达这里 } + +/** + * 并发执行任务(控制同时执行的任务数) + * @param tasks 总的任务列表 + * @param concurrentCount 同时执行的数量 + * @returns + */ +export async function ExecuteConcurrently(tasks: Array<() => Promise>, concurrentCount: number): Promise { + let activeTasks: Array> = []; + let results: Array> = []; + + while (tasks.length > 0) { + if (activeTasks.length < concurrentCount) { + let task = tasks.shift(); + let promise = task().then(result => { + activeTasks = activeTasks.filter(t => t !== promise); + return result; + }).catch(error => { + // 抛出任务,停止所有的任务 + tasks.length = 0; + throw error + }); + activeTasks.push(promise); + results.push(promise); + } else { + await Promise.race(activeTasks); + } + } + return Promise.all(results); +} \ No newline at end of file diff --git a/src/define/Tools/file.ts b/src/define/Tools/file.ts index ffd7454..22a40b4 100644 --- a/src/define/Tools/file.ts +++ b/src/define/Tools/file.ts @@ -1,6 +1,9 @@ import fs from 'fs' import { isEmpty } from 'lodash' import path from 'path' +import util from 'util' +import { exec } from 'child_process' +const execAsync = util.promisify(exec) const fspromises = fs.promises /** @@ -253,3 +256,21 @@ export async function GetSubdirectories(folderPath: string): Promise { throw new Error(error); } } + +/** + * 删除目标图片,然后将原图片的exif信息删除,然后将原图片复制到目标图片地址 + * @param {*} exiftoolPath exiftool的地址 + * @param {*} source 原图片地址 + * @param {*} target 目标图片地址 + */ +export async function DeleteFileExifData(exiftoolPath: string, source: string, target: string) { + try { + if (await CheckFileOrDirExist(target)) { + await fspromises.unlink(target) + } + let script = `"${exiftoolPath}" -all= -overwrite_original "${source}" -o "${target}"` + const output = await execAsync(script, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' }) + } catch (error) { + throw error + } +} diff --git a/src/define/Tools/image.ts b/src/define/Tools/image.ts index 79e12e7..768e4eb 100644 --- a/src/define/Tools/image.ts +++ b/src/define/Tools/image.ts @@ -1,6 +1,6 @@ import path from 'path' import sharp from 'sharp' -import { CheckFileOrDirExist } from './file' +import { CheckFileOrDirExist, CheckFolderExistsOrCreate } from './file' import fs from 'fs' import https from 'https' @@ -237,7 +237,7 @@ export async function Base64ToFile(base64: string, outFilePath: string): Promise let base64Data = base64.replace(/^data:image\/\w+;base64,/, '') let dataBuffer = Buffer.from(base64Data, 'base64') let out_folder = path.dirname(outFilePath) - await this.tools.checkFolderExistsOrCreate(out_folder) + await CheckFolderExistsOrCreate(out_folder) await fs.promises.writeFile(outFilePath, dataBuffer) // await this.tools.writeArrayToFile(dataBuffer, out_file); } catch (error) { diff --git a/src/define/db/model/Book/BookBackTaskListModel.ts b/src/define/db/model/Book/BookBackTaskListModel.ts index 3661582..1bf6036 100644 --- a/src/define/db/model/Book/BookBackTaskListModel.ts +++ b/src/define/db/model/Book/BookBackTaskListModel.ts @@ -15,6 +15,7 @@ export class BookBackTaskList extends Realm.Object { updateTime: Date startTime: number endTime: number + messageName?: string static schema: ObjectSchema = { name: 'BookBackTaskList', @@ -31,7 +32,8 @@ export class BookBackTaskList extends Realm.Object { createTime: 'date', updateTime: 'date', startTime: 'int', - endTime: 'int' + endTime: 'int', + messageName: 'string?' }, primaryKey: 'id' } diff --git a/src/define/db/model/Book/bookTaskDetail.ts b/src/define/db/model/Book/bookTaskDetail.ts index 327cfe2..8c92e24 100644 --- a/src/define/db/model/Book/bookTaskDetail.ts +++ b/src/define/db/model/Book/bookTaskDetail.ts @@ -141,6 +141,7 @@ export class BookTaskDetailModel extends Realm.Object { timeLimit: string | null // 事件实现(0 -- 3000) subValue: string | null // 包含的字幕数据 characterTags: string[] | null // 角色标签 + sceneTags: string[] | null // 场景标签 gptPrompt: string | null // GPT提示词 mjMessage: MJMessage | null // MJ消息 outImagePath: string | null // 输出图片地址 @@ -174,6 +175,7 @@ export class BookTaskDetailModel extends Realm.Object { subValue: 'string?', reversePrompt: { type: 'list', objectType: 'ReversePrompt' }, characterTags: { type: 'list', objectType: 'string' }, + sceneTags: 'string[]', gptPrompt: 'string?', mjMessage: 'MJMessage?', outImagePath: 'string?', diff --git a/src/define/db/model/SoftWare/preset.ts b/src/define/db/model/SoftWare/preset.ts new file mode 100644 index 0000000..e5cb844 --- /dev/null +++ b/src/define/db/model/SoftWare/preset.ts @@ -0,0 +1,36 @@ +import Realm, { ObjectSchema } from 'realm' + +export class PresetModel extends Realm.Object { + id: string + label: string + type: string + showImage?: string + prompt: string + chinesePrompt?: string + imageUrl?: string + srefSw?: number + crefCw?: number + lora?: string + loraWeight?: number + isShow: boolean + children?: string + static schema: ObjectSchema = { + name: 'PresetModel', + properties: { + id: 'string', + label: 'string', + type: 'string', + showImage: 'string?', + imageUrl: 'string?', + prompt: 'string', + chinesePrompt: 'string?', + srefSw: 'int?', + crefCw: 'int?', + lora: 'string?', + loraWeight: 'int?', + isShow: 'bool', + children: 'string?' + }, + primaryKey: 'id' + } +} diff --git a/src/define/db/service/Book/bookBackTaskListService.ts b/src/define/db/service/Book/bookBackTaskListService.ts index 76ed356..3c68917 100644 --- a/src/define/db/service/Book/bookBackTaskListService.ts +++ b/src/define/db/service/Book/bookBackTaskListService.ts @@ -140,7 +140,8 @@ export class BookBackTaskListService extends BaseRealmService { taskType: BookBackTaskType, executeType = TaskExecuteType.AUTO, bookTaskId = null, - bookTaskDetailId = null + bookTaskDetailId = null, + responseMessageName: string = null ): GeneralResponse.SuccessItem | GeneralResponse.ErrorItem { try { // 通过bookid获取book信息 @@ -181,6 +182,7 @@ export class BookBackTaskListService extends BaseRealmService { status: BookBackTaskStatus.WAIT, createTime: new Date(), updateTime: new Date(), + messageName: responseMessageName, startTime: 0, endTime: 0 } as TaskModal.Task diff --git a/src/define/db/service/Book/bookBasic.ts b/src/define/db/service/Book/bookBasic.ts index 39adf13..68444b9 100644 --- a/src/define/db/service/Book/bookBasic.ts +++ b/src/define/db/service/Book/bookBasic.ts @@ -162,6 +162,20 @@ const migration = (oldRealm: Realm, newRealm: Realm) => { newBookTask[i].watermarkPosition = '[]' } } + if (oldRealm.schemaVersion < 23) { + const oldBookTask = oldRealm.objects('BookTaskDetail') + const newBookTask = newRealm.objects('BookTaskDetail') + for (let i = 0; i < oldBookTask.length; i++) { + newBookTask[i].subImagePath = '[]' + } + } + if (oldRealm.schemaVersion < 24) { + const oldBookTask = oldRealm.objects('BookBackTaskList') + const newBookTask = newRealm.objects('BookBackTaskList') + for (let i = 0; i < oldBookTask.length; i++) { + newBookTask[i].messageName = '' + } + } } export class BaseRealmService extends BaseService { @@ -203,7 +217,7 @@ export class BaseRealmService extends BaseService { BookTaskDetailModel ], path: this.dbpath, - schemaVersion: 22, + schemaVersion: 24, migration: migration } this.realm = await Realm.open(config) diff --git a/src/define/db/service/Book/bookService.ts b/src/define/db/service/Book/bookService.ts index 34f016c..4c8b240 100644 --- a/src/define/db/service/Book/bookService.ts +++ b/src/define/db/service/Book/bookService.ts @@ -195,6 +195,9 @@ export class BookService extends BaseRealmService { await CheckFolderExistsOrCreate(bookTaskImageFolder) // 创建默认的任务文件夹 // 修改数据 book.oldVideoPath = path.relative(define.project_path, oldVideoPath) + if (book.type == BookType.ORIGINAL) { + book.oldVideoPath = null + } let imageCategory = BookImageCategory.MJ if (book.type == BookType.SD_REVERSE) { diff --git a/src/define/db/service/Book/bookTaskDetailService.ts b/src/define/db/service/Book/bookTaskDetailService.ts index 5c7abe2..59a7f17 100644 --- a/src/define/db/service/Book/bookTaskDetailService.ts +++ b/src/define/db/service/Book/bookTaskDetailService.ts @@ -1,6 +1,6 @@ import Realm from 'realm' import path from 'path' -import { define } from '../../../define.js' +import { define } from '../../../define' import { BookTaskModel } from '../../model/Book/bookTask.js' import { successMessage } from '../../../../main/Public/generalTools' import { BaseRealmService } from './bookBasic' @@ -72,6 +72,7 @@ export class BookTaskDetailService extends BaseRealmService { return JoinPath(define.project_path, subImage) }), characterTags: item.characterTags ? item.characterTags.map((tag) => tag) : null, + sceneTags: item.sceneTags ? item.sceneTags.map((tag) => tag) : null, subValue: isEmpty(item.subValue) ? null : JSON.parse(item.subValue), reversePrompt: item.reversePrompt.map((reversePrompt) => { return { @@ -116,7 +117,7 @@ export class BookTaskDetailService extends BaseRealmService { * 添加一条小说任务对应的详细数据 * @param BookTaskDetail */ - public AddBookTaskDetail(bookTaskDetail) { + public AddBookTaskDetail(bookTaskDetail: Book.SelectBookTaskDetail) { try { // 判断是不是又小说ID if (isEmpty(bookTaskDetail.bookId) || isEmpty(bookTaskDetail.bookTaskId)) { @@ -191,7 +192,6 @@ export class BookTaskDetailService extends BaseRealmService { */ UpdateBookTaskDetailMjMessage(bookTaskDetailId: string, mjMessage: Book.MJMessage): void { try { - console.log('UpdateBookTaskDetailMjMessage', bookTaskDetailId, mjMessage) this.transaction(() => { let mjMessageRes = this.realm.objectForPrimaryKey('MJMessage', bookTaskDetailId) let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId) diff --git a/src/define/db/service/Book/bookTaskService.ts b/src/define/db/service/Book/bookTaskService.ts index 1f93f90..662f136 100644 --- a/src/define/db/service/Book/bookTaskService.ts +++ b/src/define/db/service/Book/bookTaskService.ts @@ -1,6 +1,6 @@ import Realm from 'realm' import path from 'path' -import { define } from '../../../define.js' +import { define } from '../../../define' import { BookTaskModel } from '../../model/Book/bookTask.js' import { BookBackTaskStatus, BookImageCategory, BookTaskStatus } from '../../../enum/bookEnum.js' import { successMessage } from '../../../../main/Public/generalTools' diff --git a/src/define/db/service/SoftWare/imageStyleService.ts b/src/define/db/service/SoftWare/imageStyleService.ts new file mode 100644 index 0000000..61ec44a --- /dev/null +++ b/src/define/db/service/SoftWare/imageStyleService.ts @@ -0,0 +1,26 @@ +import Realm, { UpdateMode } from 'realm' +import path from 'path' +import { errorMessage, successMessage } from '../../../../main/Public/generalTools' +import { BaseSoftWareService } from './softwareBasic.js' +import { isEmpty } from 'lodash' +const { v4: uuidv4 } = require('uuid') + +export class ImageStyleService extends BaseSoftWareService { + static instance: ImageStyleService | null = null + realm: Realm + private constructor() { + super() + } + + /** + * 获取当前实例对象,为空则创建一个新的 + * @returns + */ + public static async getInstance() { + if (ImageStyleService.instance === null) { + ImageStyleService.instance = new ImageStyleService() + await super.getInstance() + } + return ImageStyleService.instance + } +} diff --git a/src/define/db/service/SoftWare/mjSettingService.ts b/src/define/db/service/SoftWare/mjSettingService.ts index c78b879..0bc7155 100644 --- a/src/define/db/service/SoftWare/mjSettingService.ts +++ b/src/define/db/service/SoftWare/mjSettingService.ts @@ -1,7 +1,7 @@ import Realm, { UpdateMode } from 'realm' import path from 'path' import { BaseService } from '../baseService' -import { define } from '../../../define.js' +import { define } from '../../../define' import { SoftwareModel } from '../../model/SoftWare/software' import { ComponentSize, SoftwareThemeType } from '../../../enum/softwareEnum.js' import { errorMessage, successMessage } from '../../../../main/Public/generalTools' diff --git a/src/define/db/service/SoftWare/softwareBasic.ts b/src/define/db/service/SoftWare/softwareBasic.ts index 0ffe2e2..7f6cc52 100644 --- a/src/define/db/service/SoftWare/softwareBasic.ts +++ b/src/define/db/service/SoftWare/softwareBasic.ts @@ -12,6 +12,7 @@ import { RemoteMJModel } from '../../model/SoftWare/mjSetting' import { MJImageType, MJRobotType } from '../../../enum/mjEnum' +import { PresetModel } from '../../model/SoftWare/preset' const { v4: uuidv4 } = require('uuid') let dbPath = path.resolve(define.db_path, 'software.realm') @@ -155,6 +156,9 @@ const migration = (oldRealm: Realm, newRealm: Realm) => { } }) } + if (oldRealm.schemaVersion < 22) { + + } } export class BaseSoftWareService extends BaseService { @@ -190,10 +194,11 @@ export class BaseSoftWareService extends BaseService { BrowserMJModel, RemoteMJModel, APIMjModel, - MjSettingModel + MjSettingModel, + PresetModel ], path: dbPath, - schemaVersion: 21, // 当前版本号 + schemaVersion: 23, // 当前版本号 migration: migration } // 判断当前全局是不是又当前这个 diff --git a/src/define/db/service/SoftWare/softwareService.ts b/src/define/db/service/SoftWare/softwareService.ts index 7e6d004..3cbfb41 100644 --- a/src/define/db/service/SoftWare/softwareService.ts +++ b/src/define/db/service/SoftWare/softwareService.ts @@ -25,15 +25,25 @@ export class SoftwareService extends BaseSoftWareService { } // 修改数据库中行中的某个属性数据 - UpdateSoftware(software: SoftwareSettingModel.SoftwareSetting): GeneralResponse.SuccessItem { - try { + UpdateSoftware(software: SoftwareSettingModel.SoftwareSetting) { + if (software.id) { this.realm.write(() => { this.realm.create('Software', software, UpdateMode.Modified) }) - // 返回成功信息 - return successMessage(null, '修改软件配置信息成功', 'SoftwareService_UpdateSoftware') - } catch (error) { - throw error + } else { + // 没有ID,就修改指定属性 + let softwareData = this.realm.objects('Software') + if (softwareData.length <= 0) { + throw new Error('数据库中没有软件配置信息') + } + let _software = softwareData[0] + this.realm.write(() => { + for (let key in software) { + if (software[key] != undefined) { + _software[key] = software[key] + } + } + }) } } diff --git a/src/define/define_string.ts b/src/define/define_string.ts index fa27741..f4d1cfe 100644 --- a/src/define/define_string.ts +++ b/src/define/define_string.ts @@ -1,4 +1,5 @@ export const DEFINE_STRING = { + SHOW_GLOBAL_MESSAGE: "SHOW_GLOBAL_MESSAGE", SHOW_GLOBAL_MAIN_NOTIFICATION: 'SHOW_GLOBAL_MAIN_NOTIFICATION', OPEN_DEV_TOOLS_PASSWORD: 'OPEN_DEV_TOOLS_PASSWORD', OPEN_DEV_TOOLS: 'OPEN_DEV_TOOLS', @@ -7,7 +8,7 @@ export const DEFINE_STRING = { GET_DEFINE_CONFIG_JSON_BY_PROPERTY: 'GET_DEFINE_CONFIG_JSON_BY_PROPERTY', GET_IMAGE_GENERATE_CATEGORY: 'GET_IMAGE_GENERATE_CATEGORY', SHOW_MAIN_NOTIFICATION: 'SHOW_MAIN_NOTIFICATION', - SHOW_GLOABAL_MESSAGE: 'SHOW_GLOABAL_MESSAGE', + SHOW_MAIN_MESSAGE: "SHOW_MAIN_MESSAGE", CHECK_MACHINE_ID: 'CHECK_MACHINE_ID', GET_CUSTOMIZE_GPT_PROMPT: 'GET_CUSTOMIZE_GPT_PROMPT', GENERATE_GPT_EXAMPLE_OUT: 'GENERATE_GPT_EXAMPLE_OUT', @@ -149,11 +150,19 @@ export const DEFINE_STRING = { TRANSLATE_NOW_RETURN: 'TRANSLATE_NOW_RETURN', GET_TRANSLATE_SETTING: 'GET_TRANSLATE_SETTING', RESET_TRANSLATE_SETTING: 'RESET_TRANSLATE_SETTING', - SAVE_TRANSLATE_SETTING: 'SAVE_TRANSLATE_SETTING' + SAVE_TRANSLATE_SETTING: 'SAVE_TRANSLATE_SETTING', + /** + * GPT 反推提示词翻译返回数据 + */ + GPT_PROMPT_TRANSLATE_RETRUN: "GPT_PROMPT_TRANSLATE_RETRUN" }, SD: { LOAD_SD_SERVICE_DATA: 'LOAD_SD_SERVICE_DATA', TXT2IMG: 'TXT2IMG', + + //#region SD生成图片相关 + + //#endregion }, MJ: { SAVE_WORD_SRT: 'SAVE_WORD_SRT', @@ -206,7 +215,6 @@ export const DEFINE_STRING = { }, BOOK: { MAIN_DATA_RETURN: 'MAIN_DATA_RETURN', // 监听任务返回 - REPLACE_VIDEO_CURRENT_FRAME: 'REPLACE_VIDEO_CURRENT_FRAME', GET_BOOK_TYPE: 'GET_BOOK_TYPE', ADD_OR_MODIFY_BOOK: 'ADD_OR_MODIFY_BOOK', @@ -243,6 +251,41 @@ export const DEFINE_STRING = { REMOVE_GENERATE_IMAGE: 'REMOVE_GENERATE_IMAGE', ADD_NEW_BOOK_TASK: "ADD_NEW_BOOK_TASK", REPLACE_BOOK_DATA: "REPLACE_BOOK_DATA", + SAVE_COPYWRITING: 'SAVE_COPYWRITING', + + //#region 原创推理提示词 + ORIGINAL_GPT_PROMPT: "ORIGINAL_GPT_PROMPT", + ORIGINAL_GPT_PROMPT_RETURN: "ORIGINAL_GPT_PROMPT_RETURN", + //#endregion + + //#region 生图返回相关 + + /** + * MJ生图返回信息 + */ + MJ_IMAGE_GENERATE_RETURN: 'MJ_IMAGE_GENERATE_RETURN', + + /** + * SD生图返回信息 + */ + SD_IMAGE_GENERATE_RETURN: 'SD_IMAGE_GENERATE_RETURN', + + /** + * D3 出图返回信息 + */ + D3_IMAGE_GENERATE_RETURN: 'D3_IMAGE_GENERATE_RETURN', + + /** + * flux forge 生图返回信息 + */ + FLUX_FORGE_IMAGE_GENERATE_RETURN: "FLUX_FORGE_IMAGE_GENERATE_RETURN", + + /** + * flux api 生图返回信息 + */ + FLUX_API_IMAGE_GENERATE_RETURN: "FLUX_API_IMAGE_GENERATE_RETURN", + + //#endregion COMPUTE_STORYBOARD: 'COMPUTE_STORYBOARD', @@ -290,6 +333,19 @@ export const DEFINE_STRING = { GET_PROMPT_SORT_DATA: 'GET_PROMPT_SORT_DATA', OPEN_PROMPT_FILE_TXT: 'OPEN_PROMPT_FILE_TXT' }, + /** + * IPC事件传递,预设相关 + */ + PRESET: { + /** + * 获取任务的预设(只获取 label 和 id) + */ + GET_CHARACTER_PRESET: 'GET_CHARACTER_PRESET', + /** + * 获取场景的预设(只获取 label 和 id) + */ + GET_SCENE_PRESET: "GET_SCENE_PRESET" + }, TTS: { GET_TTS_CONFIG: 'GET_TTS_CONFIG', GENERATE_AUDIO: 'GENERATE_AUDIO', @@ -309,6 +365,21 @@ export const DEFINE_STRING = { DB: { UPDATE_BOOK_TASK_DATA: "UPDATE_BOOK_TASK_DATA", UPDATE_BOOK_TASK_DETAIL_DATA: "UPDATE_BOOK_TASK_DETAIL_DATA", - UPDATE_BOOK_DATA: "UPDATE_BOOK_DATA" + UPDATE_BOOK_DATA: "UPDATE_BOOK_DATA", + UPDATE_SOFTWARE_SETTING: "UPDATE_SOFTWARE_SETTING" + }, + /** + * 后台任务相关 + */ + TASK: { + /** + * 添加单个后台任务 + */ + ADD_BOOK_BACK_TASK: "ADD_BOOK_BACK_TASK", + + /** + * 添加多个后台任务 + */ + ADD_MULTI_BOOK_BACK_TASK: 'ADD_MULTI_BOOK_BACK_TASK' } } diff --git a/src/define/enum/bookEnum.ts b/src/define/enum/bookEnum.ts index c3dba87..ae11971 100644 --- a/src/define/enum/bookEnum.ts +++ b/src/define/enum/bookEnum.ts @@ -1,6 +1,8 @@ export enum BookType { // 原创 ORIGINAL = 'original', + // 区分老版本的原创 + NEW_ORIGINAL = 'new_original', // 反推 SD_REVERSE = 'sd_reverse', // MJ 反推 @@ -14,7 +16,11 @@ export enum BookImageCategory { // SD SD = 'sd', // D3 - D3 = 'd3' + D3 = 'd3', + // FLUX API + FLUX_API = 'flux-api', + // FLUX FORGE + FLUX_FORGE = 'flux-forge' } export enum AddBookTaskCopyData { @@ -67,6 +73,10 @@ export enum BookBackTaskType { MJ_IMAGE = 'mj_image', // SD 生成图片 SD_IMAGE = 'sd_image', + // flux forge 生成图片 + FLUX_FORGE_IMAGE = 'flux_forge_image', + // flux api 生成图片 + FLUX_API_IMAGE = 'flux_api_image', // D3 生成图片 D3_IMAGE = 'd3_image', // 高清 @@ -256,3 +266,13 @@ export enum BookRepalceDataType { // 提示词 PROMPT = 'prompt', } + +/** + * 小说选择标签的方式类型,可以是下拉和标签 + */ +export enum BookTagSelectType { + // 下拉 + DROP = 'drop', + // 标签 + TAG = 'tag' +} diff --git a/src/define/enum/image.ts b/src/define/enum/image.ts index e69de29..0f5484f 100644 --- a/src/define/enum/image.ts +++ b/src/define/enum/image.ts @@ -0,0 +1,26 @@ + +/** + * Flux 调用API时候的 生图模型 + */ +export enum FLxuAPIImageType { + FLUX = "flux", + FLUX_PRO = "flux-pro", + FLUX_DEV = "flux-dev", + FLUX_SCHNELL = "flux-schnell", + FLUX_PRO_MAX = "flux-pro-max" +} + + +export enum PresetType { + // 角色 + CHARACTER = 'character', + + // 本地风格 + LOCAL_STYLE = 'localStyle', + + // 自定义风格 + CUSTOM_STYLE = 'customStyle', + + // laitool预设风格 + LAITOOL_STYLE = 'laitoolStyle', +} \ No newline at end of file diff --git a/src/define/enum/mjEnum.ts b/src/define/enum/mjEnum.ts index 1f08845..d82f312 100644 --- a/src/define/enum/mjEnum.ts +++ b/src/define/enum/mjEnum.ts @@ -9,7 +9,16 @@ export enum MJImageType { BROWSER_MJ = 'browser_mj', // API模式 - API_MJ = 'api_mj' + API_MJ = 'api_mj', + + // 本地 SD + LOCAL_SD = 'local_sd', + + // flux-api + FLUX_API = 'flux-api', + + // flxu-forge + FLUX_FORGE = 'flux-forge' } export enum MJRobotType { diff --git a/src/define/enum/preset.ts b/src/define/enum/preset.ts new file mode 100644 index 0000000..8a96ffc --- /dev/null +++ b/src/define/enum/preset.ts @@ -0,0 +1,16 @@ +/** + * 标签库预设的图片样式类型 + */ +export enum PresetType { + // 角色 + CHARACTER = 'character', + + // 本地风格 + LOCAL_STYLE = 'localStyle', + + // 自定义风格 + CUSTOM_STYLE = 'customStyle', + + // laitool预设风格 + LAITOOL_STYLE = 'laitoolStyle', +} \ No newline at end of file diff --git a/src/define/setting/imageSetting.js b/src/define/setting/imageSetting.js index e239128..dbfe520 100644 --- a/src/define/setting/imageSetting.js +++ b/src/define/setting/imageSetting.js @@ -1,245 +1,275 @@ -import { Tools } from "../../main/tools"; -import { define } from "../define"; -import path from "path"; -import { DEFINE_STRING } from "../define_string"; -import { get, has } from "lodash"; -let tools = new Tools(); -const { v4: uuidv4 } = require('uuid'); // 引入UUID库来生成唯一标识符 +import { Tools } from '../../main/tools' +import { define } from '../define' +import path from 'path' +import { DEFINE_STRING } from '../define_string' +import { get, has } from 'lodash' +let tools = new Tools() +const { v4: uuidv4 } = require('uuid') // 引入UUID库来生成唯一标识符 export const ImageSetting = { - - /** - * 获取自动保存图片的方式 - * @returns - */ - async GetAutoSaveImageClassifyOptions() { - return { - code: 1, - data: [{ - label: "不分类", - value: "one" - }, - // { - // label: "按模型", - // value: "model" - // }, - { - label: "按风格", - value: "style" - }, - { - label: "风格+模型", - value: "theme" - }] + /** + * 获取自动保存图片的方式 + * @returns + */ + async GetAutoSaveImageClassifyOptions() { + return { + code: 1, + data: [ + { + label: '不分类', + value: 'one' + }, + // { + // label: "按模型", + // value: "model" + // }, + { + label: '按风格', + value: 'style' + }, + { + label: '风格+模型', + value: 'theme' } - }, + ] + } + }, - /** - * 获取自动保存图片的方式(sd,mj,d3 等) - */ - async GetImageGenerateCategory() { - return { - code: 1, - data: [{ - label: "SD", - value: "sd" + /** + * 获取自动保存图片的方式(sd,mj,d3 等) + */ + async GetImageGenerateCategory() { + return { + code: 1, + data: [ + { + label: 'SD', + value: 'sd' + }, + { + label: 'MJ', + value: 'mj' + }, + { + label: 'D3', + value: 'd3' + }, + { + label: 'Flux-Forge', + value: 'flux-forge' + }, + { + label: 'Flux-API', + value: 'flux-api' + } + ] + } + }, + + /** + * 保存自动保存图片的设置 + */ + async SaveImageAutoSaveSetting(value) { + try { + value = JSON.parse(value) + await tools.writeJsonFilePropertyValue(define.img_base, 'auto_save_image', value) + return { + code: 1 + } + } catch (error) { + return { + code: 0, + message: error.toString() + } + } + }, + + /** + * 保存自动保存图片的设置 + */ + async GetImageAutoSaveSetting() { + try { + let res = await tools.getJsonFilePropertyValue(define.img_base, 'auto_save_image', {}, false) + return { + code: 1, + data: res + } + } catch (error) { + return { + code: 0, + message: error.toString() + } + } + }, + + /** + * 保存图片到指定文件夹,只是将任务添加到队列中 + */ + async SaveImageToOtherFolder(output = [], value) { + try { + let show = false + if (value) { + value = JSON.parse(value) + show = false + } else { + let auto_save_image = await tools.getJsonFilePropertyValue( + define.img_base, + 'auto_save_image', + {}, + false + ) + value = { + save_match_count: auto_save_image.save_match_count, + save_folder: auto_save_image.main_save_folder + } + if (!auto_save_image.auto_save) { + return { + code: 1 + } + } + } + + let batch = DEFINE_STRING.QUEUE_BATCH.IMAGE_SAVE_TO_OTHER_FOLDER + // 获取当前的所有的output文件夹 + if (output.length == 0) { + output = await tools.getSubFolderList( + path.join(global.config.project_path, 'tmp/bak/'), + 'start', + 'output_crop_' + ) + } + for (let i = 0; i < output.length; i++) { + const element = path.join(global.config.project_path, 'tmp/bak/' + output[i]) + // 获取指定的文件夹里面的文件 + let png_files = await tools.getFilesWithExtensions(element, '.png') + console.log(png_files) + // 判断当前的png_files中的数据是不是大于value.save_match_count,大于的话,删除数组里面的数据 + if (value.save_match_count && png_files.length > value.save_match_count) { + // 删除数组 + png_files.splice(value.save_match_count) + } + + for (let j = 0; j < png_files.length; j++) { + const item = png_files[j] + global.fileQueue.enqueue( + async () => { + // 复制文件到指定的文件夹 + let dst = path.join( + value.save_folder, + `${Date.now().toString()}_${uuidv4().split('-')[0]}.png` + ) + + await tools.copyFileOrDirectory(item, dst) }, - { - label: "MJ", - value: "mj" - }, - { - label: "D3", - value: "d3" - }] + `${batch}_${item}`, + batch + ) } - }, - - /** - * 保存自动保存图片的设置 - */ - async SaveImageAutoSaveSetting(value) { - try { - value = JSON.parse(value); - await tools.writeJsonFilePropertyValue(define.img_base, "auto_save_image", value); - return { - code: 1, - } - } catch (error) { - return { - code: 0, - message: error.toString() - } - } - }, - - /** - * 保存自动保存图片的设置 - */ - async GetImageAutoSaveSetting() { - try { - let res = await tools.getJsonFilePropertyValue(define.img_base, "auto_save_image", {}, false); - return { - code: 1, - data: res - } - } catch (error) { - return { - code: 0, - message: error.toString() - } - } - }, - - /** - * 保存图片到指定文件夹,只是将任务添加到队列中 - */ - async SaveImageToOtherFolder(output = [], value) { - try { - let show = false; - if (value) { - value = JSON.parse(value); - show = false; - } else { - let auto_save_image = await tools.getJsonFilePropertyValue(define.img_base, "auto_save_image", {}, false); - value = { - save_match_count: auto_save_image.save_match_count, - save_folder: auto_save_image.main_save_folder - } - if (!auto_save_image.auto_save) { - return { - code: 1 - } - } - } - - let batch = DEFINE_STRING.QUEUE_BATCH.IMAGE_SAVE_TO_OTHER_FOLDER; - // 获取当前的所有的output文件夹 - if (output.length == 0) { - output = await tools.getSubFolderList(path.join(global.config.project_path, "tmp/bak/"), "start", "output_crop_"); - } - for (let i = 0; i < output.length; i++) { - const element = path.join(global.config.project_path, 'tmp/bak/' + output[i]); - // 获取指定的文件夹里面的文件 - let png_files = await tools.getFilesWithExtensions(element, ".png"); - console.log(png_files); - // 判断当前的png_files中的数据是不是大于value.save_match_count,大于的话,删除数组里面的数据 - if (value.save_match_count && png_files.length > value.save_match_count) { - // 删除数组 - png_files.splice(value.save_match_count); - } - - for (let j = 0; j < png_files.length; j++) { - const item = png_files[j]; - global.fileQueue.enqueue(async () => { - // 复制文件到指定的文件夹 - let dst = path.join(value.save_folder, `${Date.now().toString()}_${uuidv4().split('-')[0]}.png`); - - await tools.copyFileOrDirectory(item, dst); - }, `${batch}_${item}`, batch) - } - } - global.fileQueue.setBatchCompletionCallback(batch, (failedTasks) => { - if (failedTasks.length > 0) { - let message = ` + } + global.fileQueue.setBatchCompletionCallback(batch, (failedTasks) => { + if (failedTasks.length > 0) { + let message = ` 转存任务都已完成。 但是以下任务执行失败: ` - failedTasks.forEach(({ taskId, error }) => { - message += `${taskId}-, \n 错误信息: ${error}` + '\n'; - }); + failedTasks.forEach(({ taskId, error }) => { + message += `${taskId}-, \n 错误信息: ${error}` + '\n' + }) - global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { - code: 0, - message: message - }) - } else { - if (show) { - global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { - code: 1, - message: "所有图片转存完成" - }) - } - } + global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { + code: 0, + message: message + }) + } else { + if (show) { + global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { + code: 1, + message: '所有图片转存完成' }) - - return { - code: 1 - } - - } catch (error) { - return { - code: 0, - message: error.toString() - } + } } - }, + }) - /** - * 获取指定的配置文件里面指定的属性的数据 - * @param {*} value 执行方法必要的信息 - * 0 define中的指定属性(指定的配置文件) - * 1 要获取的什么属性信息 property,property 为null,赶回当前配置文件的所有数据 - * 2 是不是要校验属性不存在 - * 3 属性没有找到的默认值 - * @returns - */ - async GetDefineConfigJsonByProperty(value) { - try { - value = JSON.parse(value); - let defin_property = value[0]; - let property = value[1]; - let need_check = value[2]; - let default_data = value[3]; - // 判断define中的属性是不是存在 - let hasProperty = has(define, defin_property); - if (!hasProperty) { - throw new Error(`${defin_property} 属性不存在。`); - } - let data = await tools.getJsonFilePropertyValue(define[defin_property], property, default_data, need_check); - return { - code: 1, - data: data - } - } catch (error) { - return { - code: 0, - message: "获取配置信息失败,错误信息入下 : " + error.message.toString() - } - } - }, - - /** - * 保存指定的配置文件里面指定的属性的数据 - * @param {*} value - * 0 define中的指定属性(指定的配置文件) - * 1 要获取的什么属性信息 property,property 为null,赶回当前配置文件的所有数据 - * 2 要写入的值 - * 3 是不是要校验属性不存在 - */ - async SaveDefineConfigJsonByProperty(value) { - try { - value = JSON.parse(value); - let defin_property = value[0]; - let property = value[1]; - let data = value[2]; - let need_check = value[3] ? value[3] : false; - // 判断define中的属性是不是存在 - let hasProperty = has(define, defin_property); - if (!hasProperty) { - throw new Error(`${defin_property} 属性不存在。`); - } - await tools.writeJsonFilePropertyValue(define[defin_property], property, data, need_check); - return { - code: 1, - message: "保存指定的配置成功" - } - } catch (error) { - return { - code: 0, - message: "保存配置信息失败,错误信息入下 : " + error.message.toString() - } - } + return { + code: 1 + } + } catch (error) { + return { + code: 0, + message: error.toString() + } } + }, -} \ No newline at end of file + /** + * 获取指定的配置文件里面指定的属性的数据 + * @param {*} value 执行方法必要的信息 + * 0 define中的指定属性(指定的配置文件) + * 1 要获取的什么属性信息 property,property 为null,赶回当前配置文件的所有数据 + * 2 是不是要校验属性不存在 + * 3 属性没有找到的默认值 + * @returns + */ + async GetDefineConfigJsonByProperty(value) { + try { + value = JSON.parse(value) + let defin_property = value[0] + let property = value[1] + let need_check = value[2] + let default_data = value[3] + // 判断define中的属性是不是存在 + let hasProperty = has(define, defin_property) + if (!hasProperty) { + throw new Error(`${defin_property} 属性不存在。`) + } + let data = await tools.getJsonFilePropertyValue( + define[defin_property], + property, + default_data, + need_check + ) + return { + code: 1, + data: data + } + } catch (error) { + return { + code: 0, + message: '获取配置信息失败,错误信息入下 : ' + error.message.toString() + } + } + }, + + /** + * 保存指定的配置文件里面指定的属性的数据 + * @param {*} value + * 0 define中的指定属性(指定的配置文件) + * 1 要获取的什么属性信息 property,property 为null,赶回当前配置文件的所有数据 + * 2 要写入的值 + * 3 是不是要校验属性不存在 + */ + async SaveDefineConfigJsonByProperty(value) { + try { + value = JSON.parse(value) + let defin_property = value[0] + let property = value[1] + let data = value[2] + let need_check = value[3] ? value[3] : false + // 判断define中的属性是不是存在 + let hasProperty = has(define, defin_property) + if (!hasProperty) { + throw new Error(`${defin_property} 属性不存在。`) + } + await tools.writeJsonFilePropertyValue(define[defin_property], property, data, need_check) + return { + code: 1, + message: '保存指定的配置成功' + } + } catch (error) { + return { + code: 0, + message: '保存配置信息失败,错误信息入下 : ' + error.message.toString() + } + } + } +} diff --git a/src/main/IPCEvent/bookIpc.ts b/src/main/IPCEvent/bookIpc.ts index 9f947be..5b01401 100644 --- a/src/main/IPCEvent/bookIpc.ts +++ b/src/main/IPCEvent/bookIpc.ts @@ -14,6 +14,7 @@ import { SubtitleService } from '../Service/Subtitle/subtitleService' import { BookFrame } from '../Service/Book/bookFrame' import { BookPrompt } from '../Service/Book/bookPrompt' import { BookGeneral } from '../Service/Book/bookGeneral' +import { OperateBookType } from '../../define/enum/bookEnum' let reverseBook = new ReverseBook() let basicReverse = new BasicReverse() let subtitle = new Subtitle() @@ -141,16 +142,30 @@ export function BookIpc() { async (event, id, operateBookType) => await bookPrompt.RemoveMergePromptData(id, operateBookType) ) + // 原创推理提示词 + ipcMain.handle( + DEFINE_STRING.BOOK.ORIGINAL_GPT_PROMPT, + async (event, id: string, operateBookType: OperateBookType, coverData: boolean) => + await bookPrompt.OriginalGetPrompt(id, operateBookType, coverData) + ) + //#endregion //#region 文案相关 - // 开始执行获取小说文案的方法 + // 开始执行反推获取小说文案的方法 ipcMain.handle( DEFINE_STRING.BOOK.GET_COPYWRITING, async (event, bookId, bookTaskId, operateBookType, coverData) => await subtitleService.GetCopywriting(bookId, bookTaskId, operateBookType, coverData) ) + // 导入文案和字幕对齐数据的方法,保存到数据库中 + ipcMain.handle( + DEFINE_STRING.BOOK.SAVE_COPYWRITING, + async (event, bookTaskId, copywritingData, operateBookType) => await subtitleService.SaveCopywriting(bookTaskId, copywritingData, operateBookType) + ) + + // 获取小说的文案数据,然后保存到对应的文件中 ipcMain.handle( DEFINE_STRING.BOOK.EXPORT_COPYWRITING, diff --git a/src/main/IPCEvent/dbIpc.ts b/src/main/IPCEvent/dbIpc.ts index 34af968..a93099c 100644 --- a/src/main/IPCEvent/dbIpc.ts +++ b/src/main/IPCEvent/dbIpc.ts @@ -5,12 +5,14 @@ import { Book } from '../../model/book' import { BookTaskService } from '../../define/db/service/Book/bookTaskService' import { BookTaskDetailService } from '../../define/db/service/Book/bookTaskDetailService' import { BookService } from '../../define/db/service/Book/bookService' +import { SoftWareServiceBasic } from '../Service/ServiceBasic/softwareServiceBasic' async function DBIpc() { let bookTaskService = await BookTaskService.getInstance() let bookTaskDetailService = await BookTaskDetailService.getInstance() let bookService = await BookService.getInstance() + let softWareServiceBasic = new SoftWareServiceBasic() //#region 小说相关的修改 // 修改小说任务的数据 @@ -47,5 +49,18 @@ async function DBIpc() { //#endregion + + //#region 软件设置的修改 + + // 修改软件设置 + ipcMain.handle(DEFINE_STRING.DB.UPDATE_SOFTWARE_SETTING, async (event, software: SoftwareSettingModel.SoftwareSetting) => { + try { + await softWareServiceBasic.UpdateSoftware(software) + return successMessage(null, "修改软件配置信息成功", "DBIpc_UpdateSoftwareSetting") + } catch (error) { + return errorMessage("修改软件配置信息失败", "DBIpc_UpdateSoftwareSetting") + } + }) + //#endregion } export { DBIpc } diff --git a/src/main/IPCEvent/globalIpc.js b/src/main/IPCEvent/globalIpc.js index 27ef707..5efc275 100644 --- a/src/main/IPCEvent/globalIpc.js +++ b/src/main/IPCEvent/globalIpc.js @@ -46,6 +46,11 @@ function GlobalIpc() { ipcMain.on(DEFINE_STRING.SHOW_GLOBAL_MAIN_NOTIFICATION, (event, value) => { global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MAIN_NOTIFICATION, value) }) + + // 监听打开message事件 + ipcMain.on(DEFINE_STRING.SHOW_GLOBAL_MESSAGE, (event, value) => { + global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MAIN_MESSAGE, value) + }) } export { GlobalIpc } diff --git a/src/main/IPCEvent/index.js b/src/main/IPCEvent/index.js index 12c5c8d..68861f0 100644 --- a/src/main/IPCEvent/index.js +++ b/src/main/IPCEvent/index.js @@ -15,6 +15,8 @@ import { SystemIpc } from './systemIpc.js' import { BookIpc } from './bookIpc' import { TTSIpc } from './ttsIpc.js' import { DBIpc } from './dbIpc' +import { PresetIpc } from './presetIpc' +import { TaskIpc } from './taskIpc' export async function RegisterIpc(createWindow) { PromptIpc() @@ -26,6 +28,8 @@ export async function RegisterIpc(createWindow) { GptIpc() SdIpc() await DBIpc() + PresetIpc() + TaskIpc() MjIpc() MainIpc(createWindow) OriginalImageGenerateIpc() diff --git a/src/main/IPCEvent/mjIpc.js b/src/main/IPCEvent/mjIpc.js index 9234023..87d1c49 100644 --- a/src/main/IPCEvent/mjIpc.js +++ b/src/main/IPCEvent/mjIpc.js @@ -141,7 +141,8 @@ function MjIpc() { // MJ出单张图 ipcMain.handle( DEFINE_STRING.MJ.ADD_MJ_GENADD_MJ_GENERATE_IMAGE_TASK, - async (event, id, operateBookType) => await mjOpt.AddMJGenerateImageTask(id, operateBookType) + async (event, id, operateBookType, responseMessageName, coverData) => + await mjOpt.AddMJGenerateImageTask(id, operateBookType, responseMessageName, coverData) ) } diff --git a/src/main/IPCEvent/presetIpc.ts b/src/main/IPCEvent/presetIpc.ts new file mode 100644 index 0000000..9596350 --- /dev/null +++ b/src/main/IPCEvent/presetIpc.ts @@ -0,0 +1,19 @@ +import { ipcMain } from 'electron' +import { DEFINE_STRING } from '../../define/define_string' +import { PresetService } from '../Service/presetService' +let presetService = new PresetService() +function PresetIpc() { + + // 获取人物的预设(只获取 lable 和 id) + ipcMain.handle( + DEFINE_STRING.PRESET.GET_CHARACTER_PRESET, + async (event) => await presetService.GetCharacterPreset() + ) + + // 获取场景的预设(只获取 lable 和 id) + ipcMain.handle( + DEFINE_STRING.PRESET.GET_SCENE_PRESET, + async (event) => await presetService.GetScenePreset() + ) +} +export { PresetIpc } diff --git a/src/main/IPCEvent/settingIpc.js b/src/main/IPCEvent/settingIpc.js index 0e74895..bd5fb2f 100644 --- a/src/main/IPCEvent/settingIpc.js +++ b/src/main/IPCEvent/settingIpc.js @@ -151,14 +151,14 @@ async function SettingIpc() { //#endregion - //#region 基础设置 + //#region 基础设置(数据库) // 获取软件的基础设置(初始的时候执行一次) ipcMain.handle( DEFINE_STRING.SETTING.GET_SOFTWARE_SETTING, async (event) => await basicSetting.GetSoftwareSetting() ) - // 保存软件的基础设置 + // 保存软件的通用设置 ipcMain.handle( DEFINE_STRING.SETTING.SAVE_SOFT_WARE_SETTING, async (event, value) => await basicSetting.SaveSoftWareSetting(value) diff --git a/src/main/IPCEvent/taskIpc.ts b/src/main/IPCEvent/taskIpc.ts new file mode 100644 index 0000000..7aee10c --- /dev/null +++ b/src/main/IPCEvent/taskIpc.ts @@ -0,0 +1,39 @@ +import { ipcMain } from "electron" +import { BookServiceBasic } from "../Service/ServiceBasic/bookServiceBasic" +import { DEFINE_STRING } from "../../define/define_string"; +import { errorMessage, successMessage } from "../Public/generalTools"; +import { BookBackTaskType, TaskExecuteType } from "../../define/enum/bookEnum"; + +let bookServiceBasic = new BookServiceBasic(); + +function TaskIpc() { + + /** + * 添加后台任务,单个 + */ + ipcMain.handle(DEFINE_STRING.TASK.ADD_BOOK_BACK_TASK, async (event, bookId: string, taskType: BookBackTaskType, executeType: TaskExecuteType, bookTaskId: string, bookTaskDetailId: string, responseMessageName: string) => { + try { + let res = await bookServiceBasic.AddBookBackTask(bookId, taskType, executeType, bookTaskId, bookTaskDetailId, responseMessageName) + return successMessage(res, `添加 ${taskType} 任务成功`, 'TaskIpc_AddBookBackTask'); + } catch (error) { + return errorMessage(`添加 ${taskType} 任务失败,错误信息如下:${error.toString()} `, 'TaskIpc_AddBookBackTask') + } + }) + + /** + * 添加多个后台任务 + */ + ipcMain.handle(DEFINE_STRING.TASK.ADD_MULTI_BOOK_BACK_TASK, async (event, tasks: TaskModal.Task[]) => { + try { + for (let i = 0; i < tasks.length; i++) { + const element = tasks[i]; + let res = await bookServiceBasic.AddBookBackTask(element.bookId, element.type, element.executeType, element.bookTaskId, element.bookTaskDetailId, element.messageName) + } + return successMessage(null, `添加多个任务成功`, 'TaskIpc_AddMultiBookBackTask'); + } catch (error) { + return errorMessage(`添加多个任务失败,错误信息如下:${error.toString()} `, 'TaskIpc_AddMultiBookBackTask') + } + }) +} + +export { TaskIpc } \ No newline at end of file diff --git a/src/main/Original/MJOriginalImageGenerate.js b/src/main/Original/MJOriginalImageGenerate.js index f9c8a56..061ec3b 100644 --- a/src/main/Original/MJOriginalImageGenerate.js +++ b/src/main/Original/MJOriginalImageGenerate.js @@ -92,10 +92,9 @@ export class MJOriginalImageGenerate { LOGGER_DEFINE.ORIGINAL_AUTO_MATCH_USER ) } - + let result = [] for (let i = 0; i < value.length; i++) { const element = value[i] - let res_data = { code: 1, id: element.id, // 当前 data 的ID @@ -104,13 +103,12 @@ export class MJOriginalImageGenerate { // 获取当前的字幕数据 let temp_sub = [] - for (let j = 0; element.suValue && j < element.suValue.length; j++) { - const element = array[j] - temp_sub.push(element.srt_value) + for (let j = 0; element.subValue && j < element.subValue.length; j++) { + temp_sub.push(element.subValue[j].srt_value) } let word = '' if (temp_sub.length == 0) { - word = element.after_gpt + word = element.after_gpt ? element.after_gpt : element.afterGpt } else { word = temp_sub.join(',') } @@ -144,11 +142,11 @@ export class MJOriginalImageGenerate { res_data.match_character.push(temp_item_data) } } - + result.push(res_data) // 开始往前端传递数据 this.sendChangeMessage(res_data, DEFINE_STRING.MJ.MACTH_USER_RETURN) } - return successMessage(null, '人物标签自动匹配完成', LOGGER_DEFINE.ORIGINAL_AUTO_MATCH_USER) + return successMessage(result, '人物标签自动匹配完成', LOGGER_DEFINE.ORIGINAL_AUTO_MATCH_USER) } catch (error) { return errorMessage( '通过文本自动匹配数据错误,错误信息如下:' + error.message, diff --git a/src/main/Public/generalTools.ts b/src/main/Public/generalTools.ts index a7b25aa..6162f57 100644 --- a/src/main/Public/generalTools.ts +++ b/src/main/Public/generalTools.ts @@ -1,3 +1,4 @@ +import { DEFINE_STRING } from '../../define/define_string' import { GeneralResponse } from '../../model/generalResponse' /** @@ -104,11 +105,21 @@ function errorMessage(message: string, service?: string): GeneralResponse.ErrorI } } +/** + * 主动发送消息到前端,用作数据返回 + * @param data 要返回的数据 + * @param message_name 消息名称 + */ +function SendReturnMessage(data: GeneralResponse.MessageResponse, message_name: string = DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN) { + global.newWindow[0].win.webContents.send(message_name, data) +} + export { checkStringValueAddSuffix, checkStringValueAddPrefix, checkStringValueDeletePrefix, checkStringValueDeleteSuffix, successMessage, - errorMessage + errorMessage, + SendReturnMessage } diff --git a/src/main/Service/Book/BooKBasic.ts b/src/main/Service/Book/BooKBasic.ts index d6034c6..89643bd 100644 --- a/src/main/Service/Book/BooKBasic.ts +++ b/src/main/Service/Book/BooKBasic.ts @@ -138,10 +138,13 @@ export class BookBasic { watermarkPosition: undefined, }) // 文件重置,获取data下面的所有的子文件夹,删除所有的文件夹 - let dirs = await GetSubdirectories(path.join(book.bookFolderPath, 'data')) - for (let i = 0; i < dirs.length; i++) { - const element = dirs[i]; - await DeleteFolderAllFile(element, true) + let bookData = path.join(book.bookFolderPath, 'data'); + if (await CheckFileOrDirExist(bookData)) { + let dirs = await GetSubdirectories(bookData) + for (let i = 0; i < dirs.length; i++) { + const element = dirs[i]; + await DeleteFolderAllFile(element, true) + } } let scriptPath = path.join(book.bookFolderPath, 'script') if (await CheckFileOrDirExist(scriptPath)) { diff --git a/src/main/Service/Book/ReverseBook.ts b/src/main/Service/Book/ReverseBook.ts index a93ce8b..f9b3324 100644 --- a/src/main/Service/Book/ReverseBook.ts +++ b/src/main/Service/Book/ReverseBook.ts @@ -2,7 +2,7 @@ import { successMessage, errorMessage } from '../../Public/generalTools' import { BookBasic } from './BooKBasic' import { BookService } from '../../../define/db/service/Book/bookService' import { BookTaskService } from '../../../define/db/service/Book/bookTaskService' -import { define } from '../../../define/define.js' +import { define } from '../../../define/define' import fs from 'fs' import { DEFINE_STRING } from "../../../define/define_string"; import path from 'path' @@ -299,6 +299,7 @@ export class ReverseBook { // TODO SD反推,待重写 async SDReversePrompt(task: TaskModal.Task): Promise { + console.log("SDReversePrompt", '') return successMessage(null, "", "ReverseBook_SDReversePrompt") } diff --git a/src/main/Service/Book/bookImage.ts b/src/main/Service/Book/bookImage.ts index b249345..0ba205a 100644 --- a/src/main/Service/Book/bookImage.ts +++ b/src/main/Service/Book/bookImage.ts @@ -12,6 +12,7 @@ import { isEmpty } from "lodash"; import { DEFINE_STRING } from "../../../define/define_string"; import { ResponseMessageType } from "../../../define/enum/softwareEnum"; import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; +import fs from 'fs' import util from 'util' import { exec } from 'child_process' const execAsync = util.promisify(exec); @@ -44,7 +45,7 @@ export class BookImage { * @param id * @param operateBookType */ - async ResetGenerateImage(id: string, operateBookType: OperateBookType): Promise { + async ResetGenerateImage(id: string, operateBookType: OperateBookType, coverData: boolean): Promise { try { let bookTaskDetails = undefined as Book.SelectBookTaskDetail[] let bookTask = undefined as Book.SelectBookTask @@ -56,11 +57,15 @@ export class BookImage { } else { throw new Error('不支持的操作类型,请检查') } + //这边过滤被锁定的数据 + if (!coverData) { + bookTaskDetails = bookTaskDetails.filter(item => !item.imageLock) + } + if (bookTaskDetails.length <= 0) { throw new Error('没有要删除的分镜数据,请检查') } - // 开始删除数据,要删除图片数据和出图的信息 for (let i = 0; i < bookTaskDetails.length; i++) { const element = bookTaskDetails[i]; @@ -69,12 +74,18 @@ export class BookImage { } else if (bookTask.imageCategory == BookImageCategory.D3) { throw new Error('暂时不支持D3生成的图片删除') } else if (bookTask.imageCategory == BookImageCategory.SD) { - throw new Error('暂时不支持SD生成的图片删除') + await this.bookServiceBasic.DeleteBoookTaskDetailGenerateImage(element.id); } else { throw new Error('未知的小说类型,请检查') } + // 上面的信息重置完毕之后,开始删除文件信息 + let outImage = element.outImagePath; + if (await CheckFileOrDirExist(outImage)) { + await fs.promises.unlink(outImage) + } } - return successMessage(bookTask.imageCategory, "删除所有的生图数据成功", "BookImage_ResetGenerateImage") + + return successMessage(bookTaskDetails, "删除所有的生图数据成功", "BookImage_ResetGenerateImage") } catch (error) { return errorMessage("删除所有的图片数据失败,失败信息如下:" + error.toString(), "BookImage_ResetGenerateImage") } diff --git a/src/main/Service/Book/bookPrompt.ts b/src/main/Service/Book/bookPrompt.ts index 86e3bf8..8ac9db7 100644 --- a/src/main/Service/Book/bookPrompt.ts +++ b/src/main/Service/Book/bookPrompt.ts @@ -2,15 +2,21 @@ import { isEmpty } from "lodash"; import { BookType, OperateBookType } from "../../../define/enum/bookEnum"; import { Book } from "../../../model/book"; import { GeneralResponse } from "../../../model/generalResponse"; -import { errorMessage, successMessage } from "../../Public/generalTools"; +import { errorMessage, SendReturnMessage, successMessage } from "../../Public/generalTools"; import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; +import { GptService } from "../GPT/gpt"; +import { ExecuteConcurrently } from "../../../define/Tools/common"; +import { DEFINE_STRING } from "../../../define/define_string"; export class BookPrompt { bookServiceBasic: BookServiceBasic + gptService: GptService constructor() { this.bookServiceBasic = new BookServiceBasic() + this.gptService = new GptService() } + //#region 反推的提示词相关 /** * 将MJ反推出来的数据,选择指定的写入到GPT提示词中 * @param bookId 小说的ID @@ -39,7 +45,7 @@ export class BookPrompt { emptyName.push(element.name) continue; } - let reversePrompt = reversePrompts[index - 1] + let reversePrompt = reversePrompts[index - 1] let gptPrompt = undefined if (!isEmpty(reversePrompt.promptCN) && reversePrompt.promptCN != "") { gptPrompt = reversePrompt.promptCN @@ -102,7 +108,16 @@ export class BookPrompt { await this.bookServiceBasic.DeleteBookTaskDetailReversePromptById(element.id); } } else if (type == BookType.ORIGINAL) { - throw new Error("原创小说删除还不支持") + // 原创删除所有的反推提示词数据 + this.bookServiceBasic.transaction(async (realm) => { + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i]; + let btd = realm.objectForPrimaryKey('BookTaskDetail', element.id) + if (btd) { + btd.gptPrompt = undefined + } + } + }) } else if (type == BookType.SD_REVERSE) { } else { throw new Error("SD反推删除还不支持") @@ -149,5 +164,98 @@ export class BookPrompt { } } + //#endregion + //#region 原创的提示词相关 + + /** + * 原创推理提示词 + * @param id 要推理的ID + * @param operateBookType 操作的类型 + * @param coverData 是不是要覆盖数据(用于推理空白提示词) + */ + async OriginalGetPrompt(id: string, operateBookType: OperateBookType, coverData: boolean): Promise { + try { + let bookTask = undefined as Book.SelectBookTask + let bookTaskDetails = undefined as Book.SelectBookTaskDetail[] + let allBookTaskDetails = undefined as Book.SelectBookTaskDetail[] + + if (operateBookType == OperateBookType.BOOKTASK) { + bookTask = await this.bookServiceBasic.GetBookTaskDataById(id) + allBookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({ + bookTaskId: id + }) + if (!coverData) { // 不覆盖数据,只推理空白提示词 + bookTaskDetails = allBookTaskDetails.filter(item => isEmpty(item.gptPrompt)) + } else { // 不覆盖,就是全部 + bookTaskDetails = allBookTaskDetails; + } + } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { + let singleBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id) + bookTask = await this.bookServiceBasic.GetBookTaskDataById(singleBookTaskDetail.bookTaskId) + bookTaskDetails = [singleBookTaskDetail] + } else if (operateBookType == OperateBookType.UNDERBOOKTASK) { + let singleBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id) + bookTask = await this.bookServiceBasic.GetBookTaskDataById(singleBookTaskDetail.bookTaskId) + // 开始计算上下文数据 + allBookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({ + bookTaskId: bookTask.id + }) + for (let i = 0; i < allBookTaskDetails.length; i++) { + const element = allBookTaskDetails[i]; + if (!bookTaskDetails) { + bookTaskDetails = [] + } + if (i + 1 >= singleBookTaskDetail.no) { + bookTaskDetails.push(element) + } + } + } else { + throw new Error('未知的操作类型'); + } + if (!allBookTaskDetails) { + allBookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({ + bookTaskId: bookTask.id + }) + } + + if (bookTaskDetails.length <= 0) { + throw new Error('没有找到要推理的分镜数据') + } + + let tasks = [] as Array<() => Promise> + // 添加异步任务 + for (let i = 0; i < bookTaskDetails.length; i++) { + const element = bookTaskDetails[i]; + tasks.push(async () => { + let content = await this.gptService.OriginalInferencePrompt(element, allBookTaskDetails, global.config.gpt_count, bookTask.autoAnalyzeCharacter) + // 修改推理出来的数据 + await this.bookServiceBasic.UpdateBookTaskDetail(element.id, { + gptPrompt: content + }) + // 每次完成,都要向前端返回信息 + SendReturnMessage({ + code: 1, + id: element.id, + data: { + content: content, + progress: { + current: i, + total: bookTaskDetails.length + } as GeneralResponse.ProgressResponse + } + }, DEFINE_STRING.BOOK.ORIGINAL_GPT_PROMPT_RETURN); + }) + } + // 分批次执行异步任务 + let res = await ExecuteConcurrently(tasks, global.config.task_number) + // 执行完毕 + return successMessage(null, "推理所有数据完成", 'BookPrompt_OriginalGetPrompt') + + } catch (error) { + return errorMessage("原创推理提示词失败,错误信息: " + error.toString(), 'BookPrompt_OriginalGetPrompt') + } + } + + //#endregion } \ No newline at end of file diff --git a/src/main/Service/Flux/flux.ts b/src/main/Service/Flux/flux.ts new file mode 100644 index 0000000..795aa67 --- /dev/null +++ b/src/main/Service/Flux/flux.ts @@ -0,0 +1,341 @@ +import { define } from '../../../define/define' +import fs from "fs"; +import { ImageStyle } from "../Book/imageStyle"; +import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; +import { isEmpty } from 'lodash'; +import axios from 'axios'; +const fspromise = fs.promises +import path from 'path' +import { CheckFolderExistsOrCreate, CopyFileOrFolder, DeleteFileExifData } from '../../../define/Tools/file'; +import { Base64ToFile, GetImageBase64 } from '../../../define/Tools/image'; +import { BookBackTaskStatus } from '../../../define/enum/bookEnum'; +import { MJAction, MJImageType } from '../../../define/enum/mjEnum'; +import { GptService } from '../GPT/gpt'; + +export class FluxOpt { + gptService: GptService + bookServiceBasic: BookServiceBasic + constructor() { + this.bookServiceBasic = new BookServiceBasic() + this.gptService = new GptService() + } + + + // TODO 这边的设置应该改为数据库 + /** + * 获取SD的设置,之后要删掉,改为数据库 + */ + private async GetSDSetting() { + let sdSetting = JSON.parse(await fspromise.readFile(define.sd_setting, 'utf-8')) + return sdSetting + } + + + //#region flux forge 生图 + /** + * 使用flux forge生图 + * @param task + */ + async FluxForgeImage(task: TaskModal.Task): Promise { + let sdSetting = undefined + try { + // 开始生图 + sdSetting = await this.GetSDSetting() + + let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId); + let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskDetail.bookTaskId); + let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId); + + let prompt = bookTaskDetail.prompt; + let url = sdSetting.setting.webui_api_url + if (url.endsWith('/')) { + url = url + "sdapi/v1/txt2img" + } else { + url = url + "/sdapi/v1/txt2img" + } + if (!isEmpty(sdSetting.webui.prompt)) { + prompt = sdSetting.webui.prompt + ', ' + prompt + } + + // 判断当前是不是有开修脸修手 + let ADetailer = { + args: sdSetting.adetailer + } + + // 种子默认 -1,随机 + let seed = -1; + let body = { + scheduler: 'Simple', + prompt: prompt, + seed: seed, + sampler_name: sdSetting.webui.sampler_name, + // 提示词相关性 + cfg_scale: sdSetting.webui.cfg_scale, + distilled_cfg_scale: 3.5, + width: sdSetting.webui.width, + height: sdSetting.webui.height, + batch_size: sdSetting.setting.batch_size, + steps: sdSetting.webui.steps, + save_images: false, + tiling: false, + override_settings_restore_afterwards: true + } + + if (bookTaskDetail.adetailer) { + body['alwayson_scripts'] = { + "ADetailer": ADetailer + } + } + + const response = await axios.post(url, body) + // TODO 对SD图片种子的一些处理,待定 + // let info = JSON.parse(res.data.info) + // if (seed == -1) { + // seed = info.seed + // } + let images = response.data.images + 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); + + let subImagePath = [] + let outImagePath = '' + // 开始写出图片 + for (let i = 0; i < images.length; i++) { + const element = images[i]; + // 包含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 Base64ToFile(element, infoImgPath) + // 这边去图片信息 + await DeleteFileExifData(path.join(define.package_path, 'exittool/exiftool.exe'), infoImgPath, imgPath); + // 写出去 + if (bookTask.name == 'output_00001') { + // 复制一个到input + let inputImgPath = path.join(inputFolder, `${bookTaskDetail.name}.png`) + await CopyFileOrFolder(imgPath, inputImgPath) + } + if (i == 0) { + // 复制到对应的文件夹里面 + let outPath = path.join(outputFolder, `${bookTaskDetail.name}.png`) + await CopyFileOrFolder(imgPath, outPath) + outImagePath = outPath + } + subImagePath.push(imgPath) + } + // 修改数据库 + await this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetail.id, { + outImagePath: path.relative(define.project_path, outImagePath), + subImagePath: subImagePath.map((item) => path.relative(define.project_path, item)) + }) + await this.bookServiceBasic.UpdateTaskStatus({ + id: task.id, + status: BookBackTaskStatus.DONE + }); + let resp = { + mjApiUrl: url, + progress: 100, + category: MJImageType.FLUX_FORGE, + imageClick: subImagePath.join(','), + imageShow: subImagePath.join(','), + messageId: "", + action: MJAction.IMAGINE, + status: "success", + subImagePath: subImagePath, + outImagePath: outImagePath, + message: "FLUX FORGE 生成图片成功" + } + await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, resp) + global.newWindow[0].win.webContents.send(task.messageName, { + code: 1, + message: "FLUX FORGE 生成图片成功", + data: { + ...resp, + id: bookTaskDetail.id + } + }) + } catch (error) { + let errorMsg = "FLUX FORGE 生成图片失败,错误信息如下:" + error.toString() + await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { + mjApiUrl: sdSetting ? sdSetting.setting.webui_api_url : "", + progress: 0, + category: MJImageType.FLUX_FORGE, + 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 + + + //#region flux api + + /** + * 发送 FLUX API 请求 + * @param url 请求的地址 + * @param key 请求的key + * @param body 请求的请求体 + * @returns + */ + async FluxAPIImageRequest(url: string, key: string, body: { model: string; prompt: string; size: string; }): Promise { + let response = await axios.post(url, { ...body, n: 1 }, { + headers: { + Authorization: 'Bearer ' + key + } + }) + + if (response.data && response.data.data && response.data.data.length > 0) { + return response.data.data[0].url + } else { + return undefined + } + } + + /** + * 调用 FLUX API 生成图片 + * @param task + */ + async FluxAPIImage(task: TaskModal.Task): Promise { + let sdSetting = undefined + try { + let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId); + let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskDetail.bookTaskId); + let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId); + sdSetting = await this.GetSDSetting() + await this.gptService.RefreshGptSetting(); + + let prompt = bookTaskDetail.prompt; + let requestUrl = this.gptService.gptUrl + let uil = new URL(requestUrl); + let url = `${uil.protocol}//${uil.hostname}` + "/v1/images/generations" + + if (!isEmpty(sdSetting.webui.prompt)) { + prompt = sdSetting.webui.prompt + ', ' + prompt + } + let size = `${sdSetting.webui.width}x${sdSetting.webui.height}` + let model = sdSetting.flux.model + // 一次请求生成一张 多个请求 + + 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); + + let outImagePath = '' + let subImagePath = [] + + let batchSize = sdSetting.setting.batch_size; + for (let i = 0; i < batchSize; i++) { + const element = batchSize; + let imageUrl = await this.FluxAPIImageRequest(url, this.gptService.gptApiKey, { + model: model, + prompt: prompt, + size: size + }) + // 这边开始处理返回的数据 + if (isEmpty(imageUrl)) { + throw new Error('FLUX 生图返回的图片地址为空') + } + // 下载指定的文件 + let base64 = await GetImageBase64(imageUrl) + // 将base64 写出 + let imgPath = path.join(SdOriginalImage, `${bookTaskDetail.name}_${new Date().getTime()}_${i}.png`) + await Base64ToFile(base64, imgPath); + + // 写出去 + if (bookTask.name == 'output_00001') { + // 复制一个到input + let inputImgPath = path.join(inputFolder, `${bookTaskDetail.name}.png`) + await CopyFileOrFolder(imgPath, inputImgPath) + } + if (i == 0) { + // 复制到对应的文件夹里面 + let outPath = path.join(outputFolder, `${bookTaskDetail.name}.png`) + await CopyFileOrFolder(imgPath, outPath) + outImagePath = outPath + } + subImagePath.push(imgPath) + } + + // 结束 开始返回 + // 修改数据库 + await this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetail.id, { + outImagePath: path.relative(define.project_path, outImagePath), + subImagePath: subImagePath.map((item) => path.relative(define.project_path, item)) + }) + await this.bookServiceBasic.UpdateTaskStatus({ + id: task.id, + status: BookBackTaskStatus.DONE + }); + let resp = { + mjApiUrl: url, + progress: 100, + category: MJImageType.FLUX_API, + imageClick: subImagePath.join(','), + imageShow: subImagePath.join(','), + messageId: "", + action: MJAction.IMAGINE, + status: "success", + subImagePath: subImagePath, + outImagePath: outImagePath, + message: "FLUX API 生成图片成功" + } + await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, resp) + global.newWindow[0].win.webContents.send(task.messageName, { + code: 1, + message: "FLUX API 生成图片成功", + data: { + ...resp, + id: bookTaskDetail.id + } + }) + + } catch (error) { + let errorMsg = "FLUX API 生成图片失败,错误信息如下:" + error.toString() + await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { + mjApiUrl: "", + progress: 0, + category: MJImageType.FLUX_API, + 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 + } + } +} \ No newline at end of file diff --git a/src/main/Service/Flux/fluxService.ts b/src/main/Service/Flux/fluxService.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/main/Service/GPT/gpt.ts b/src/main/Service/GPT/gpt.ts index 6aa1945..93cb623 100644 --- a/src/main/Service/GPT/gpt.ts +++ b/src/main/Service/GPT/gpt.ts @@ -2,6 +2,7 @@ import { isEmpty } from "lodash"; import { gptDefine } from "../../../define/gptDefine"; import axios from "axios"; import { RetryWithBackoff } from "../../../define/Tools/common"; +import { Book } from "../../../model/book"; /** * 一些GPT相关的服务都在这边 @@ -103,6 +104,7 @@ export class GptService { //#endregion + //#region GPT 通用请求 /** * 发送GPT请求 * @param {*} message 请求的信息 @@ -138,9 +140,146 @@ export class GptService { throw error; } } + //#endregion - //#region 繁体中文 -> 简体中文 + //#region 原创推理 + /** + * 获取当前分镜的上下文数据 + * @param currentBookTaskDetail 当前分镜数据 + * @param bookTaskDetails 所有的小说分镜数据 + * @param contextCount 上下文行数 + */ + GetBookTaskDetailContextData(currentBookTaskDetail: Book.SelectBookTaskDetail, bookTaskDetails: Book.SelectBookTaskDetail[], contextCount: number): string { + let prefix = ""; + // 拼接一个word + let i = currentBookTaskDetail.no - 1 + if (i <= contextCount) { + prefix = bookTaskDetails + .filter((item, index) => index < i) + .map((item) => item.afterGpt) + .join('\r\n') + } else if (i > contextCount) { + prefix = bookTaskDetails + .filter((item, index) => i - index <= contextCount && i - index > 0) + .map((item) => item.afterGpt) + .join('\r\n') + } + + let suffix = ""; + let o_i = bookTaskDetails.length - i + if (o_i <= contextCount) { + suffix = bookTaskDetails + .filter((item, index) => index > i) + .map((item) => item.afterGpt) + .join('\r\n') + } else if (o_i > contextCount) { + suffix = bookTaskDetails + .filter((item, index) => index - i <= contextCount && index - i > 0) + .map((item) => item.afterGpt) + .join('\r\n') + } + + return `${prefix}\r\n${currentBookTaskDetail.afterGpt}\r\n${suffix}`; + } + + /** + * 返回当前推理数据的请求体中的message + * @param currentBookTaskDetail 当前推理的提示词数据 + * @param contextData 上下文数据 + * @param autoAnalyzeCharacter 自动分析的角色数据 + * @returns + */ + GetGPTRequestMessage(currentBookTaskDetail: Book.SelectBookTaskDetail, contextData: string, autoAnalyzeCharacter: string): any[] { + let message = [] + if ( + ['superSinglePrompt', 'onlyPromptMJ', 'superSinglePromptChinese'].includes( + global.config.gpt_auto_inference + ) + ) { + // 有返回案例的 + message = gptDefine.GetExamplePromptMessage(global.config.gpt_auto_inference) + // 加当前提问的 + message.push({ + role: 'user', + content: currentBookTaskDetail.afterGpt + }) + } else { + // 直接返回,没有案例的 + message = [ + { + role: 'system', + content: gptDefine.getSystemContentByType(global.config.gpt_auto_inference, { + textContent: contextData, + characterContent: autoAnalyzeCharacter + }) + }, + { + role: 'user', + content: gptDefine.getUserContentByType(global.config.gpt_auto_inference, { + textContent: currentBookTaskDetail.afterGpt, + wordCount: + global.config.gpt_model && global.config.gpt_model.includes('gpt-4') + ? '20' + : '40' + }) + } + ] + } + return message + } + + /** + * 原创推理提示词数据 + * @param currentBookTaskDetail 要推理的小说分镜任务 + * @param bookTaskDetails 所有的小说分镜任务 + * @param contextCount 上下文的数量 + * @param autoAnalyzeCharacter 自动分析的角色数据字符串 + */ + async OriginalInferencePrompt(currentBookTaskDetail: Book.SelectBookTaskDetail, bookTaskDetails: Book.SelectBookTaskDetail[], contextCount: number, autoAnalyzeCharacter: string) { + let gptPromptType = global.config.gpt_auto_inference; + let message = [] + if (gptPromptType == "customize") { //自定义模式 + // 自定义模式 + // 获取当前自定义的推理提示词 + let customize_gpt_prompt = ( + await gptDefine.getGptDataByTypeAndProperty('dynamic', 'customize_gpt_prompt', []) + ).data + let index = customize_gpt_prompt.findIndex( + (item: any) => item.id == global.config.customize_gpt_prompt + ) + if (global.config.customize_gpt_prompt && index < 0) { + throw new Error('自定义推理时要选择对应的自定义推理词') + } + message = gptDefine.CustomizeGptPrompt(customize_gpt_prompt[index], currentBookTaskDetail.afterGpt) + message.push({ + role: 'user', + content: currentBookTaskDetail.afterGpt + }) + } else { // 内置模式 + let context = this.GetBookTaskDetailContextData(currentBookTaskDetail, bookTaskDetails, contextCount); + message = this.GetGPTRequestMessage(currentBookTaskDetail, context, autoAnalyzeCharacter); + } + // 开始请求 + let res = await RetryWithBackoff(async () => { + return this.FetchGpt(message) + }, 5, 1000) + if (res) { + if (res) { + res = res + .replace(/\)\s*\(/g, ', ') + .replace(/^\(/, '') + .replace(/\)$/, '') + .replaceAll('*', '') + .replaceAll('--', ' ') + } + } + return res + } + + //#endregion + + //#region 中文 繁体转简体 /** * 将繁体中文转换为简体中文 * @param traditionalText 繁体中文文本 diff --git a/src/main/Service/MJ/mj.ts b/src/main/Service/MJ/mj.ts index 8bd4f9a..50aab0d 100644 --- a/src/main/Service/MJ/mj.ts +++ b/src/main/Service/MJ/mj.ts @@ -1,8 +1,6 @@ -import { isEmpty, join } from "lodash"; +import { isEmpty } 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 { CompressImageToSize, GetImageBase64, ImageSplit } from "../../../define/Tools/image"; @@ -14,14 +12,12 @@ 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 { PresetService } from "../presetService"; +import { SoftWareServiceBasic } from "../ServiceBasic/softwareServiceBasic"; import path from "path" const { v4: uuidv4 } = require('uuid') @@ -29,53 +25,42 @@ 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 + presetService: PresetService + softWareServiceBasic: SoftWareServiceBasic constructor() { this.imageStyle = new ImageStyle() this.taskScheduler = new TaskScheduler() this.tools = new Tools() this.bookServiceBasic = new BookServiceBasic(); + this.presetService = new PresetService() + this.mjApi = new MJApi() + this.softWareServiceBasic = new SoftWareServiceBasic() } - async InitService() { - if (!this.reverseBook) { - this.reverseBook = new ReverseBook() + + /** + * 获取MJ设置 + */ + async GetMJSetting() { + if (!this.mjSetting) { + this.mjSetting = await this.softWareServiceBasic.GetMjSetting() } - 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) { + sendChangeMessage(data: GeneralResponse.MessageResponse, message_name: string = DEFINE_STRING.BOOK.MAIN_DATA_RETURN) { + if (!message_name) { + message_name = DEFINE_STRING.BOOK.MAIN_DATA_RETURN + } global.newWindow[0].win.webContents.send(message_name, data) } @@ -88,8 +73,8 @@ export class MJOpt { */ async SingleReverseToGptPrompt(bookTaskDetailId: string, index: number): Promise { try { - await this.InitService(); - let bookTaskDetail = this.bookTaskDetail.GetBookTaskDetailDataById(bookTaskDetailId) + await this.GetMJSetting() + let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(bookTaskDetailId) if (bookTaskDetail == null) { throw new Error("没有找到对应的数据") } @@ -100,7 +85,7 @@ export class MJOpt { let reversePrompt = reversePrompts[index] let gptPrompt = reversePrompt.promptCN ? reversePrompt.promptCN : reversePrompt.prompt // 开始修改 - this.bookTaskDetail.UpdateBookTaskDetail(bookTaskDetailId, { + await this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetailId, { gptPrompt: gptPrompt }) @@ -125,14 +110,13 @@ export class MJOpt { try { // 执行你的操作 let task_res = await this.mjApi.GetMJAPITaskById(reqRes, task.id); - // 判断他的状态是不是成功 if (task_res.code == 0) { // 反推失败 - this.bookTaskDetail.UpdateBookTaskDetail(task.bookTaskDetailId, { + await this.bookServiceBasic.UpdateBookTaskDetail(task.bookTaskDetailId, { status: BookTaskStatus.REVERSE_FAIL }); - this.bookBackTaskList.UpdateTaskStatus({ + await this.bookServiceBasic.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.FAIL, errorMessage: task_res.message @@ -142,7 +126,7 @@ export class MJOpt { if (task_res.progress == 100) { task_res.type == MJRespoonseType.FINISHED; - this.bookBackTaskList.UpdateTaskStatus({ + await this.bookServiceBasic.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.DONE }); @@ -162,7 +146,7 @@ export class MJOpt { }); } - this.bookTaskDetail.UpdateBookTaskDetail(task.bookTaskDetailId, { + await this.bookServiceBasic.UpdateBookTaskDetail(task.bookTaskDetailId, { status: BookTaskStatus.REVERSE_DONE, reversePrompt: reversePrompt, gptPrompt: undefined @@ -175,7 +159,7 @@ export class MJOpt { type: ResponseMessageType.MJ_REVERSE, id: task.bookTaskDetailId, data: task_res - }); + }, task.messageName); break; } else { // 当获取的图片的进度小于100的时候,等待5秒继续监听 @@ -189,7 +173,7 @@ export class MJOpt { type: ResponseMessageType.MJ_REVERSE, id: task.bookTaskDetailId, data: task_res - }); + }, task.messageName); } catch (error) { throw error; } @@ -205,9 +189,8 @@ export class MJOpt { if (isEmpty(task.bookTaskDetailId)) { throw new Error("MJ反推,没有找到对应的分镜信息") } - await this.InitService() - - let bookTaskDetail = this.bookTaskDetail.GetBookTaskDetailDataById(task.bookTaskDetailId); + await this.GetMJSetting() + let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId); let oldImagePath = bookTaskDetail.oldImage if (isEmpty(oldImagePath)) { @@ -228,7 +211,7 @@ export class MJOpt { }) if (reqRes == '23') { // 任务队列过多,重新提交排队 - this.bookBackTaskList.UpdateTaskStatus({ + await this.bookServiceBasic.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.RECONNECT, }) @@ -236,7 +219,7 @@ export class MJOpt { return; } - this.bookTaskDetail.UpdateBookTaskDetail(task.bookTaskDetailId, { + await this.bookServiceBasic.UpdateBookTaskDetail(task.bookTaskDetailId, { status: BookTaskStatus.REVERSE }) this.sendChangeMessage({ @@ -248,18 +231,18 @@ export class MJOpt { type: MJRespoonseType.UPDATED, mjType: MJAction.DESCRIBE, category: this.mjSetting.type, - message_id: reqRes, + messageId: reqRes, id: task.bookTaskDetailId, progress: 0, status: "success" } as MJ.MJResponseToFront - }) + }, task.messageName) await this.fetchWithRetry(task, reqRes); } catch (error) { console.log(error.toString()) let errorMsg = "MJ反推失败,失败信息如下:" + error.toString() - this.bookBackTaskList.UpdateTaskStatus({ + await this.bookServiceBasic.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.FAIL, errorMessage: errorMsg @@ -275,13 +258,13 @@ export class MJOpt { type: MJRespoonseType.UPDATED, mjType: MJAction.DESCRIBE, category: this.mjSetting.type, - message_id: undefined, + messageId: undefined, id: task.bookTaskDetailId, progress: 0, message: error.toString(), status: "failure" } - }) + }, task.messageName) return errorMessage(errorMsg, "MJReverse_MJImage2Text") } } @@ -289,6 +272,59 @@ export class MJOpt { //#endregion //#region 合并提示词相关 + + /** + * 获取场景的提示词 + * @param ids 需要获取的IDs + * @returns + */ + async GetScenePresetStringByIds(ids: string[]): Promise { + let sceneString = '' + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let scene = await this.presetService.GetScenePresetDetailById(id) + if (scene.code == 1) { + // 这边开始拼接 + sceneString += scene.data.prompt + ', ' + } else { + throw new Error(scene.message) + } + } + return sceneString + } + + /** + * 获取人物提示词,包括垫图链接 + * @param ids 需要获取的IDs + * @returns + */ + async GetCharacterPresetStringByIds(ids: string[]): Promise<{ characterString: string, characterUrl: string }> { + let characterString = '' + let characterUrl = '' + let crefCw = undefined + + for (let i = 0; i < ids.length; i++) { + const element = ids[i]; + let character = await this.presetService.GetCharacterPresetDetailById(element) + if (character.code == 1) { + characterString += character.data.prompt + ', ' + if (character.data.image_url && character.data.image_url != '' && character.data.cref_cw) { + characterUrl += ` ${character.data.image_url} ` + crefCw = character.data.cref_cw + } + } + else { + throw new Error(character.message) + } + } + + //这边坐下合并s + if (characterUrl != '') { + characterUrl = ` --cref ${characterUrl} --cw ${crefCw}` + } + return { characterString, characterUrl } + } + /** * MJ 进行合并提示词,通过类型进行判断是单个还是全部合并 * @param id 合并的ID @@ -296,16 +332,14 @@ export class MJOpt { */ async MergePrompt(id: string, operateBookType: OperateBookType): Promise { try { - await this.InitService() - let bookTaskDetail = undefined as Book.SelectBookTaskDetail[]; let bookTask = undefined as Book.SelectBookTask; - + await this.GetMJSetting() if (operateBookType == OperateBookType.BOOKTASK) { - bookTaskDetail = this.bookTaskDetail.GetBookTaskData({ + bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({ bookTaskId: id - }).data - bookTask = this.bookTaskService.GetBookTaskDataById(id); + }) + bookTask = await this.bookServiceBasic.GetBookTaskDataById(id); // 判断是不是有为空的 let emptyName = [] as string[] for (let i = 0; i < bookTaskDetail.length; i++) { @@ -323,11 +357,16 @@ export class MJOpt { throw new Error("当前分镜没有推理提示词,请先生成") } bookTaskDetail = [tempBookTaskDetail] - bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail[0].bookTaskId); + bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskDetail[0].bookTaskId); } else { throw new Error("未知的合并类型") } + if (bookTaskDetail.length <= 0) { + throw new Error("没有找到对应的需要合并的分镜数据") + } + let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId); + // 获取合并的排序 let imageBaseSetting = JSON.parse(await fspromise.readFile(define.img_base, 'utf-8')); // 没有就直接报错 let promptSort = imageBaseSetting.prompt_sort; // 没有就直接报错 @@ -336,11 +375,8 @@ export class MJOpt { } // 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 mjSettingDb = await this.softWareServiceBasic.GetMjSetting() + let suffixParam = mjSettingDb.imageSuffix // let styleString = ''; // 拿到所有的风格 @@ -349,18 +385,33 @@ export class MJOpt { for (let i = 0; i < bookTaskDetail.length; i++) { const element = bookTaskDetail[i]; + // 没有推理提示词,直接跳过 + if (isEmpty(element.gptPrompt)) { + continue; + } let promptStr = ''; for (let i = 0; i < promptSort.length; i++) { const element = promptSort[i]; promptStr += `${'${' + element.value + '}'} ` } - console.log(promptStr) - // TODO 后面需要完善人物和场景的提示词 let characterString = '' // 人物 + let characterUrl = '' let sceneString = "" // 场景 // 获取当前的自定义风格的垫图字符串 + if (book.type == BookType.ORIGINAL) { + let sceneIds = element.sceneTags ? element.sceneTags : [] + let characterIds = element.characterTags ? element.characterTags : [] + if (sceneIds && sceneIds.length > 0) { + sceneString = await this.GetScenePresetStringByIds(sceneIds) + } + if (characterIds && characterIds.length > 0) { + let res = await this.GetCharacterPresetStringByIds(characterIds) + characterString = res.characterString + characterUrl = res.characterUrl + } + } let styleString = "" let style_url = '' @@ -404,9 +455,9 @@ export class MJOpt { promptStr = checkStringValueAddSuffix(promptStr, ',') + bookTask.suffixPrompt } promptStr = ' ' + promptStr; - promptStr += ` ${cref_url} ${style_url}${suffixParam}` + promptStr += ` ${characterUrl} ${style_url}${suffixParam}` // 修改数据库数据 - this.bookTaskDetail.UpdateBookTaskDetail(element.id, { + await this.bookServiceBasic.UpdateBookTaskDetail(element.id, { prompt: promptStr }) // 写回数据 @@ -416,7 +467,6 @@ export class MJOpt { }) } return successMessage(result, "MJ模式合并数据成功", "MJOpt_MergePrompt") - } catch (error) { return errorMessage("MJ合并提示词失败,错误信息如下:" + error.message, "MJOpt_MergePrompt") } @@ -430,29 +480,32 @@ export class MJOpt { * @param id 要添加的ID * @param operateBookType 操作的类型 */ - async AddMJGenerateImageTask(id: string, operateBookType: OperateBookType): Promise { + async AddMJGenerateImageTask(id: string, operateBookType: OperateBookType, responseMessageName: string = null, coverData: boolean = true): 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({ + bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({ bookTaskId: id - }).data + }) + if (!coverData) { + // 过滤掉已经生成的数据 + bookTaskDetail = bookTaskDetail.filter((item) => !item.mjMessage) + } // bookTask = this.bookTaskService.GetBookTaskDataById(id); } else if (operateBookType == OperateBookType.BOOKTASKDETAIL) { - bookTaskDetail = [this.bookTaskDetail.GetBookTaskDetailDataById(id)] + let bookTaskDetailRes = await this.bookServiceBasic.GetBookTaskDetailDataById(id) + bookTaskDetail = [bookTaskDetailRes] // bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail[0].bookTaskId); } else if (operateBookType == OperateBookType.UNDERBOOKTASK) { - let thisBookTaskDetail = this.bookTaskDetail.GetBookTaskDetailDataById(id); + let thisBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id); if (thisBookTaskDetail == null) { throw new Error("没有找到对应的数据") } // 获取批次的所有数据 - bookTaskDetail = this.bookTaskDetail.GetBookTaskData({ + bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({ bookTaskId: thisBookTaskDetail.bookTaskId - }).data + }) // bookTask = this.bookTaskService.GetBookTaskDataById(thisBookTaskDetail.bookTaskId); bookTaskDetail = bookTaskDetail.filter((item) => item.no >= thisBookTaskDetail.no) // 需要包含自己 } @@ -469,13 +522,10 @@ export class MJOpt { // 将任务添加到队列中 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 + let taskRes = await this.bookServiceBasic.AddBookBackTask(element.bookId, BookBackTaskType.MJ_IMAGE, TaskExecuteType.AUTO, element.bookTaskId, element.id, responseMessageName ); - 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) + await this.taskScheduler.AddLogToDB(element.bookId, BookBackTaskType.MJ_IMAGE, `添加 ${element.name} MJ生成任务成功`, element.bookTaskId, LoggerStatus.SUCCESS) } // 全部完毕 return successMessage(null, "MJ添加生成图片任务成功", "MJOpt_AddGenerateImageTask") @@ -492,30 +542,32 @@ export class MJOpt { async FetchImageTask(task: TaskModal.Task, reqRes: string, book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetail: Book.SelectBookTaskDetail) { while (true) { try { + await this.GetMJSetting() // 执行你的操作 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, { + await this.bookServiceBasic.UpdateBookTaskDetail(task.bookTaskDetailId, { status: BookTaskStatus.IMAGE_FAIL, }); - this.bookTaskDetail.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { + let errorMsg = `MJ生成图片失败,失败信息如下:${task_res.message}` + await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { mjApiUrl: this.mjApi.imagineUrl, - progress: 0, + progress: 100, category: this.mjApi.mjSetting.type, - imageClick: task_res.image_click, - imageShow: task_res.image_show, - messageId: task_res.message_id, + imageClick: task_res.imageClick, + imageShow: task_res.imageShow, + messageId: task_res.messageId, action: MJAction.IMAGINE, status: 'error', - message: task_res.message + message: errorMsg }) - this.bookBackTaskList.UpdateTaskStatus({ + await this.bookServiceBasic.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.FAIL, - errorMessage: task_res.message + errorMessage: errorMsg }); this.sendChangeMessage({ code: 0, @@ -523,24 +575,25 @@ export class MJOpt { id: task.bookTaskDetailId, data: { ...task_res, - status: "error" + status: "error", + message: errorMsg }, - message: task_res.message - }); + message: errorMsg + }, task.messageName); 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({ + await this.bookServiceBasic.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.DONE }); // 下载图片 - let imagePath = path.join(book.bookFolderPath, `data\\MJOriginalImage\\${task_res.message_id}.png`); + let imagePath = path.join(book.bookFolderPath, `data\\MJOriginalImage\\${task_res.messageId}.png`); await CheckFolderExistsOrCreate(path.dirname(imagePath)) - await this.tools.downloadFileUrl(task_res.image_click, imagePath) + await this.tools.downloadFileUrl(task_res.imageClick, imagePath) // 进行图片裁剪 let imageRes = await ImageSplit(imagePath, bookTaskDetail.name, path.join(book.bookFolderPath, 'data\\MJOriginalImage')); @@ -560,17 +613,17 @@ export class MJOpt { task_res.id = task.bookTaskDetailId; // 修改分镜的数据 - this.bookTaskDetail.UpdateBookTaskDetail(task.bookTaskDetailId, { + await this.bookServiceBasic.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, { + await this.bookServiceBasic.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, + imageClick: task_res.imageClick, + imageShow: task_res.imageShow, + messageId: task_res.messageId, action: MJAction.IMAGINE, status: task_res.status, }) @@ -579,32 +632,32 @@ export class MJOpt { type: ResponseMessageType.MJ_IMAGE, id: task.bookTaskDetailId, data: task_res - }); + }, task.messageName); break; } } // 这边也要修改数据 task_res.id = task.bookTaskDetailId; - this.bookTaskDetail.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { + await this.bookServiceBasic.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, + imageClick: task_res.imageClick, + imageShow: task_res.imageShow, + messageId: task_res.messageId, action: MJAction.IMAGINE, status: task_res.status, message: task_res.message }) - task_res.outImagePath = task_res.image_path; + task_res.outImagePath = task_res.imagePath; this.sendChangeMessage({ code: 1, type: ResponseMessageType.MJ_IMAGE, id: task.bookTaskDetailId, data: task_res - }); + }, task.messageName); // 当获取的图片的进度小于100的时候,等待5秒继续监听 await new Promise(resolve => setTimeout(resolve, 5000)); } catch (error) { @@ -622,16 +675,16 @@ export class MJOpt { if (isEmpty(task.bookTaskDetailId)) { throw new Error("MJ出图,没有找到对应的分镜信息") } - await this.InitService() - let bookTaskDetail = this.bookTaskDetail.GetBookTaskDetailDataById(task.bookTaskDetailId); + await this.GetMJSetting() + let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId); if (bookTaskDetail == null) { throw new Error("没有找到对应的分镜信息") } - let book = this.bookService.GetBookDataById(bookTaskDetail.bookId) + let book = await this.bookServiceBasic.GetBookDataById(bookTaskDetail.bookId) if (book == null) { throw new Error("没有找到对应的小说信息") } - let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail.bookTaskId) + let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskDetail.bookTaskId) if (bookTask == null) { throw new Error("没有找到对应的任务信息") } @@ -640,11 +693,11 @@ export class MJOpt { throw new Error(`${bookTaskDetail.name} 没有找到对应的提示词`) } // 这个就是任务ID - let reqRes = await this.mjApi.SubmitMJImagineAPI(task.id, prompt) + let reqRes = await this.mjApi.SubmitMJImagine(task.id, prompt) if (reqRes == '23') { console.log(task.id, "33333") // 任务队列过多,重新提交排队 - this.bookBackTaskList.UpdateTaskStatus({ + await this.bookServiceBasic.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.RECONNECT, }) @@ -662,8 +715,8 @@ export class MJOpt { progress: 0, status: "re_connect" } as MJ.MJResponseToFront - }) - this.bookTaskDetail.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { + }, task.messageName) + await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { mjApiUrl: this.mjApi.imagineUrl, progress: 0, category: this.mjApi.mjSetting.type, @@ -677,10 +730,10 @@ export class MJOpt { return; } - this.bookTaskDetail.UpdateBookTaskDetail(task.bookTaskDetailId, { + await this.bookServiceBasic.UpdateBookTaskDetail(task.bookTaskDetailId, { status: BookTaskStatus.IMAGE }) - this.bookBackTaskList.UpdateTaskStatus({ + await this.bookServiceBasic.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.RUNNING }) @@ -698,14 +751,12 @@ export class MJOpt { progress: 0, status: "submited" } as MJ.MJResponseToFront - }) + }, task.messageName) 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({ + await this.bookServiceBasic.UpdateTaskStatus({ id: task.id, status: BookBackTaskStatus.FAIL, errorMessage: errorMsg @@ -721,14 +772,14 @@ export class MJOpt { type: MJRespoonseType.UPDATED, mjType: MJAction.IMAGINE, category: this.mjSetting.type, - message_id: undefined, + messageId: undefined, id: task.bookTaskDetailId, progress: 0, message: error.toString(), status: "error" } - }) - this.bookTaskDetail.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { + }, task.messageName) + await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { mjApiUrl: this.mjApi.imagineUrl, progress: 0, category: this.mjApi.mjSetting.type, diff --git a/src/main/Service/MJ/mjApi.ts b/src/main/Service/MJ/mjApi.ts index d7f041a..08a0bde 100644 --- a/src/main/Service/MJ/mjApi.ts +++ b/src/main/Service/MJ/mjApi.ts @@ -69,7 +69,6 @@ class MJApi { */ async GetMJAPITaskById(taskId: string, backTaskId: string) { try { - await this.InitMJSetting(); let url = this.fetchTaskUrl.replace("${id}", taskId) let headers = undefined @@ -107,15 +106,15 @@ class MJApi { type: MJRespoonseType.UPDATED, progress: isNaN(progress) ? 0 : progress, category: this.mjSetting.type, - image_click: res.data.imageUrl, - image_show: res.data.imageUrl, - image_path: res.data.imageUrl, - message_id: taskId, + imageClick: res.data.imageUrl, + imageShow: res.data.imageUrl, + imagePath: res.data.imageUrl, + messageId: taskId, status: status, code: code, prompt: res.data.prompt == "" ? res.data.promptEn : res.data.prompt, message: res.data.failReason, - mj_api_url: this.fetchTaskUrl, + mjApiUrl: this.fetchTaskUrl, } as MJ.MJResponseToFront return resObj } catch (error) { diff --git a/src/main/Service/SD/sd.ts b/src/main/Service/SD/sd.ts index 8687b81..253c0cd 100644 --- a/src/main/Service/SD/sd.ts +++ b/src/main/Service/SD/sd.ts @@ -4,18 +4,25 @@ import { checkStringValueAddSuffix, errorMessage, successMessage } from "../../P import { define } from '../../../define/define' import fs from "fs"; import { ImageStyle } from "../Book/imageStyle"; -import { OperateBookType } from "../../../define/enum/bookEnum"; +import { BookBackTaskStatus, BookType, OperateBookType } from "../../../define/enum/bookEnum"; import { isEmpty } from "lodash"; import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; +import { PresetService } from "../presetService"; +import path from "path"; +import axios from "axios"; +import { CheckFolderExistsOrCreate, CopyFileOrFolder, DeleteFileExifData } from "../../../define/Tools/file"; +import { Base64ToFile } from "../../../define/Tools/image"; +import { MJAction, MJImageType } from "../../../define/enum/mjEnum"; const fspromise = fs.promises export class SDOpt { imageStyle: ImageStyle - bookServiceBasic: BookServiceBasic + presetService: PresetService constructor() { this.bookServiceBasic = new BookServiceBasic() this.imageStyle = new ImageStyle() + this.presetService = new PresetService() } @@ -28,6 +35,50 @@ export class SDOpt { return sdSetting } + //#region 合并提示词 + + /** + * 获取所有的场景提示词,然后合并 + * @param ids 需要获取场景的ID + * @returns + */ + async GetScenePresetStringByIds(ids: string[]): Promise { + let result = '' + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let scene = await this.presetService.GetScenePresetDetailById(id) + if (scene.code == 1) { + // 这边开始拼接 + result += scene.data.prompt + ', ' + } else { + throw new Error(scene.message) + } + } + return result + } + + /** + * 获取所有的人物的提示词,然后合并 + * @param ids 需要获取人物的ID + * @returns + */ + async GetCharacterPresetStringByIds(ids: string[]): Promise { + let result = '' + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let character = await this.presetService.GetCharacterPresetDetailById(id) + if (character.code == 1) { + result += character.data.prompt + ', ' + if (character.data.lora && character.data.lora != '无' && character.data.loraWeight) { + result += `, ` + } + } else { + throw new Error(character.message) + } + } + return result + } + /** * SD的提示词合并 * @param id 要处理的ID @@ -42,9 +93,9 @@ export class SDOpt { let style_weight = sd_setting.setting.style_weight ? sd_setting.setting.style_weight : 1 if (operateBookType == OperateBookType.BOOKTASK) { - bookTaskDetail = (await this.bookServiceBasic.GetBookTaskData({ + bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({ bookTaskId: id - })).bookTasks; + }); bookTask = await this.bookServiceBasic.GetBookTaskDataById(id); // 判断是不是有为空的 let emptyName = [] as string[] @@ -67,6 +118,7 @@ export class SDOpt { } else { throw new Error("未知的合并类型") } + let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId); // 获取合并的排序 let promptSort = JSON.parse(await fspromise.readFile(define.img_base, 'utf-8')).prompt_sort; // 没有就直接报错 @@ -80,33 +132,50 @@ export class SDOpt { for (let i = 0; i < styleArr.length; i++) { const element = styleArr[i] if (element.type == 'style_main') { - styleString += `(${element.prompt}:${style_weight})` + ', ' + if (!isEmpty(element.prompt)) { + styleString += `(${element.prompt}:${style_weight})` + ', ' + } if (element.lora && element.lora != '无' && element.lora_weight) { styleString += `, ` } } else { - styleString += `(${element.english_style}:${style_weight})` + ',' + if (!isEmpty(element.prompt)) { + styleString += `(${element.english_style}:${style_weight})` + ',' + } } } // 获取SD的通用前缀 let sdGlobalPrompt = JSON.parse(await fspromise.readFile(define.sd_setting, 'utf-8')).webui.prompt; - - // TODO 反推这边目前就只有风格和提示词,暂时没有人物和场景 - let sceneString = ""; // 场景 - let characterString = ""; // 人物 - - let result = []; // 返回前端的数据数组 for (let i = 0; i < bookTaskDetail.length; i++) { const element = bookTaskDetail[i]; - + // 没有推理提示词,直接跳过 + if (isEmpty(element.gptPrompt)) { + continue; + } let promptStr = ''; for (let i = 0; i < promptSort.length; i++) { const element = promptSort[i]; promptStr += `${'${' + element.value + '}'} ` } + let sceneString = ""; // 场景 + let characterString = ""; // 人物 + + // 只有原创才需要获取人物和场景 + if (book.type == BookType.ORIGINAL) { + // 这边获取对应的人物和场景 + let sceneIds = element.sceneTags ? element.sceneTags : []; + let characterIds = element.characterTags ? element.characterTags : []; + if (sceneIds && sceneIds.length > 0) { + sceneString = await this.GetScenePresetStringByIds(sceneIds) + } + if (characterIds && characterIds.length > 0) { + characterString = await this.GetCharacterPresetStringByIds(characterIds) + } + } + // 开始合并 promptStr = promptStr.replace('${style}', styleString) // 风格 promptStr = promptStr.replace('${character}', characterString) // 人物 @@ -145,4 +214,157 @@ export class SDOpt { return errorMessage("SD合并提示词,错误信息如下:" + error.toString(), "SDOpt_MergePrompt") } } + + //#endregion + + //#region 生图相关任务 + + /** + * SD单个生成图片任务 + * @param task + */ + async SDImageGenerate(task: TaskModal.Task) { + let sdSetting = undefined + try { + // 开始生图 + sdSetting = await this.GetSDSetting(); + let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId); + let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskDetail.bookTaskId); + let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId); + + let prompt = bookTaskDetail.prompt; + let url = sdSetting.setting.webui_api_url + if (url.endsWith('/')) { + url = url + "sdapi/v1/txt2img" + } else { + url = url + "/sdapi/v1/txt2img" + } + if (!isEmpty(sdSetting.webui.prompt)) { + prompt = sdSetting.webui.prompt + ', ' + prompt + } + + // 判断当前是不是有开修脸修手 + let ADetailer = { + args: sdSetting.adetailer + } + + // 种子默认 -1,随机 + let seed = -1; + let body = { + prompt: prompt, + negative_prompt: sdSetting.webui.negative_prompt, + seed: seed, + sampler_name: sdSetting.webui.sampler_name, + // 提示词相关性 + cfg_scale: sdSetting.webui.cfg_scale, + width: sdSetting.webui.width, + height: sdSetting.webui.height, + batch_size: sdSetting.setting.batch_size, + n_iter: 1, + steps: sdSetting.webui.steps, + save_images: false + } + if (bookTaskDetail.adetailer) { + body['alwayson_scripts'] = { + "ADetailer": ADetailer + } + } + const response = await axios.post(url, body) + // TODO 对SD图片种子的一些处理,待定 + // let info = JSON.parse(res.data.info) + // if (seed == -1) { + // seed = info.seed + // } + let images = response.data.images + 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); + + let subImagePath = [] + let outImagePath = '' + // 开始写出图片 + for (let i = 0; i < images.length; i++) { + const element = images[i]; + // 包含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 Base64ToFile(element, infoImgPath) + // 这边去图片信息 + await DeleteFileExifData(path.join(define.package_path, 'exittool/exiftool.exe'), infoImgPath, imgPath); + // 写出去 + if (bookTask.name == 'output_00001') { + // 复制一个到input + let inputImgPath = path.join(inputFolder, `${bookTaskDetail.name}.png`) + await CopyFileOrFolder(imgPath, inputImgPath) + } + if (i == 0) { + // 复制到对应的文件夹里面 + let outPath = path.join(outputFolder, `${bookTaskDetail.name}.png`) + await CopyFileOrFolder(imgPath, outPath) + outImagePath = outPath + } + subImagePath.push(imgPath) + } + // 修改数据库 + await this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetail.id, { + outImagePath: path.relative(define.project_path, outImagePath), + subImagePath: subImagePath.map((item) => path.relative(define.project_path, item)) + }) + await this.bookServiceBasic.UpdateTaskStatus({ + id: task.id, + status: BookBackTaskStatus.DONE + }); + let resp = { + mjApiUrl: url, + progress: 100, + category: MJImageType.LOCAL_SD, + imageClick: subImagePath.join(','), + imageShow: subImagePath.join(','), + messageId: subImagePath.join(','), + action: MJAction.IMAGINE, + status: "success", + subImagePath: subImagePath, + outImagePath: outImagePath, + message: "SD生成图片成功" + } + await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, resp) + global.newWindow[0].win.webContents.send(task.messageName, { + code: 1, + message: "SD生成图片成功", + data: { + ...resp, + id: bookTaskDetail.id + } + }) + } catch (error) { + let errorMsg = "SD生成图片失败,错误信息如下:" + error.toString() + await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { + mjApiUrl: sdSetting ? sdSetting.setting.webui_api_url : "", + progress: 0, + category: MJImageType.LOCAL_SD, + imageClick: "", + imageShow: "", + messageId: "", + action: MJAction.IMAGINE, + status: "error", + message: errorMsg, + id: task.bookTaskDetailId + }) + + global.newWindow[0].win.webContents.send(task.messageName, { + code: 0, + message: errorMsg, + data: { + status: 'error', + message: errorMsg + } + }) + throw error + } + } + //#endregion } \ No newline at end of file diff --git a/src/main/Service/ServiceBasic/bookServiceBasic.ts b/src/main/Service/ServiceBasic/bookServiceBasic.ts index 0e28bbc..47b86b6 100644 --- a/src/main/Service/ServiceBasic/bookServiceBasic.ts +++ b/src/main/Service/ServiceBasic/bookServiceBasic.ts @@ -43,6 +43,7 @@ export class BookServiceBasic { } + //#region 事务操作 transaction(callback: (realm: any) => void) { this.bookService.transaction(() => { @@ -57,7 +58,7 @@ export class BookServiceBasic { /** * 通过小说ID获取小说数据 * @param bookId 小说ID - * @returns + * @returns */ async GetBookDataById(bookId: string): Promise { await this.InitService(); @@ -96,7 +97,7 @@ export class BookServiceBasic { /** * 通过小说ID获取小说批次任务数据 * @param bookTaskId 小说批次任务ID - * @returns + * @returns */ async GetBookTaskDataById(bookTaskId: string): Promise { await this.InitService(); @@ -138,7 +139,7 @@ export class BookServiceBasic { /** * 更新小说批次任务的数据 * @param bookTaskId 小说批次任务ID - * @param data + * @param data */ async UpdetedBookTaskData(bookTaskId: string, data: Book.SelectBookTask): Promise { await this.InitService(); @@ -166,10 +167,15 @@ export class BookServiceBasic { //#region 小说批次任务对应的分镜的相关的基础服务 + async AddBookTaskDetail(bookTaskDetail: Book.SelectBookTaskDetail): Promise { + await this.InitService(); + this.bookTaskDetailService.AddBookTaskDetail(bookTaskDetail) + } + /** * 获取指定的小说批次任务分镜数据,通过分镜ID * @param bookTaskDetailId 小说批次任务分镜ID - * @returns + * @returns */ async GetBookTaskDetailDataById(bookTaskDetailId: string): Promise { await this.InitService(); @@ -184,12 +190,13 @@ export class BookServiceBasic { /** * 获取指定的小说批次任务分镜数据,通过查询条件 - * @param condition + * @param condition + * @param returnEmpty 是否返回空数据,默认 false 不返回,如果返回空数据,会抛出异常 */ - async GetBookTaskDetailData(condition: Book.QueryBookTaskDetailCondition): Promise { + async GetBookTaskDetailData(condition: Book.QueryBookTaskDetailCondition, returnEmpty: boolean = false): Promise { await this.InitService(); let bookTaskDetails = this.bookTaskDetailService.GetBookTaskData(condition) - if (bookTaskDetails.data.length <= 0) { + if (!returnEmpty && bookTaskDetails.data.length <= 0) { let msg = "未找到对应的小说批次任务分镜数据,请检查"; throw new Error(msg) } @@ -221,7 +228,7 @@ export class BookServiceBasic { /** * 删除指定的小说分镜的反推提示词 - * @param bookTaskDetail + * @param bookTaskDetail */ async DeleteBookTaskDetailReversePromptById(bookTaskDetailId: string): Promise { await this.InitService(); @@ -230,13 +237,35 @@ export class BookServiceBasic { /** * 删除指定的小说分镜生成的图片 - * @param bookTaskDetailId + * @param bookTaskDetailId */ async DeleteBoookTaskDetailGenerateImage(bookTaskDetailId: string): Promise { await this.InitService() this.bookTaskDetailService.DeleteBoookTaskDetailGenerateImage(bookTaskDetailId); } + /** + * 修改小说分镜数据的反推提示词 + * @param bookTaskDetailId 小说分镜的ID + * @param reversePromptId 反推提示词的ID + * @param data 反推提示词数据 + */ + async UpdateBookTaskDetailReversePrompt(bookTaskDetailId: string, reversePromptId: string, data: Book.ReversePrompt): Promise { + await this.InitService(); + this.bookTaskDetailService.UpdateBookTaskDetailReversePrompt(bookTaskDetailId, reversePromptId, data) + } + + /** + * 更行小说分镜的MJ消息,就是出图信息 + * @param bookTaskDetailId 小说分镜的数据信息 + * @param mjMessage 要保存到分镜信息 + */ + async UpdateBookTaskDetailMjMessage(bookTaskDetailId: string, mjMessage: Book.MJMessage) { + await this.InitService(); + this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(bookTaskDetailId, mjMessage) + } + + //#endregion //#region 小说后台任务相关操作 @@ -253,10 +282,12 @@ export class BookServiceBasic { taskType: BookBackTaskType, executeType = TaskExecuteType.AUTO, bookTaskId = null, - bookTaskDetailId = null): Promise { + bookTaskDetailId = null, + responseMessageName: string = null + ): Promise { await this.InitService(); - let res = this.bookBackTaskListService.AddBookBackTask(bookId, taskType, executeType, bookTaskId, bookTaskDetailId) + let res = this.bookBackTaskListService.AddBookBackTask(bookId, taskType, executeType, bookTaskId, bookTaskDetailId, responseMessageName) if (res.code == 0) { throw new Error(res.message) } @@ -270,7 +301,7 @@ export class BookServiceBasic { * 通过小说ID和小说批次任务ID获取小说和小说批次任务数据 * @param bookId 小说ID * @param bookTaskName 小说批次的名字,或者是小说批次任务的ID - * @returns + * @returns */ async GetBookAndTask(bookId: string, bookTaskName: string) { await this.InitService(); @@ -295,4 +326,13 @@ export class BookServiceBasic { } return { book: book as Book.SelectBook, bookTask: bookTaskRes.data.bookTasks[0] as Book.SelectBookTask } } + + /** + * 修改后台队列的状态 + * @param bookBackTask 要修改的数据 + */ + async UpdateTaskStatus(bookBackTask: Book.UpdateBookTaskListStatus) { + await this.InitService(); + this.bookBackTaskListService.UpdateTaskStatus(bookBackTask) + } } diff --git a/src/main/Service/ServiceBasic/softwareServiceBasic.ts b/src/main/Service/ServiceBasic/softwareServiceBasic.ts index 3637906..5bd4a1d 100644 --- a/src/main/Service/ServiceBasic/softwareServiceBasic.ts +++ b/src/main/Service/ServiceBasic/softwareServiceBasic.ts @@ -1,20 +1,26 @@ import { SoftwareService } from '../../../define/db/service/SoftWare/softwareService'; +import { MJSettingService } from '../../../define/db/service/SoftWare/mjSettingService'; +import { MJSetting } from '../../../model/Setting/mjSetting'; export class SoftWareServiceBasic { softwareService: SoftwareService + mjSettingService: MJSettingService constructor() { } async InitService() { if (!this.softwareService) { this.softwareService = await SoftwareService.getInstance() } + if (!this.mjSettingService) { + this.mjSettingService = await MJSettingService.getInstance() + } } //#region software相关的基础服务 /** - * 更新软件配置信息 + * 更新软件配置信息,如果没有ID则修改指定属性 * @param software */ async UpdateSoftware(software: SoftwareSettingModel.SoftwareSetting): Promise { @@ -73,4 +79,26 @@ export class SoftWareServiceBasic { //#endregion + //#region MJ设置相关 + + /** + * 获取MJ的设置信息 + * @returns + */ + async GetMjSetting(): Promise { + await this.InitService(); + let mjSetting = this.mjSettingService.GetMjSetting({}) + if (mjSetting.code == 1) { + if (mjSetting.data.length <= 0) { + throw new Error("未找到MJ的设置信息,请检查"); + } + // 这边只是返回第一个 + return mjSetting.data[0] + } else { + throw new Error(mjSetting.message) + } + } + + //#endregion + } \ No newline at end of file diff --git a/src/main/Service/Subtitle/subtitleService.ts b/src/main/Service/Subtitle/subtitleService.ts index 6f3cd91..8264917 100644 --- a/src/main/Service/Subtitle/subtitleService.ts +++ b/src/main/Service/Subtitle/subtitleService.ts @@ -12,7 +12,7 @@ import { CheckFileOrDirExist } from "../../../define/Tools/file"; import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; import { Subtitle } from "./subtitle"; import { TaskScheduler } from "../taskScheduler"; -import { BookType, OperateBookType } from "../../../define/enum/bookEnum"; +import { BookTaskStatus, BookType, OperateBookType } from "../../../define/enum/bookEnum"; import { Book } from "../../../model/book"; import { TimeStringToMilliseconds } from "../../../define/Tools/time"; @@ -130,7 +130,7 @@ export class SubtitleService { * @param bookTaskId 小说批次任务ID * @param operateBookType 操作的小说类型 * @param coverData 是不是要覆盖旧的数据 - * @returns + * @returns */ async GetCopywriting(bookId: string, bookTaskId: string, operateBookType: OperateBookType, coverData: boolean): Promise { try { @@ -195,7 +195,7 @@ export class SubtitleService { /** * 导出指定导出指定小说的文案 * @param bookTaskId 小说批次任务ID - * @returns + * @returns */ async ExportCopywriting(bookTaskId: string): Promise { try { @@ -233,11 +233,11 @@ export class SubtitleService { } /** - * 将 - * @param bookId - * @param bookTaskId - * @param txtPath - * @returns + * 导入修改过后的文案数据 + * @param bookId + * @param bookTaskId + * @param txtPath + * @returns */ async ImportCopywriting(bookId: string, bookTaskId: string, txtPath: string): Promise { try { @@ -305,7 +305,7 @@ export class SubtitleService { originalTime = JSON.parse(originalTimeString) } } - // 判断分镜数据和批次数据是不是相同的 + // 判断分镜数据和批次数据是不是相同的 好好办办 if (originalTime.length != bookTaskDetails.length) { originalTime = [] } @@ -337,5 +337,68 @@ export class SubtitleService { } } + /** + * 保存文案的对齐信息,这边会做一些判断 + * @param bookTaskId 小说任务ID + * @param copywritingData 要保存的文案数据 + * @param operateBookType 操作的小说类型 + * @returns + */ + async SaveCopywriting(bookTaskId: string, copywritingData: SubtitleModel.SaveCopywritingData[], operateBookType: OperateBookType): Promise { + try { + if (operateBookType != OperateBookType.BOOKTASK) { + throw new Error('目前只支持对小说任务的文案保存') + } + let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId) + let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({ + bookTaskId: bookTaskId + }, true) + + if (bookTaskDetails.length == 0) { + // 新增 + for (let i = 0; i < copywritingData.length; i++) { + const element = copywritingData[i]; + await this.bookServiceBasic.AddBookTaskDetail({ + bookTaskId: bookTaskId, + bookId: bookTask.bookId, + startTime: element.start_time, + endTime: element.end_time, + status: BookTaskStatus.WAIT, + word: element.word, + afterGpt: element.after_gpt, + subValue: JSON.stringify(element.subValue), + timeLimit: element.timeLimit + }) + } + } else { + // 修改,这边就要判断是不是数量一致了 + if (bookTaskDetails.length != copywritingData.length) { + throw new Error("已有文案,再导入的时候,文案函数数量和分镜数据不一致,请检查") + } + // 开始修改。修改使用事务吧 + this.bookServiceBasic.transaction((realm) => { + for (let i = 0; i < copywritingData.length; i++) { + const element = copywritingData[i]; + let btd = realm.objectForPrimaryKey("BookTaskDetail", bookTaskDetails[i].id); + if (btd == null) { + throw new Error("未找到对应的分镜数据,请检查") + } + // 开始修改 + btd.startTime = element.start_time; + btd.endTime = element.end_time; + btd.word = element.word; + btd.afterGpt = element.after_gpt; + btd.subValue = JSON.stringify(element.subValue); + btd.timeLimit = element.timeLimit + } + }) + + } + + } catch (error) { + return errorMessage("保存文案数据失败,失败信息如下:" + error.toString(), 'SubtitleService_SaveCopywriting') + } + } + //#endregion -} \ No newline at end of file +} diff --git a/src/main/Service/Translate/Translate.ts b/src/main/Service/Translate/Translate.ts index 4552ca7..892c270 100644 --- a/src/main/Service/Translate/Translate.ts +++ b/src/main/Service/Translate/Translate.ts @@ -299,19 +299,11 @@ export class Translate { for (let j = 0; j < req_arr.length; j++) { const item = req_arr[j]; let res_tmp = translateList.find(item => item.index == j); - if (to == "zh") { - let obj = { - src: item, - dst: res_tmp.translated - } - res_data.push(obj); - } else if (to == "en") { - let obj = { - src: res_tmp.translated, - dst: item - } - res_data.push(obj); + let obj = { + src: item, + dst: res_tmp.translated } + res_data.push(obj); } // 直接返回数据 @@ -388,19 +380,11 @@ export class Translate { let res_data = []; for (let j = 0; j < req_data.length; j++) { const item = req_data[j]; - if (to == "zh") { - let obj = { - src: item, - dst: translateList[j] - } - res_data.push(obj); - } else if (to == "en") { - let obj = { - src: translateList[j], - dst: item - } - res_data.push(obj); + let obj = { + src: item, + dst: translateList[j] } + res_data.push(obj); } // 直接返回数据 @@ -474,19 +458,11 @@ export class Translate { let res_data = []; for (let j = 0; j < req_data.length; j++) { const item = req_data[j]; - if (to == "zh") { - let obj = { - src: item, - dst: translateList[j].Translation - } - res_data.push(obj); - } else if (to == "en") { - let obj = { - src: translateList[j].Translation, - dst: item - } - res_data.push(obj); + let obj = { + src: item, + dst: translateList[j].Translation } + res_data.push(obj); } // 直接返回数据 @@ -568,20 +544,20 @@ export class Translate { } let res_data = [] + res_data = res.data.trans_result // 将所有的数据协会到本地(然后发送消息到前台界面) - if (res.data.to == "zh") { - res_data = res.data.trans_result - } else { - // 直接在这边处理(前端不用处理) - for (let i = 0; i < res.data.trans_result.length; i++) { - const element = res.data.trans_result[i]; - let obj = { - src: element.dst, - dst: element.src - }; - res_data.push(obj); - } - } + // if (res.data.to == "zh") { + // } else { + // // 直接在这边处理(前端不用处理) + // for (let i = 0; i < res.data.trans_result.length; i++) { + // const element = res.data.trans_result[i]; + // let obj = { + // src: element.dst, + // dst: element.src + // }; + // res_data.push(obj); + // } + // } // 直接返回数据 return successMessage({ to: value.to, diff --git a/src/main/Service/Translate/TranslateService.ts b/src/main/Service/Translate/TranslateService.ts index 7714447..e004cd9 100644 --- a/src/main/Service/Translate/TranslateService.ts +++ b/src/main/Service/Translate/TranslateService.ts @@ -4,46 +4,38 @@ import { errorMessage, successMessage } from "../../Public/generalTools"; import { Translate } from "./Translate"; import { DEFINE_STRING } from "../../../define/define_string" import { TranslateAPIType, TranslateType } from "../../../define/enum/translate"; -import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService"; import { Book } from "../../../model/book"; import { ResponseMessageType } from "../../../define/enum/softwareEnum"; -import { SoftwareService } from '../../../define/db/service/SoftWare/softwareService' import { isEmpty } from "lodash"; import { ValidateJson } from "../../../define/Tools/validate"; import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; +import { SoftWareServiceBasic } from "../ServiceBasic/softwareServiceBasic"; +import { ExecuteConcurrently } from "../../../define/Tools/common"; /** * 翻译实现服务 */ export class TranslateService { translate: Translate - bookTaskDetail: BookTaskDetailService; - softwareService: SoftwareService bookServiceBasic: BookServiceBasic + softWareServiceBasic: SoftWareServiceBasic constructor() { this.bookServiceBasic = new BookServiceBasic(); - } - - async InitService() { - if (!this.bookTaskDetail) { - this.bookTaskDetail = await BookTaskDetailService.getInstance() - } - if (!this.softwareService) { - this.softwareService = await SoftwareService.getInstance() - } - if (!this.translate) { - this.translate = new Translate() - } + this.translate = new Translate(); + this.softWareServiceBasic = new SoftWareServiceBasic(); } // 返回翻译结果。用于前端修改 - private sendTranslateReturn(windowId: number, data: GeneralResponse.MessageResponse): void { + private sendTranslateReturn(windowId: number, data: GeneralResponse.MessageResponse, message_name: string = DEFINE_STRING.BOOK.MAIN_DATA_RETURN): void { let win = global.newWindow[0] if (windowId) { win = global.newWindow.filter(item => item.id == windowId)[0]; } - win.win.webContents.send(DEFINE_STRING.BOOK.MAIN_DATA_RETURN, data) + if (!message_name) { + message_name = DEFINE_STRING.BOOK.MAIN_DATA_RETURN + } + win.win.webContents.send(message_name, data) } @@ -97,14 +89,13 @@ export class TranslateService { */ async GetTranslateSetting(): Promise { try { - await this.InitService() let translateSetting = undefined as TranslateModel.TranslateModel - let translateSettingString = this.softwareService.GetSoftWarePropertyData('translationSetting'); + let translateSettingString = await this.softWareServiceBasic.GetSoftWarePropertyData('translationSetting'); if (isEmpty(translateSettingString)) { // 初始化 translateSetting = this.InitialTranslateSetting(); await this.ResetTranslateSetting(); - translateSettingString = this.softwareService.GetSoftWarePropertyData('translationSetting'); + translateSettingString = await this.softWareServiceBasic.GetSoftWarePropertyData('translationSetting'); translateSetting = JSON.parse(translateSettingString); } else { // 解析 @@ -140,7 +131,7 @@ export class TranslateService { async ResetTranslateSetting(): Promise { try { let translateSetting = this.InitialTranslateSetting() - let res = this.softwareService.SaveSoftwarePropertyData('translationSetting', JSON.stringify(translateSetting)) + let res = await this.softWareServiceBasic.SaveSoftwarePropertyData('translationSetting', JSON.stringify(translateSetting)) return successMessage(translateSetting, "重置翻译设置成功", "TranslateService_ResetTranslateSetting") } catch (error) { @@ -155,7 +146,7 @@ export class TranslateService { */ async SaveTranslateSetting(value: TranslateModel.TranslateModel): Promise { try { - let res = this.softwareService.SaveSoftwarePropertyData('translationSetting', JSON.stringify(value)) + let res = await this.softWareServiceBasic.SaveSoftwarePropertyData('translationSetting', JSON.stringify(value)) // 这边要判断是不是用的laitool let laitool = value.translates.filter(item => item.name == TranslateAPIType.LAITOOL) if (laitool.length > 0) { @@ -194,7 +185,7 @@ export class TranslateService { updateData.promptCN = dstString } // 修改数据 - this.bookTaskDetail.UpdateBookTaskDetailReversePrompt(bookTaskDetailId, reversePromptId, updateData) + await this.bookServiceBasic.UpdateBookTaskDetailReversePrompt(bookTaskDetailId, reversePromptId, updateData) } // 处理返回的数据 @@ -214,56 +205,62 @@ export class TranslateService { } } - // 翻译 + /** + * 翻译 + * @param value + * @returns + */ async TranslateNowReturn(value: TranslateModel.TranslateNowIPCParams[]): Promise { try { - await this.InitService() // 循环所有的数据,返回翻译结果 + let tasks = [] for (let i = 0; i < value.length; i++) { const element = value[i]; - let res = await this.translate.TranslateReturnNow(element) - // 单个翻译,将返回的数据写入到原数据中 + tasks.push(async () => { + let res = await this.translate.TranslateReturnNow(element) + // global.logger.info("断点写出", JSON.stringify(res)) + // 单个翻译,将返回的数据写入到原数据中 - // 添加一个对返回信息进行处理的函数 - let data = res.data - // 先将数据进行拼接 - let srcString = "" - if (element.isSplit) { - let dstStrs = [] - } else { - // 没有拆分的,只有一句 - srcString = data.data[0].dst - } - // 写回数据库 - await this.TranslateReturnProcess(element, data.to, srcString); - - let responseType = ResponseMessageType.REVERSE_PROMPT_TRANSLATE; - if (element.type == TranslateType.GPT_PROMPT_TRANSLATE) { - responseType = ResponseMessageType.GPT_PROMPT_TRANSLATE - } - // 做个返回数据 - this.sendTranslateReturn(element.windowId, { - code: 1, - id: element.bookTaskDetailId, - type: responseType, - data: { - progress: i + 1, - total: value.length, - from: element.from, - to: data.to, - bookTaskDetailId: element.bookTaskDetailId, - reversePromptId: element.reversePromptId, - prompt: srcString, - promptCN: srcString + // 添加一个对返回信息进行处理的函数 + let data = res.data + // 先将数据进行拼接 + let srcString = "" + if (element.isSplit) { + let dstStrs = [] + } else { + // 没有拆分的,只有一句 + srcString = data.data[0].dst } + // 写回数据库 + await this.TranslateReturnProcess(element, data.to, srcString); + + let responseType = ResponseMessageType.REVERSE_PROMPT_TRANSLATE; + if (element.type == TranslateType.GPT_PROMPT_TRANSLATE) { + responseType = ResponseMessageType.GPT_PROMPT_TRANSLATE + } + // 做个返回数据 + this.sendTranslateReturn(element.windowId, { + code: 1, + id: element.bookTaskDetailId, + type: responseType, + data: { + progress: i + 1, + total: value.length, + from: element.from, + to: data.to, + bookTaskDetailId: element.bookTaskDetailId, + reversePromptId: element.reversePromptId, + prompt: srcString, + promptCN: srcString + } + }, element.responseMessgeName) }) } - + let res = await ExecuteConcurrently(tasks, global.config.task_number) + // 将翻译后的数据返回,前端进行修改 return successMessage(null, "全部翻译完成", "TranslateService_TranslateNowReturn") - } catch (error) { return errorMessage("翻译失败,失败信息如下:" + error.toString(), "TranslateService_TranslateNowReturn") } } - } \ No newline at end of file diff --git a/src/main/Service/d3.ts b/src/main/Service/d3.ts index a48f5c4..a41c7a0 100644 --- a/src/main/Service/d3.ts +++ b/src/main/Service/d3.ts @@ -1,15 +1,193 @@ +import { BookServiceBasic } from "./ServiceBasic/bookServiceBasic"; +import { define } from '../../define/define' +import fs from 'fs' +import { GptService } from "./GPT/gpt"; +import { isEmpty } from "lodash"; +import path from 'path' +import { CheckFolderExistsOrCreate, CopyFileOrFolder } from "../../define/Tools/file"; +import { Base64ToFile, GetImageBase64 } from "../../define/Tools/image"; +import { BookBackTaskStatus } from "../../define/enum/bookEnum"; +import { MJAction, MJImageType } from "../../define/enum/mjEnum"; +import axios from "axios"; +export class D3Opt { + bookServiceBasic: BookServiceBasic + gptService: GptService + constructor() { + this.gptService = new GptService() + this.bookServiceBasic = new BookServiceBasic() + } -export class D3 { - constructor() { } + /** + * 获取SD的设置,之后要删掉,改为数据库 + */ + private async GetSDSetting() { + let sdSetting = JSON.parse(await fs.promises.readFile(define.sd_setting, 'utf-8')) + return sdSetting + } - //#region D3进行画图的基础方法 + //#region D3生图 - //#endregion + /** + * 异步发送D3 API图像请求 + * 该函数用于向指定的D3 API发送图像生成请求,通过提供模型、提示和图像大小等参数来定制生成的图像 + * + * @param url {string} - API的基础URL字符串,指定了发送请求的目标地址 + * @param key {string} - API密钥字符串,用于认证请求,使函数能够访问API服务 + * @param body {object} - 包含模型、提示和图像大小的对象 + * - model {string} 模型字段,指定用于生成图像的AI模型 + * - prompt {string} 提示字段,提供生成图像的文本描述或提示 + * - size {string} 大小字段,定义生成图像的尺寸 + * @returns 无返回值,但函数内部可能包含对API的异步请求操作 + */ + async D3APIImageRequest(url: string, key: string, body: { model: string; prompt: string; size: string; }) { + let response = await axios.post(url, { ...body, n: 1 }, { + headers: { + Authorization: 'Bearer ' + key + } + }) + if (response.data && response.data.data && response.data.data.length > 0) { + return response.data.data[0].url + } else { + return undefined + } + } - //#region 软件相关的方法 + /** + * 异步生成D3图像 + * @param task 任务模型 + * @returns Promise + */ + async D3ImageGenerate(task: TaskModal.Task): Promise { + let sdSetting = undefined + try { + console.log("D3ImageGenerate", task) + let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId); + let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskDetail.bookTaskId); + let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId); + sdSetting = await this.GetSDSetting() + await this.gptService.RefreshGptSetting(); + + let prompt = bookTaskDetail.prompt; + let requestUrl = this.gptService.gptUrl + let uil = new URL(requestUrl); + let url = `${uil.protocol}//${uil.hostname}` + "/v1/images/generations" + + if (!isEmpty(sdSetting.webui.prompt)) { + prompt = sdSetting.webui.prompt + ', ' + prompt + } + let size = `${sdSetting.webui.width}x${sdSetting.webui.height}` + // 这边需要判断下size是不是合法的 + if (!['1024x1024', '1024x1792', '1792x1024'].includes(size)) { + throw new Error('D3 生成图片的尺寸不合法,只支持 1024x1024、1024x1792、1792x1024') + } + let model = 'dall-e-3' + // 一次请求生成一张 多个请求 + + 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); + + let outImagePath = '' + let subImagePath = [] + + let batchSize = sdSetting.setting.batch_size; 7 + for (let i = 0; i < batchSize; i++) { + const element = batchSize; + let imageUrl = await this.D3APIImageRequest(url, this.gptService.gptApiKey, { + model: model, + prompt: prompt, + size: size + }) + // 这边开始处理返回的数据 + if (isEmpty(imageUrl)) { + throw new Error('D3 生图返回的图片地址为空') + } + // 下载指定的文件 + let base64 = await GetImageBase64(imageUrl) + // 将base64 写出 + let imgPath = path.join(SdOriginalImage, `${bookTaskDetail.name}_${new Date().getTime()}_${i}.png`) + await Base64ToFile(base64, imgPath); + + // 写出去 + if (bookTask.name == 'output_00001') { + // 复制一个到input + let inputImgPath = path.join(inputFolder, `${bookTaskDetail.name}.png`) + await CopyFileOrFolder(imgPath, inputImgPath) + } + if (i == 0) { + // 复制到对应的文件夹里面 + let outPath = path.join(outputFolder, `${bookTaskDetail.name}.png`) + await CopyFileOrFolder(imgPath, outPath) + outImagePath = outPath + } + subImagePath.push(imgPath) + } + + // 结束 开始返回 + // 修改数据库 + await this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetail.id, { + outImagePath: path.relative(define.project_path, outImagePath), + subImagePath: subImagePath.map((item) => path.relative(define.project_path, item)) + }) + await this.bookServiceBasic.UpdateTaskStatus({ + id: task.id, + status: BookBackTaskStatus.DONE + }); + let resp = { + mjApiUrl: url, + progress: 100, + category: MJImageType.FLUX_API, + imageClick: subImagePath.join(','), + imageShow: subImagePath.join(','), + messageId: "", + action: MJAction.IMAGINE, + status: "success", + subImagePath: subImagePath, + outImagePath: outImagePath, + message: "D3 生成图片成功" + } + await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, resp) + global.newWindow[0].win.webContents.send(task.messageName, { + code: 1, + message: "D3 生成图片成功", + data: { + ...resp, + id: bookTaskDetail.id + } + }) + + } catch (error) { + let errorMsg = "D3 生成图片失败,错误信息如下:" + error.toString() + await this.bookServiceBasic.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId, { + mjApiUrl: "", + progress: 0, + category: MJImageType.FLUX_API, + 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 - - //endregion -} \ No newline at end of file diff --git a/src/main/Service/presetService.ts b/src/main/Service/presetService.ts new file mode 100644 index 0000000..edf6234 --- /dev/null +++ b/src/main/Service/presetService.ts @@ -0,0 +1,129 @@ +import { errorMessage, successMessage } from "../Public/generalTools"; +import { define } from '../../define/define' +import { CheckFileOrDirExist } from "../../define/Tools/file"; +import fs from 'fs' +import { ValidateJson } from "../../define/Tools/validate"; +import { GeneralResponse } from "../../model/generalResponse"; +import { PresetModel } from "../../model/preset"; + +export class PresetService { + constructor() { } + + //TODO 这边现在都是基于文件的,后面需要改为基于数据库的 + + //#region 人物预设 + /** + *获取所有的人物显示的预设(只要label和id) + */ + async GetCharacterPreset(): Promise { + try { + let result = [] + let tagPath = define.tag_setting + if (!(await CheckFileOrDirExist(tagPath))) { + return successMessage(result, '获取人物预设列表成功', "PresetService_GetCharacterPreset") + } + let tagString = await fs.promises.readFile(tagPath, 'utf-8'); + if (!ValidateJson(tagString)) { + return successMessage(result, '获取人物预设列表成功', "PresetService_GetCharacterPreset") + } + let tags = JSON.parse(tagString) + let characterTags = tags.character_tags + for (let i = 0; characterTags && i < characterTags.length; i++) { + let element = characterTags[i] + if (element.isShow) { + result.push({ + label: element.label, + id: element.key + }) + } + } + return successMessage(result, '获取人物预设列表成功', "PresetService_GetCharacterPreset") + } catch (error) { + return errorMessage("获取人物预设失败,失败信息如下: " + error.toString(), "PresetService_GetCharacterPreset") + } + } + + /** + * 获取指定ID的人物预设的详细信息 + * @param id 人物预设的ID + */ + async GetCharacterPresetDetailById(id: string): Promise { + try { + let result = undefined as PresetModel.Preset + let tagPath = define.tag_setting + if (!(await CheckFileOrDirExist(tagPath))) { + return successMessage(result, '获取人物预设详细数据成功', "PresetService_GetCharacterPreset") + } + let tagString = await fs.promises.readFile(tagPath, 'utf-8'); + if (!ValidateJson(tagString)) { + return successMessage(result, '获取人物预设列表成功', "PresetService_GetCharacterPreset") + } + let tags = JSON.parse(tagString) + let characterTags = tags.character_tags + result = characterTags.find((item: any) => item.key === id) + return successMessage(result, '获取人物预设详细数据成功', "PresetService_GetCharacterPreset") + } catch (error) { + return errorMessage("获取人物预设详细信息失败,失败信息如下: " + error.toString(), "PresetService_GetCharacterPresetDetailById") + } + } + + //#endregion + + + //#region 场景预设 + + // 获取所有的场景预设(label和ID) + async GetScenePreset(): Promise { + try { + let result = [] + let tagPath = define.tag_setting + if (!(await CheckFileOrDirExist(tagPath))) { + return successMessage(result, '获取场景预设列表成功', "PresetService_GetScenePreset") + } + let tagString = await fs.promises.readFile(tagPath, 'utf-8'); + if (!ValidateJson(tagString)) { + return successMessage(result, '获取场景预设列表成功', "PresetService_GetScenePreset") + } + let tags = JSON.parse(tagString) + let sceneTags = tags.scene_tags + for (let i = 0; sceneTags && i < sceneTags.length; i++) { + let element = sceneTags[i] + if (element.isShow) { + result.push({ + label: element.label, + id: element.key + }) + } + } + return successMessage(result, '获取场景预设列表成功', "PresetService_GetScenePreset") + } catch (error) { + return errorMessage("获取场景预设失败,失败信息如下: " + error.toString(), "PresetService_GetScenePreset") + } + } + + /** + * 通过获取场景预设的详细信息 + * @param id 场景预设的ID + */ + async GetScenePresetDetailById(id: string): Promise { + try { + let result = undefined as PresetModel.Preset + let tagPath = define.tag_setting + if (!(await CheckFileOrDirExist(tagPath))) { + return successMessage(result, '获取场景预设详细数据成功', "PresetService_GetCharacterPreset") + } + let tagString = await fs.promises.readFile(tagPath, 'utf-8'); + if (!ValidateJson(tagString)) { + return successMessage(result, '获取场景预设详细数据成功', "PresetService_GetCharacterPreset") + } + let tags = JSON.parse(tagString) + let sceneTags = tags.scene_tags + result = sceneTags.find((item: any) => item.key === id) + return successMessage(result, '获取人物预设详细数据成功', "PresetService_GetCharacterPreset") + } catch (error) { + return errorMessage("获取场景预设详细信息失败,失败信息如下: " + error.toString(), "PresetService_GetScenePresetDetailById") + } + } + + //#endregion +} \ No newline at end of file diff --git a/src/main/Service/taskManage.ts b/src/main/Service/taskManage.ts index cf97711..22d4adf 100644 --- a/src/main/Service/taskManage.ts +++ b/src/main/Service/taskManage.ts @@ -7,7 +7,12 @@ import { ReverseBook } from './Book/ReverseBook' import { GeneralResponse } from '../../model/generalResponse' import { DEFINE_STRING } from '../../define/define_string' import { MJOpt } from './MJ/mj' +import { SDOpt } from './SD/sd' +import { D3Opt } from './d3' +import { FluxOpt } from './Flux/flux' import { AsyncQueue } from '../../main/quene' +import { SoftWareServiceBasic } from './ServiceBasic/softwareServiceBasic' +import { MJSetting } from '../../model/Setting/mjSetting' export class TaskManager { isExecuting: boolean = false; @@ -18,12 +23,17 @@ export class TaskManager { softwareService!: SoftwareService; bookBackTaskListService!: BookBackTaskListService; eventListeners: Record = {}; + softWareServiceBasic: SoftWareServiceBasic + mjSetting: MJSetting.MjSetting spaceTime: number = 5000; count = 0; isListening = false; intervalId: any; // 用于存储 setInterval 的 ID mjOpt: MJOpt + sdOpt: SDOpt + d3Opt: D3Opt + fluxOpt: FluxOpt constructor() { this.isExecuting = false; @@ -32,6 +42,10 @@ export class TaskManager { this.basicReverse = new BasicReverse(); this.reverseBook = new ReverseBook(); this.mjOpt = new MJOpt(); + this.sdOpt = new SDOpt(); + this.d3Opt = new D3Opt() + this.softWareServiceBasic = new SoftWareServiceBasic(); + this.fluxOpt = new FluxOpt() } async InitService(getMJsetting = false) { @@ -42,7 +56,8 @@ export class TaskManager { this.bookBackTaskListService = await BookBackTaskListService.getInstance(); } if (getMJsetting) { - await this.mjOpt.InitService(); + // 初始化MJ设置 + this.mjSetting = await this.softWareServiceBasic.GetMjSetting() } } @@ -126,8 +141,8 @@ export class TaskManager { if (element.type == BookBackTaskType.MJ_IMAGE || element.type == BookBackTaskType.MJ_REVERSE) { // 判断任务数量是不是又修改 let taskNumber = global.mjQueue.getConcurrencyLimit(); - if (taskNumber != this.mjOpt.mjSetting.taskCount) { - global.mjQueue.concurrencyLimit = this.mjOpt.mjSetting.taskCount // 重置并发执行的数量 + if (taskNumber != this.mjSetting.taskCount) { + global.mjQueue.concurrencyLimit = this.mjSetting.taskCount // 重置并发执行的数量 } if (global.mjQueue.getWaitingQueue() > 10) { @@ -216,6 +231,68 @@ export class TaskManager { }, `${batch}_${task.id}`, batch) } + /** + * 将MJ生图生成任务添加内存任务中 + * @param task + */ + async AddImageMJImage(task: TaskModal.Task) { + // 判断是不是MJ的任务 + let batch = DEFINE_STRING.MJ.MJ_IMAGE; + global.mjQueue.enqueue(async () => { + await this.mjOpt.MJImagine(task); + }, `${batch}_${task.id}`, batch, `${batch}_${task.id}_${new Date().getTime()}`); + } + + + /** + * 将SD生图任务添加到内存任务中 + * @param task + */ + async AddSDImage(task: TaskModal.Task) { + let batch = DEFINE_STRING.SD.TXT2IMG + global.requestQuene.enqueue(async () => { + await this.sdOpt.SDImageGenerate(task); + }, `${batch}_${task.id}`, batch, `${batch}_${task.id}_${new Date().getTime()}`) + } + + /** + * 异步添加D3图像生成任务 + * + * 此方法接受一个任务对象,将基于该任务生成D3图像 + * 它使用全局请求队列来管理任务,确保并发处理的效率和稳定性 + * + * @param task 任务对象,包含任务的具体信息和标识 + */ + async AddD3Image(task: TaskModal.Task) { + let batch = task.messageName; + global.requestQuene.enqueue(async () => { + await this.d3Opt.D3ImageGenerate(task); + }, `${batch}_${task.id}`, batch, `${batch}_${task.id}_${new Date().getTime()}`) + } + + + /** + * 添加 flux forge 任务到内存队列中 + * @param task + */ + async AddFluxForgeImage(task: TaskModal.Task) { + let batch = task.messageName + global.requestQuene.enqueue(async () => { + await this.fluxOpt.FluxForgeImage(task); + }, `${batch}_${task.id}`, batch, `${batch}_${task.id}_${new Date().getTime()}`) + } + + /** + * 添加 FLUX api 到内存队列中 + * @param task + */ + async AddFluxAPIImage(task: TaskModal.Task) { + let batch = task.messageName; + global.requestQuene.enqueue(async () => { + await this.fluxOpt.FluxAPIImage(task); + }, `${batch}_${task.id}`, batch, `${batch}_${task.id}_${new Date().getTime()}`) + } + /** * 添加任务到内存队列中,分流 * @param task 任务相关 @@ -243,10 +320,21 @@ export class TaskManager { case BookBackTaskType.MJ_REVERSE || BookBackTaskType.SD_REVERSE: this.AddSingleReversePrompt(task); break; - + case BookBackTaskType.FLUX_FORGE_IMAGE: + this.AddFluxForgeImage(task); + break; + case BookBackTaskType.FLUX_API_IMAGE: + this.AddFluxAPIImage(task); + break; case BookBackTaskType.MJ_IMAGE: this.AddImageMJImage(task); break; + case BookBackTaskType.SD_IMAGE: + this.AddSDImage(task); + break; + case BookBackTaskType.D3_IMAGE: + this.AddD3Image(task); + break; default: throw new Error('未知的任务类型'); } @@ -276,17 +364,6 @@ export class TaskManager { } } - /** - * 将MJ生图生成 - * @param task - */ - async AddImageMJImage(task: TaskModal.Task) { - // 判断是不是MJ的任务 - let batch = DEFINE_STRING.MJ.MJ_IMAGE; - global.mjQueue.enqueue(async () => { - await this.mjOpt.MJImagine(task); - }, `${batch}_${task.id}`, batch, `${batch}_${task.id}_${new Date().getTime()}`); - } //#endregion diff --git a/src/main/Service/writing.js b/src/main/Service/writing.js index d87c9cc..646e474 100644 --- a/src/main/Service/writing.js +++ b/src/main/Service/writing.js @@ -93,15 +93,20 @@ export class Writing extends ServiceBase { 'Writing_ActionStart' ) } else { - // 处理不同类型的错误消息 - if (setting.gptAI == 'laiapi') { - throw new Error(GetRixApiErrorResponse(dataRes.data)) - } else if (setting.gptAI == 'kimi') { - throw new Error(GetKimiErrorResponse(dataRes.data)) - } else if (setting.gptAI == 'doubao') { - throw new Error(GetDoubaoErrorResponse(dataRes.data)) + // 系统报错 + if (dataRes.code == 5000) { + throw new Error('系统错误,错误信息如下:' + dataRes.message) } else { - throw new Error(dataRes.data) + // 处理不同类型的错误消息 + if (setting.gptAI == 'laiapi') { + throw new Error(GetRixApiErrorResponse(dataRes.data)) + } else if (setting.gptAI == 'kimi') { + throw new Error(GetKimiErrorResponse(dataRes.data)) + } else if (setting.gptAI == 'doubao') { + throw new Error(GetDoubaoErrorResponse(dataRes.data)) + } else { + throw new Error(dataRes.data) + } } } } catch (error) { diff --git a/src/main/func.js b/src/main/func.js index e16cc4d..0a4209d 100644 --- a/src/main/func.js +++ b/src/main/func.js @@ -17,6 +17,7 @@ import { PublicMethod } from "./Public/publicMethod" import { ImageStyleDefine } from "../define/iamgeStyleDefine"; let tools = new Tools(); let pm = new PublicMethod(global); +import { FLxuAPIImageType } from '../define/enum/image' /** * 获取对应的轨道 @@ -641,7 +642,15 @@ async function SaveSDConfig(value) { sd_config.webui.height = value.height ? value.height : sd_config.webui.height; sd_config.webui.cfg_scale = value.cfg_scale ? value.cfg_scale : sd_config.webui.cfg_scale; sd_config.webui.adetailer = value.hasOwnProperty("adetailer") ? value.adetailer : sd_config.webui.adetailer; - + + if(!sd_config.flux){ + let model = { + model : value.flux_model ? value.flux_model : FLxuAPIImageType.FLUX + } + sd_config.flux = model + }else{ + sd_config.flux.model = value.flux_model ? value.flux_model : FLxuAPIImageType.FLUX; + } await fspromises.writeFile(define.sd_setting, JSON.stringify(sd_config)); return { code: 1, diff --git a/src/main/index.js b/src/main/index.js index 4c581df..9eaa4a8 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -7,7 +7,7 @@ import { app, shell, BrowserWindow, ipcMain, dialog, nativeTheme, session } from import path, { join } from 'path' import { electronApp, optimizer, is } from '@electron-toolkit/utils' import icon from '../../resources/icon.ico?asset' -import { define } from '../define/define.js' +import { define } from '../define/define' import { func } from './func.js' import { AsyncQueue } from './quene' import { DEFINE_STRING } from '../define/define_string' @@ -17,6 +17,7 @@ import { Setting } from './setting/setting.js' import { has, isEmpty } from 'lodash' import { AutoSync } from './setting/autoSync.js' import { TaskManager } from './Service/taskManage' +import { SoftWareServiceBasic } from './Service/ServiceBasic/softwareServiceBasic' // ipc import { DiscordIpc, RemoveDiscordIpc } from './IPCEvent/discordIpc.js' @@ -26,6 +27,7 @@ import { RegisterIpc } from './IPCEvent/index.js' let tools = new Tools() let imageGenerate = new ImageGenerate(global) let setting = new Setting(global) +let softWareServiceBasic = new SoftWareServiceBasic() async function InitData(gl) { let res = await setting.getSettingDafultData() @@ -113,6 +115,9 @@ async function createWindow(hash = 'ShowMessage', data, url = null) { let window_wh_bm = mainWindow.getBounds() // 记录到文件中 await setting.ModifySampleSetting(JSON.stringify({ window_wh_bm: window_wh_bm })) + await softWareServiceBasic.UpdateSoftware({ + window_wh_bm: JSON.stringify(window_wh_bm) + }) } }) diff --git a/src/main/quene.js b/src/main/quene.js index d334325..12afb7b 100644 --- a/src/main/quene.js +++ b/src/main/quene.js @@ -141,7 +141,8 @@ export class AsyncQueue { if ( [ DEFINE_STRING.QUEUE_BATCH.SD_BACKSTEP_GENERATE_IMAGE, - DEFINE_STRING.QUEUE_BATCH.SD_ORIGINAL_GENERATE_IMAGE + DEFINE_STRING.QUEUE_BATCH.SD_ORIGINAL_GENERATE_IMAGE, + DEFINE_STRING.SD.TXT2IMG ].includes(batchId) ) { retryTask() diff --git a/src/main/setting/autoSync.js b/src/main/setting/autoSync.js index 5abb84f..89b779a 100644 --- a/src/main/setting/autoSync.js +++ b/src/main/setting/autoSync.js @@ -46,10 +46,8 @@ async function GlobalAutoSync() { if (softWareData.globalSetting == null || softWareData.globalSetting == '') { let initGlobalConfig = await fspromises.readFile(initConifgPath, 'utf-8') softWareData.globalSetting = initGlobalConfig - let updateSfotwareRes = _softwareService.UpdateSoftware(softWareData) - if (updateSfotwareRes.code == 1) { - global.logger.info('AutoSunc_GlobalAutoSync', '初始化全局设置成功') - } + _softwareService.UpdateSoftware(softWareData) + global.logger.info('AutoSunc_GlobalAutoSync', '初始化全局设置成功') } } } catch (error) { diff --git a/src/main/setting/basicSetting.js b/src/main/setting/basicSetting.ts similarity index 67% rename from src/main/setting/basicSetting.js rename to src/main/setting/basicSetting.ts index 3b10246..6555da5 100644 --- a/src/main/setting/basicSetting.js +++ b/src/main/setting/basicSetting.ts @@ -1,15 +1,12 @@ -import SoftwareService from '../../define/db/service/SoftWare/softwareService' import { ComponentSize } from '../../define/enum/softwareEnum' import { errorMessage, successMessage } from '../Public/generalTools' +import { SoftWareServiceBasic } from '../Service/ServiceBasic/softwareServiceBasic' export class BasicSetting { - constructor() {} - - // 初始化数据 - async init() { - this.setting = await SoftwareService.getInstance() + softWareServiceBasic: SoftWareServiceBasic + constructor() { + this.softWareServiceBasic = new SoftWareServiceBasic() } - /** * 获取修改尺寸的下拉菜单的尺寸 * @returns @@ -46,9 +43,8 @@ export class BasicSetting { // 获取基础配置信息 async GetSoftwareSetting() { try { - await this.init() - let res = this.setting.GetSoftwareData() - return res + let res = await this.softWareServiceBasic.GetSoftwareData() + return successMessage(res, '获取软件配置信息成功', 'BasicSetting_GetSoftwareSetting') } catch (error) { return errorMessage(error.message, 'BasicSetting_GetSoftwareSetting') } @@ -60,11 +56,10 @@ export class BasicSetting { * @returns */ - async SaveSoftWareSetting(paramms) { + async SaveSoftWareSetting(paramms: SoftwareSettingModel.SoftwareSetting) { try { - await this.init() - let res = this.setting.UpdateSoftware(paramms) - return res + await this.softWareServiceBasic.UpdateSoftware(paramms) + return successMessage(null, '保存软件设置成功', 'BasicSetting_SaveSoftWareSetting') } catch (error) { return errorMessage(error.message, 'BasicSetting_SaveSoftWareSetting') } diff --git a/src/main/setting/setting.js b/src/main/setting/setting.js index 0d293f3..6a10e20 100644 --- a/src/main/setting/setting.js +++ b/src/main/setting/setting.js @@ -10,12 +10,15 @@ import { DEFINE_STRING } from '../../define/define_string' import { TagDefine } from '../../define/tagDefine' import { errorMessage } from '../Public/generalTools' import { TaskManager } from '../Service/taskManage' +import { SoftWareServiceBasic } from '../Service/ServiceBasic/softwareServiceBasic' +import { FLxuAPIImageType } from '../../define/enum/image' let tagDefine = new TagDefine(global) export class Setting { constructor(global) { this.global = global this.tools = new Tools() + this.softWareServiceBasic = new SoftWareServiceBasic() } //#region 剪映设置 @@ -172,7 +175,8 @@ export class Setting { cfg_scale: sd_config.webui.cfg_scale, sd_model: sd_config.sd_model, lora: sd_config.lora, - sampler: sd_config.sampler + sampler: sd_config.sampler, + flux_model: sd_config.flux?.model ? sd_config.flux.model : FLxuAPIImageType.FLUX } } } catch (error) { diff --git a/src/model/Setting/softwareSetting.d.ts b/src/model/Setting/softwareSetting.d.ts index 3aef767..c7b30d7 100644 --- a/src/model/Setting/softwareSetting.d.ts +++ b/src/model/Setting/softwareSetting.d.ts @@ -14,4 +14,30 @@ declare namespace SoftwareSettingModel { translationSetting?: string subtitleSetting?: string } + + type GlobalSetting = { + draft_path: string = undefined // 草稿路径 + project_path: string = undefined // 项目路径 + project_name: string = undefined // 项目名称 + gpt_business: string = undefined // GPT服务商ID + gpt_model: string = undefined // GPT模型 + task_number: number = undefined // 任务数量 + theme: string = undefined // 主题 + gpt_auto_inference: string = undefined // GPT自动推理模式 + webui_api_url: string = undefined // webui地址 + gpt_count: number = 10 // GPT上下文数量 + customize_gpt_prompt: string = undefined // 自定义GPT提示ID + character_select_model: "drop" | 'tag' = 'drop' // 人物选择模式, + image_generate_category: 'sd' | 'mj' | 'd3' | 'fulx-forge' | 'flux-api' = 'mj' // 生图方式 + window_wh_bm_remember: boolean = false// 记住窗口大小 + window_wh_bm: { + x: number = 0, // 窗口x坐标 + y: number = 0, // 窗口y坐标 + width: number = 800, // 窗口宽度 + height: number = 600, // 窗口高度 + }, + space_image: string = undefined // 空白图片 + gpt_key: string = undefined // GPT KEY, + laiApiSelect: string = undefined // LaiAPI选择 + } } \ No newline at end of file diff --git a/src/model/book.d.ts b/src/model/book.d.ts index 092bc50..77d7349 100644 --- a/src/model/book.d.ts +++ b/src/model/book.d.ts @@ -1,4 +1,4 @@ -import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, TaskExecuteType, BookRepalceDataType } from "../define/enum/bookEnum" +import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, TaskExecuteType, BookRepalceDataType, BookImageCategory } from "../define/enum/bookEnum" import { MJAction } from "../define/enum/bookEnum" import { MJImageType } from "../define/enum/mjEnum" @@ -154,6 +154,7 @@ declare namespace Book { timeLimit?: string // 事件实现(0 -- 3000) subValue?: string // 包含的字幕数据 characterTags?: string[] // 角色标签 + sceneTags?: string[] // 场景标签 gptPrompt?: string // GPT提示词 mjMessage?: MJMessage // MJ消息 outImagePath?: string // 输出图片地址 diff --git a/src/model/generalResponse.d.ts b/src/model/generalResponse.d.ts index 48a17cf..d2be0f8 100644 --- a/src/model/generalResponse.d.ts +++ b/src/model/generalResponse.d.ts @@ -30,7 +30,7 @@ declare namespace GeneralResponse { type MessageResponse = { code: number, id: string, // 消息的唯一标识(可以和前端相关联) - type: ResponseMessageType, + type?: ResponseMessageType, dialogType?: DialogType = DialogType.MESSAGE, message?: string, data?: MJ.MJResponseToFront | Buffer | string | TranslateModel.TranslateResponseMessageModel | ProgressResponse | SubtitleProgressResponse diff --git a/src/model/mj.d.ts b/src/model/mj.d.ts index da8f322..dec4721 100644 --- a/src/model/mj.d.ts +++ b/src/model/mj.d.ts @@ -14,15 +14,15 @@ declare namespace MJ { type: MJRespoonseType, // 返回前端的操作类型 mjType: MJAction, // 执行MJ的类型 category: MJImageType, // 调用MJ分类 - message_id?: string, // 返回消息的id,就是任务ID - image_click?: string, // 预览的图片(再使用浏览器模式的时候需要,其他都是null) - image_show?: string, // 实际下载的图片的地址 - image_path?: string, //实际下载的图片的地址 + messageId?: string, // 返回消息的id,就是任务ID + imageClick?: string, // 预览的图片(再使用浏览器模式的时候需要,其他都是null) + imageShow?: string, // 实际下载的图片的地址 + imagePath?: string, //实际下载的图片的地址 prompt?: string, // 提示词消息 progress: number, // 实现的进程 message?: string // 消息 status: string - mj_api_url?: string // 请求的MJ地址 + mjApiUrl?: string // 请求的MJ地址 outImagePath?: string // 输出的图片地址 subImagePath?: string[] // 子图片地址 } diff --git a/src/model/preset.d.ts b/src/model/preset.d.ts new file mode 100644 index 0000000..e4b1934 --- /dev/null +++ b/src/model/preset.d.ts @@ -0,0 +1,31 @@ +import { PresetType } from "../define/enum/preset" + +declare namespace PresetModel { + /** + * 完整的预设的数据Model + */ + type Preset = { + id?: string + label?: string + type?: TAGType + showImage?: string + prompt?: string + chinesePrompt?: string + imageUrl?: string + srefSw?: number + crefCw?: number + lora?: string + loraWeight?: number + isShow?: boolean + children?: string + } + + /** + * 前端界面相关的数据model + */ + type PresetList = { + id: string + label: string + checked: boolean = false + } +} \ No newline at end of file diff --git a/src/model/subtitle.d.ts b/src/model/subtitle.d.ts index 4af06fc..839e4aa 100644 --- a/src/model/subtitle.d.ts +++ b/src/model/subtitle.d.ts @@ -1,6 +1,11 @@ import { GetSubtitleType } from "../define/enum/waterMarkAndSubtitle" declare namespace SubtitleModel { + + //#region 提取视频文案相关 + /** + * 提取文案设置 + */ type subtitleSettingModel = { selectModel: GetSubtitleType, laiWhisper: { @@ -10,4 +15,35 @@ declare namespace SubtitleModel { prompt: string } } + + //#endregion + + //#region 小说文案相关s + + /** + * 文案数据包含的字幕数据 + */ + type CopywritingSubValue = { + end_time: number, + start_time: number, + id: string, + srt_value: string + } + + /** + * 保存文案数据类型 + */ + type SaveCopywritingData = { + id: string, + lastId: string, + no: number, + after_gpt: string, + start_time: number, + end_time: number, + subValue: CopywritingSubValue[] + timeLimit: string, + word: string + } + + //#endregion } \ No newline at end of file diff --git a/src/model/task.d.ts b/src/model/task.d.ts index 79e151c..9d73c32 100644 --- a/src/model/task.d.ts +++ b/src/model/task.d.ts @@ -1,18 +1,19 @@ declare namespace TaskModal { type Task = { - id: string - bookId: string - bookTaskId: string - bookTaskDetailId: string - name: string // 任务名称,小说名+批次名+分镜名 - type: BookBackTaskType - status: BookBackTaskStatus - errorMessage: string | null - executeType: TaskExecuteType // 任务执行类型,手动还是自动 - createTime: Date - updateTime: Date - startTime: number - endTime: number + id?: string + bookId?: string + bookTaskId?: string + bookTaskDetailId?: string + name?: string // 任务名称,小说名+批次名+分镜名 + type?: BookBackTaskType + status?: BookBackTaskStatus + errorMessage?: string | null + executeType?: TaskExecuteType // 任务执行类型,手动还是自动 + createTime?: Date + updateTime?: Date + startTime?: number + endTime?: number, + messageName?: string } } \ No newline at end of file diff --git a/src/model/translate.d.ts b/src/model/translate.d.ts index df38472..31b3bb7 100644 --- a/src/model/translate.d.ts +++ b/src/model/translate.d.ts @@ -17,6 +17,7 @@ declare namespace TranslateModel { reversePromptId?: string // 反推提示词ID windowId?: number // 窗口ID type: TranslateType // 翻译类型 + responseMessgeName?: string // 返回的消息名称(用作自定义) } type TranslateResponseMessageModel = { diff --git a/src/preload/book.ts b/src/preload/book.ts index 9fa4a15..5be2918 100644 --- a/src/preload/book.ts +++ b/src/preload/book.ts @@ -1,6 +1,8 @@ import { ipcRenderer } from 'electron' import { DEFINE_STRING } from '../define/define_string' import { Book } from '../model/book' +import { SubtitleModel } from '../model/subtitle' +import { BookType, OperateBookType } from '../define/enum/bookEnum' const book = { // 获取小说操作类型(原创/SD反推/MJ反推) @@ -84,7 +86,7 @@ const book = { //#region 文案相关信息 - // 获取文案信息 + // 反推识别文案的方法 GetCopywriting: async (bookId, bookTaskId, operateBookType, coverData) => await ipcRenderer.invoke( DEFINE_STRING.BOOK.GET_COPYWRITING, @@ -94,6 +96,11 @@ const book = { coverData ), + // 保存导入的文案数据(文案和字幕对齐后的) + SaveCopywriting: async (bookTaskId: string, copywritingData: SubtitleModel.SaveCopywritingData, operateBookType: OperateBookType) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.SAVE_COPYWRITING, bookTaskId, copywritingData, operateBookType), + + // 将文案信息导出,方便修改 ExportCopywriting: async (bookTaskId) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.EXPORT_COPYWRITING, bookTaskId), @@ -121,10 +128,6 @@ const book = { MergePrompt: async (id, type, operateBookType) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.MERGE_PROMPT, id, type, operateBookType), - //#endregion - - //#region 一键反推的单个任务 - // 添加单句反推的 AddReversePrompt: async (bookTaskDetailIds, operateBookType, type) => await ipcRenderer.invoke( @@ -143,6 +146,30 @@ const book = { index ), + // 删除掉所有的反推和GPT提示词数据 + ResetGptReverseData: async (id: string, operateBookType: OperateBookType, type: BookType) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_GPT_REVERSE_DATA, id, operateBookType, type), + + // 删除所有的合并提示词数据 + ResetMergePromptData: async (id, operateBookType) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.REMOVE_MERGE_PROMPT_DATA, id, operateBookType), + + // 原创推理所有的提示词 + OriginalGetPrompt: async (id: string, operateBookType: OperateBookType, coverData: boolean) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.ORIGINAL_GPT_PROMPT, id, operateBookType, coverData), + + //#endregion + + //#region 图片相关 + + // 删除所有的生成图片 + ResetGenerateImage: async (id: string, operateBookType: OperateBookType, coverData: boolean) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.REMOVE_GENERATE_IMAGE, id, operateBookType, coverData), + + //#endregion + + //#region 一键反推的单个任务 + // 单个重选反推的提示词 SingleReverseToGptPrompt: async (bookTaskDetailId, index) => await ipcRenderer.invoke( @@ -151,17 +178,6 @@ const book = { index ), - // 删除掉所有的反推和GPT提示词数据 - ResetGptReverseData: async (id, operateBookType, type) => - await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_GPT_REVERSE_DATA, id, operateBookType, type), - - // 删除所有的合并提示词数据 - ResetMergePromptData: async (id, operateBookType) => - await ipcRenderer.invoke(DEFINE_STRING.BOOK.REMOVE_MERGE_PROMPT_DATA, id, operateBookType), - - // 删除所有的生成图片 - ResetGenerateImage: async (id, operateBookType) => - await ipcRenderer.invoke(DEFINE_STRING.BOOK.REMOVE_GENERATE_IMAGE, id, operateBookType), //#endregion diff --git a/src/preload/db.ts b/src/preload/db.ts index 1ddb27d..d87db87 100644 --- a/src/preload/db.ts +++ b/src/preload/db.ts @@ -18,8 +18,18 @@ const db = { // 修改小说详细任务的数据 UpdateBookTaskDetailData: async (bookTaskDetailId: string, data: Book.SelectBookTaskDetail) => { return await ipcRenderer.invoke(DEFINE_STRING.DB.UPDATE_BOOK_TASK_DETAIL_DATA, bookTaskDetailId, data) - } + }, //endregion + + //#region 软件设置的修改 + + // 修改软件通用设置 + UpdateSoftwareSetting: async (software: SoftwareSettingModel.SoftwareSetting) => { + return await ipcRenderer.invoke(DEFINE_STRING.DB.UPDATE_SOFTWARE_SETTING, software) + }, } + +//#endregion + export { db } diff --git a/src/preload/index.js b/src/preload/index.js index bd0db60..167dbd7 100644 --- a/src/preload/index.js +++ b/src/preload/index.js @@ -3,7 +3,7 @@ import { electronAPI } from '@electron-toolkit/preload' import { DEFINE_STRING } from '../define/define_string' import { discord } from './discord.js' import { mj } from './mj.js' -import { sd } from './sd.js' +import { sd } from './sd' import { img } from './img.js' import { system } from './system.js' import { setting } from './setting.js' @@ -14,6 +14,8 @@ import { write } from './write.js' import { gpt } from './gpt.js' import { db } from './db' import { translate } from './translate.js' +import { preset } from './preset' +import { task } from './task' // Custom APIs for renderer let events = [] @@ -451,6 +453,9 @@ const api = { showGlobalNotificationDialog: (value) => ipcRenderer.send(DEFINE_STRING.SHOW_GLOBAL_MAIN_NOTIFICATION, value), + // 打开message + showGlobalMessage: (value) => ipcRenderer.send(DEFINE_STRING.SHOW_GLOBAL_MESSAGE, value), + // 知道文件地址,获取文件base64编码 GetFileBase64: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.GET_FILE_BASE64, value)), @@ -480,6 +485,8 @@ if (process.contextIsolated) { contextBridge.exposeInMainWorld('gpt', gpt) contextBridge.exposeInMainWorld('translate', translate) contextBridge.exposeInMainWorld('db', db) + contextBridge.exposeInMainWorld('preset', preset) + contextBridge.exposeInMainWorld('task', task) contextBridge.exposeInMainWorld('darkMode', { toggle: (value) => ipcRenderer.invoke('dark-mode:toggle', value) }) @@ -501,5 +508,7 @@ if (process.contextIsolated) { window.write = write window.gpt = gpt window.db = db + window.preset = preset + window.task = task window.translate = translate } diff --git a/src/preload/mj.js b/src/preload/mj.js index c2f1096..7edf330 100644 --- a/src/preload/mj.js +++ b/src/preload/mj.js @@ -70,8 +70,14 @@ const mj = { callback(await ipcRenderer.invoke(DEFINE_STRING.MJ.AUTO_MATCH_USER, value)), // 单个出图 - AddMJGenerateImageTask: async (id, operateBookType) => - await ipcRenderer.invoke(DEFINE_STRING.MJ.ADD_MJ_GENADD_MJ_GENERATE_IMAGE_TASK, id, operateBookType) + AddMJGenerateImageTask: async (id, operateBookType, responseMessageName, coverData) => + await ipcRenderer.invoke( + DEFINE_STRING.MJ.ADD_MJ_GENADD_MJ_GENERATE_IMAGE_TASK, + id, + operateBookType, + responseMessageName, + coverData + ) } export { mj } diff --git a/src/preload/preset.ts b/src/preload/preset.ts new file mode 100644 index 0000000..f40cb76 --- /dev/null +++ b/src/preload/preset.ts @@ -0,0 +1,16 @@ +import { ipcRenderer } from 'electron' +import { DEFINE_STRING } from '../define/define_string' + +const preset = { + /** + * 获取人物预设的列表 + * @returns + */ + GetCharacterPreset: async () => + await ipcRenderer.invoke(DEFINE_STRING.PRESET.GET_CHARACTER_PRESET), + + GetScenePreset: async () => + await ipcRenderer.invoke(DEFINE_STRING.PRESET.GET_SCENE_PRESET) +} + +export { preset } diff --git a/src/preload/sd.js b/src/preload/sd.ts similarity index 88% rename from src/preload/sd.js rename to src/preload/sd.ts index 14da344..4728caa 100644 --- a/src/preload/sd.js +++ b/src/preload/sd.ts @@ -1,5 +1,6 @@ import { ipcRenderer } from 'electron' import { DEFINE_STRING } from '../define/define_string' +import { OperateBookType } from '../define/enum/bookEnum' const sd = { // 加载当前链接的SD服务数据 diff --git a/src/preload/task.ts b/src/preload/task.ts new file mode 100644 index 0000000..8fdab72 --- /dev/null +++ b/src/preload/task.ts @@ -0,0 +1,31 @@ +import { ipcRenderer } from 'electron' +import { DEFINE_STRING } from '../define/define_string' +import { BookBackTaskType, TaskExecuteType } from '../define/enum/bookEnum' + +const task = { + /** + * 添加单个任务 + * @param bookId 小说ID + * @param taskType 任务类型 + * @param executeType 任务执行类型 + * @param bookTaskId 小说任务ID + * @param bookTaskDetailId 小说分镜ID + * @param responseMessageName 响应消息名称 + * @returns + */ + AddBookBackTask: async (bookId: string, taskType: BookBackTaskType, + executeType = TaskExecuteType.AUTO, + bookTaskId = null, + bookTaskDetailId = null, + responseMessageName: string = null) => + await ipcRenderer.invoke(DEFINE_STRING.TASK.ADD_BOOK_BACK_TASK, bookId, taskType, executeType, bookTaskId, bookTaskDetailId, responseMessageName), + + + /** + * 同时太你家多个任务 + * @param task + */ + AddMultiBookBackTask: async (task: TaskModal.Task[]) => + await ipcRenderer.invoke(DEFINE_STRING.TASK.ADD_MULTI_BOOK_BACK_TASK, task), +} +export { task } diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue index c1afe1d..4da74ce 100644 --- a/src/renderer/src/App.vue +++ b/src/renderer/src/App.vue @@ -1,5 +1,5 @@