LaiTool V3.1.1.

This commit is contained in:
lq1405 2024-09-12 14:13:09 +08:00
parent 5dc75f018a
commit 918d06e990
123 changed files with 8028 additions and 756 deletions

4
package-lock.json generated
View File

@ -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",

View File

@ -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",

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -37,3 +37,33 @@ export async function RetryWithBackoff<T>(fn: () => Promise<T>, retries: number
}
throw new Error('所有重试失败'); // 理论上不会到达这里
}
/**
*
* @param tasks
* @param concurrentCount
* @returns
*/
export async function ExecuteConcurrently(tasks: Array<() => Promise<any>>, concurrentCount: number): Promise<any[]> {
let activeTasks: Array<Promise<any>> = [];
let results: Array<Promise<any>> = [];
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);
}

View File

@ -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<string[]> {
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
}
}

View File

@ -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) {

View File

@ -15,6 +15,7 @@ export class BookBackTaskList extends Realm.Object<BookBackTaskList> {
updateTime: Date
startTime: number
endTime: number
messageName?: string
static schema: ObjectSchema = {
name: 'BookBackTaskList',
@ -31,7 +32,8 @@ export class BookBackTaskList extends Realm.Object<BookBackTaskList> {
createTime: 'date',
updateTime: 'date',
startTime: 'int',
endTime: 'int'
endTime: 'int',
messageName: 'string?'
},
primaryKey: 'id'
}

View File

@ -141,6 +141,7 @@ export class BookTaskDetailModel extends Realm.Object<BookTaskDetailModel> {
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<BookTaskDetailModel> {
subValue: 'string?',
reversePrompt: { type: 'list', objectType: 'ReversePrompt' },
characterTags: { type: 'list', objectType: 'string' },
sceneTags: 'string[]',
gptPrompt: 'string?',
mjMessage: 'MJMessage?',
outImagePath: 'string?',

View File

@ -0,0 +1,36 @@
import Realm, { ObjectSchema } from 'realm'
export class PresetModel extends Realm.Object<PresetModel> {
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'
}
}

View File

@ -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

View File

@ -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)

View File

@ -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) {

View File

@ -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)

View File

@ -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'

View File

@ -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
}
}

View File

@ -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'

View File

@ -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
}
// 判断当前全局是不是又当前这个

View File

@ -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]
}
}
})
}
}

View File

@ -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'
}
}

View File

@ -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'
}

View File

@ -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',
}

View File

@ -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 {

16
src/define/enum/preset.ts Normal file
View File

@ -0,0 +1,16 @@
/**
*
*/
export enum PresetType {
// 角色
CHARACTER = 'character',
// 本地风格
LOCAL_STYLE = 'localStyle',
// 自定义风格
CUSTOM_STYLE = 'customStyle',
// laitool预设风格
LAITOOL_STYLE = 'laitoolStyle',
}

View File

@ -1,13 +1,12 @@
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
@ -15,22 +14,24 @@ export const ImageSetting = {
async GetAutoSaveImageClassifyOptions() {
return {
code: 1,
data: [{
label: "不分类",
value: "one"
data: [
{
label: '不分类',
value: 'one'
},
// {
// label: "按模型",
// value: "model"
// },
{
label: "按风格",
value: "style"
label: '按风格',
value: 'style'
},
{
label: "风格+模型",
value: "theme"
}]
label: '风格+模型',
value: 'theme'
}
]
}
},
@ -40,18 +41,28 @@ export const ImageSetting = {
async GetImageGenerateCategory() {
return {
code: 1,
data: [{
label: "SD",
value: "sd"
data: [
{
label: 'SD',
value: 'sd'
},
{
label: "MJ",
value: "mj"
label: 'MJ',
value: 'mj'
},
{
label: "D3",
value: "d3"
}]
label: 'D3',
value: 'd3'
},
{
label: 'Flux-Forge',
value: 'flux-forge'
},
{
label: 'Flux-API',
value: 'flux-api'
}
]
}
},
@ -60,10 +71,10 @@ export const ImageSetting = {
*/
async SaveImageAutoSaveSetting(value) {
try {
value = JSON.parse(value);
await tools.writeJsonFilePropertyValue(define.img_base, "auto_save_image", value);
value = JSON.parse(value)
await tools.writeJsonFilePropertyValue(define.img_base, 'auto_save_image', value)
return {
code: 1,
code: 1
}
} catch (error) {
return {
@ -78,7 +89,7 @@ export const ImageSetting = {
*/
async GetImageAutoSaveSetting() {
try {
let res = await tools.getJsonFilePropertyValue(define.img_base, "auto_save_image", {}, false);
let res = await tools.getJsonFilePropertyValue(define.img_base, 'auto_save_image', {}, false)
return {
code: 1,
data: res
@ -96,12 +107,17 @@ export const ImageSetting = {
*/
async SaveImageToOtherFolder(output = [], value) {
try {
let show = false;
let show = false
if (value) {
value = JSON.parse(value);
show = false;
value = JSON.parse(value)
show = false
} else {
let auto_save_image = await tools.getJsonFilePropertyValue(define.img_base, "auto_save_image", {}, false);
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
@ -113,30 +129,41 @@ export const ImageSetting = {
}
}
let batch = DEFINE_STRING.QUEUE_BATCH.IMAGE_SAVE_TO_OTHER_FOLDER;
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_");
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]);
const element = path.join(global.config.project_path, 'tmp/bak/' + output[i])
// 获取指定的文件夹里面的文件
let png_files = await tools.getFilesWithExtensions(element, ".png");
console.log(png_files);
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);
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 () => {
const item = png_files[j]
global.fileQueue.enqueue(
async () => {
// 复制文件到指定的文件夹
let dst = path.join(value.save_folder, `${Date.now().toString()}_${uuidv4().split('-')[0]}.png`);
let dst = path.join(
value.save_folder,
`${Date.now().toString()}_${uuidv4().split('-')[0]}.png`
)
await tools.copyFileOrDirectory(item, dst);
}, `${batch}_${item}`, batch)
await tools.copyFileOrDirectory(item, dst)
},
`${batch}_${item}`,
batch
)
}
}
global.fileQueue.setBatchCompletionCallback(batch, (failedTasks) => {
@ -146,8 +173,8 @@ export const ImageSetting = {
但是以下任务执行失败
`
failedTasks.forEach(({ taskId, error }) => {
message += `${taskId}-, \n 错误信息: ${error}` + '\n';
});
message += `${taskId}-, \n 错误信息: ${error}` + '\n'
})
global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, {
code: 0,
@ -157,7 +184,7 @@ export const ImageSetting = {
if (show) {
global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, {
code: 1,
message: "所有图片转存完成"
message: '所有图片转存完成'
})
}
}
@ -166,7 +193,6 @@ export const ImageSetting = {
return {
code: 1
}
} catch (error) {
return {
code: 0,
@ -186,17 +212,22 @@ export const ImageSetting = {
*/
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];
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);
let hasProperty = has(define, defin_property)
if (!hasProperty) {
throw new Error(`${defin_property} 属性不存在。`);
throw new Error(`${defin_property} 属性不存在。`)
}
let data = await tools.getJsonFilePropertyValue(define[defin_property], property, default_data, need_check);
let data = await tools.getJsonFilePropertyValue(
define[defin_property],
property,
default_data,
need_check
)
return {
code: 1,
data: data
@ -204,7 +235,7 @@ export const ImageSetting = {
} catch (error) {
return {
code: 0,
message: "获取配置信息失败,错误信息入下 " + error.message.toString()
message: '获取配置信息失败,错误信息入下 ' + error.message.toString()
}
}
},
@ -219,27 +250,26 @@ export const ImageSetting = {
*/
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;
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);
let hasProperty = has(define, defin_property)
if (!hasProperty) {
throw new Error(`${defin_property} 属性不存在。`);
throw new Error(`${defin_property} 属性不存在。`)
}
await tools.writeJsonFilePropertyValue(define[defin_property], property, data, need_check);
await tools.writeJsonFilePropertyValue(define[defin_property], property, data, need_check)
return {
code: 1,
message: "保存指定的配置成功"
message: '保存指定的配置成功'
}
} catch (error) {
return {
code: 0,
message: "保存配置信息失败,错误信息入下 " + error.message.toString()
message: '保存配置信息失败,错误信息入下 ' + error.message.toString()
}
}
}
}

View File

@ -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,

View File

@ -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 }

View File

@ -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 }

View File

@ -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()

View File

@ -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)
)
}

View File

@ -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 }

View File

@ -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)

View File

@ -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 }

View File

@ -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,

View File

@ -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
}

View File

@ -138,11 +138,14 @@ export class BookBasic {
watermarkPosition: undefined,
})
// 文件重置获取data下面的所有的子文件夹删除所有的文件夹
let dirs = await GetSubdirectories(path.join(book.bookFolderPath, 'data'))
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)) {
await DeleteFolderAllFile(scriptPath, true)

View File

@ -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<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
console.log("SDReversePrompt", '')
return successMessage(null, "", "ReverseBook_SDReversePrompt")
}

View File

@ -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<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
async ResetGenerateImage(id: string, operateBookType: OperateBookType, coverData: boolean): Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
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")
}

View File

@ -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
@ -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<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
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<any>>
// 添加异步任务
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
}

View File

@ -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<void> {
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<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
}
}
/**
* FLUX API
* @param task
*/
async FluxAPIImage(task: TaskModal.Task): Promise<void> {
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
}
}
}

View File

View File

@ -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

View File

@ -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();
}
async InitService() {
if (!this.reverseBook) {
this.reverseBook = new ReverseBook()
}
if (!this.bookBackTaskList) {
this.bookBackTaskList = await BookBackTaskListService.getInstance()
}
if (!this.bookTaskDetail) {
this.bookTaskDetail = await BookTaskDetailService.getInstance()
}
if (!this.mjApi) {
this.presetService = new PresetService()
this.mjApi = new MJApi()
this.softWareServiceBasic = new SoftWareServiceBasic()
}
if (!this.bookTaskService) {
this.bookTaskService = await BookTaskService.getInstance()
/**
* MJ设置
*/
async GetMJSetting() {
if (!this.mjSetting) {
this.mjSetting = await this.softWareServiceBasic.GetMjSetting()
}
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<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
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<string> {
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<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
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<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
async AddMJGenerateImageTask(id: string, operateBookType: OperateBookType, responseMessageName: string = null, coverData: boolean = true): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
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,

View File

@ -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) {

View File

@ -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<string> {
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<string> {
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 += `, <lora:${character.data.lora}:${character.data.lora_weight}>`
}
} 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') {
if (!isEmpty(element.prompt)) {
styleString += `(${element.prompt}:${style_weight})` + ', '
}
if (element.lora && element.lora != '无' && element.lora_weight) {
styleString += `<lora:${element.lora}:${element.lora_weight}>, `
}
} else {
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
}

View File

@ -43,6 +43,7 @@ export class BookServiceBasic {
}
//#region 事务操作
transaction(callback: (realm: any) => void) {
this.bookService.transaction(() => {
@ -166,6 +167,11 @@ export class BookServiceBasic {
//#region 小说批次任务对应的分镜的相关的基础服务
async AddBookTaskDetail(bookTaskDetail: Book.SelectBookTaskDetail): Promise<void> {
await this.InitService();
this.bookTaskDetailService.AddBookTaskDetail(bookTaskDetail)
}
/**
* ID
* @param bookTaskDetailId ID
@ -185,11 +191,12 @@ export class BookServiceBasic {
/**
*
* @param condition
* @param returnEmpty false
*/
async GetBookTaskDetailData(condition: Book.QueryBookTaskDetailCondition): Promise<Book.SelectBookTaskDetail[]> {
async GetBookTaskDetailData(condition: Book.QueryBookTaskDetailCondition, returnEmpty: boolean = false): Promise<Book.SelectBookTaskDetail[]> {
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)
}
@ -237,6 +244,28 @@ export class BookServiceBasic {
this.bookTaskDetailService.DeleteBoookTaskDetailGenerateImage(bookTaskDetailId);
}
/**
*
* @param bookTaskDetailId ID
* @param reversePromptId ID
* @param data
*/
async UpdateBookTaskDetailReversePrompt(bookTaskDetailId: string, reversePromptId: string, data: Book.ReversePrompt): Promise<void> {
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<TaskModal.Task> {
bookTaskDetailId = null,
responseMessageName: string = null
): Promise<TaskModal.Task> {
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)
}
@ -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)
}
}

View File

@ -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<void> {
@ -73,4 +79,26 @@ export class SoftWareServiceBasic {
//#endregion
//#region MJ设置相关
/**
* MJ的设置信息
* @returns
*/
async GetMjSetting(): Promise<MJSetting.MjSetting> {
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
}

View File

@ -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";
@ -233,7 +233,7 @@ export class SubtitleService {
}
/**
*
*
* @param bookId
* @param bookTaskId
* @param txtPath
@ -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<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
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
}

View File

@ -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);
}
}
// 直接返回数据
@ -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);
}
}
// 直接返回数据
@ -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);
}
}
// 直接返回数据
@ -568,20 +544,20 @@ export class Translate {
}
let res_data = []
// 将所有的数据协会到本地(然后发送消息到前台界面)
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,

View File

@ -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<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
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<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
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<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
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,14 +205,20 @@ export class TranslateService {
}
}
// 翻译
/**
*
* @param value
* @returns
*/
async TranslateNowReturn(value: TranslateModel.TranslateNowIPCParams[]): Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
try {
await this.InitService()
// 循环所有的数据,返回翻译结果
let tasks = []
for (let i = 0; i < value.length; i++) {
const element = value[i];
tasks.push(async () => {
let res = await this.translate.TranslateReturnNow(element)
// global.logger.info("断点写出", JSON.stringify(res))
// 单个翻译,将返回的数据写入到原数据中
// 添加一个对返回信息进行处理的函数
@ -256,14 +253,14 @@ export class TranslateService {
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")
}
}
}

View File

@ -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<void>
*/
async D3ImageGenerate(task: TaskModal.Task): Promise<void> {
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'
// 一次请求生成一张 多个请求
//endregion
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

View File

@ -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<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
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<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
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<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
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<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
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
}

View File

@ -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<string | number, Function[]> = {};
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

View File

@ -92,6 +92,10 @@ export class Writing extends ServiceBase {
'请求成功',
'Writing_ActionStart'
)
} else {
// 系统报错
if (dataRes.code == 5000) {
throw new Error('系统错误,错误信息如下:' + dataRes.message)
} else {
// 处理不同类型的错误消息
if (setting.gptAI == 'laiapi') {
@ -104,6 +108,7 @@ export class Writing extends ServiceBase {
throw new Error(dataRes.data)
}
}
}
} catch (error) {
return errorMessage(
'执行文案相关任务失败,失败信息如下:' + error.toString(),

View File

@ -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'
/**
* 获取对应的轨道
@ -642,6 +643,14 @@ async function SaveSDConfig(value) {
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,

View File

@ -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)
})
}
})

View File

@ -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()

View File

@ -46,12 +46,10 @@ 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) {
_softwareService.UpdateSoftware(softWareData)
global.logger.info('AutoSunc_GlobalAutoSync', '初始化全局设置成功')
}
}
}
} catch (error) {
// 同步数据不报错,只添加日志
global.logger.error(

View File

@ -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')
}

View File

@ -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) {

View File

@ -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选择
}
}

3
src/model/book.d.ts vendored
View File

@ -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 // 输出图片地址

View File

@ -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

10
src/model/mj.d.ts vendored
View File

@ -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[] // 子图片地址
}

31
src/model/preset.d.ts vendored Normal file
View File

@ -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
}
}

View File

@ -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
}

27
src/model/task.d.ts vendored
View File

@ -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
}
}

View File

@ -17,6 +17,7 @@ declare namespace TranslateModel {
reversePromptId?: string // 反推提示词ID
windowId?: number // 窗口ID
type: TranslateType // 翻译类型
responseMessgeName?: string // 返回的消息名称(用作自定义)
}
type TranslateResponseMessageModel = {

View File

@ -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

View File

@ -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 }

View File

@ -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
}

View File

@ -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 }

16
src/preload/preset.ts Normal file
View File

@ -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 }

View File

@ -1,5 +1,6 @@
import { ipcRenderer } from 'electron'
import { DEFINE_STRING } from '../define/define_string'
import { OperateBookType } from '../define/enum/bookEnum'
const sd = {
// 加载当前链接的SD服务数据

31
src/preload/task.ts Normal file
View File

@ -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 }

View File

@ -1,5 +1,5 @@
<template>
<n-spin style="z-index: 1000 !important;" :show="softwareStore.spin.spinning">
<n-spin style="z-index: 1000 !important" :show="softwareStore.spin.spinning">
<template #description> {{ softwareStore.spin.tip }} </template>
<n-config-provider
:hljs="hljs"
@ -47,6 +47,7 @@ export default defineComponent({
softwareStore.SoftColor = SoftColor
window.api.getSettingDafultData(async (value) => {
await window.darkMode.toggle(value.theme)
softwareStore.globalSetting = value
})
let software = await window.setting.GetSoftwareSetting()
if (software.code == 0) {
@ -58,7 +59,7 @@ export default defineComponent({
if (software.data.length == 0) {
throw new Error('初始化信息错误: 未获取到数据')
}
softwareStore.softWare = software.data[0]
softwareStore.softWare = software.data
await window.darkMode.toggle(softwareStore.softWare.theme)
})

View File

@ -1,9 +1,11 @@
<template>
<div v-if="data.subValue && data.subValue.length != 0">
<div v-for="(item, index) in data.subValue">
<div style="height: 135px; width : 100%;overflow: auto; display: flex; margin: auto">
<div v-if="data.subValue && data.subValue.length != 0" style="width : 100%">
<div v-for="(item, index) in data.subValue" style="width : 100%">
<n-input
v-model:value="item.srt_value"
type="textarea"
style="width : 100%"
size="tiny"
:autosize="{ minRows: 1, maxRows: 5 }"
@input="InputDebounced"
@ -20,6 +22,7 @@
@input="InputDebounced"
>
</n-input>
</div>
</template>
<script>

View File

@ -0,0 +1,322 @@
<template>
<div>
<div style="height: 22px; display: flex">
<div v-if="reverseManageStore.selectBookTask.imageCategory == 'mj'" style="z-index: 100">
进度{{ data.mjMessage?.progress }}%
</div>
<n-tag
v-if="data.mjMessage?.status != 'error'"
type="success"
style="height: 18px; margin: 2px 0 2px 5px"
>{{
data.mjMessage?.status != null && data.mjMessage?.status != ''
? data.mjMessage?.status
: 'wait'
}}</n-tag
>
<div v-else style="margin-left: 5px">
<n-popover style="padding: 0; margin: 0" trigger="hover">
<template #trigger>
<n-tag style="height: 18px; margin: 2px 0 2px 0" type="error">error</n-tag>
</template>
<span>{{ data.mjMessage?.message }}</span>
</n-popover>
</div>
<n-popover
v-if="reverseManageStore.selectBookTask.imageCategory == 'mj'"
style="padding: 0; margin: 0"
trigger="hover"
>
<template #trigger>
<div
style="
margin-left: 5px;
width: 100px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
"
>
{{ data.mjMessage?.messageId }}
</div>
</template>
<span>{{ data.mjMessage?.messageId }}</span>
</n-popover>
</div>
<div></div>
<div
style="display: flex; margin-right: 10px; height: auto; min-height: 120px"
@dragstart="imageDragStart"
@drop="imageDragDrop"
@dragover="imageDragOver"
>
<div style="display: flex; align-items: center; position: relative">
<n-image
class="g-image-class generate-image-show"
:src="data.outImagePath ? data.outImagePath : softwareStore.globalSetting.space_image"
fit="cover"
:width="120"
:height="120"
lazy
/>
<n-button
text
:type="data.imageLock ? 'error' : 'primary'"
style="font-size: 24px; position: absolute; left: -12px; top: -5px"
@click="ModifyLock"
>
<n-icon>
<LockClosed v-if="data.imageLock" />
<LockOpen v-else />
</n-icon>
</n-button>
<n-button
text
type="info"
style="font-size: 24px; position: absolute; left: 8px; top: -5px"
@click="DownloadImage"
>
<n-icon>
<Download />
</n-icon>
</n-button>
</div>
<div>
<n-divider vertical style="height: 100%" />
</div>
<div style="display: flex; align-items: center">
<n-image-group>
<div
style="
display: flex;
flex-wrap: wrap;
align-items: center;
height: 122px;
overflow: auto;
"
>
<n-image
subImage="true"
class="g-image-class"
draggable="true"
:width="56"
style="margin: 1px"
v-for="(image, index) in data.subImagePath"
:key="image.id"
:src="image"
alt="图片描述"
lazy
>
</n-image>
</div>
</n-image-group>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import {
NImage,
useDialog,
NImageGroup,
NDivider,
NIcon,
NButton,
NPopover,
NTag,
useMessage
} from 'naive-ui'
import { LockClosed, LockOpen, Download } from '@vicons/ionicons5'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { useImageStore } from '../../../../../stores/image'
let props = defineProps({
initData: undefined,
index: undefined
})
let imageStore = useImageStore()
let message = useMessage()
let dialog = useDialog()
let data = ref(props.initData)
let images = ref(props.initData.subImagePath)
let outImagePath = ref(props.initData.outImagePath)
let width = ref(null)
let dragTarget = null
let reverseManageStore = useReverseManageStore()
let softwareStore = useSoftwareStore()
// props.initData.subImagePath
watch(
() => props.initData.subImagePath,
(value) => {
images.value = value
width.value = 170 / 3
setNodeAttribute()
}
)
// props.initData.outImagePath
watch(
() => props.initData.outImagePath,
(value) => {
outImagePath.value = value + `?t=${new Date().getTime()}`
}
)
// props.initData
watch(
() => props.initData,
(value) => {
data.value = value
setNodeAttribute()
},
{ deep: true }
)
onMounted(async () => {
setNodeAttribute()
outImagePath.value = outImagePath.value + `?t=${new Date().getTime()}`
})
async function setNodeAttribute() {
let ele = document.querySelectorAll('.g-image-class')
ele.forEach((item) => {
item.setAttribute('data-id', data.value.id)
Array.from(item.getElementsByTagName('img')).forEach((img) => {
img.setAttribute('data-id', data.value.id)
})
})
let mainImage = document.querySelectorAll('.generate-image-show')
mainImage.forEach((item) => {
item.setAttribute('mainImage', '1')
Array.from(item.getElementsByTagName('img')).forEach((img) => {
img.setAttribute('mainImage', '1')
})
})
}
//
async function imageDragStart(e) {
e.dataTransfer.effectAllowed = 'move'
console.log('move', e.target)
dragTarget = e.target
imageStore.SetDragTarget(e.target)
}
//
async function imageDragDrop(e) {
console.log('end', e.target, dragTarget)
//
// if (dragTarget == null) {
// message.error('')
// return
// }
//
if (e.target.getAttribute('mainImage') != '1') {
message.error('图片只能拖拽到主图片上')
return
}
let source_img = imageStore.GetDragTarget.src
.split('?')[0]
.replace(/^file:\/\/\//, '')
.replace(/^\//, '')
let target_img = e.target.src
.split('?')[0]
.replace(/^file:\/\/\//, '')
.replace(/^\//, '')
//
await window.api.DownloadImageFile([source_img, 'replace', target_img], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
data.value.outImagePath =
e.target.src.split('?')[0].replaceAll('\\', '/') + '?time=' + new Date().getTime()
})
}
//
async function imageDragOver(e) {
e.preventDefault()
}
async function ModifyLock() {
//
if (!outImagePath.value) {
message.error('没有生成图片,不能锁定')
return
}
let lock = !data.value.imageLock
//
let res = await window.db.UpdateBookTaskDetailData(data.value.id, {
imageLock: lock
})
if (res.code == 0) {
message.error(res.message)
return
}
message.success('图片锁定/解锁成功')
reverseManageStore.selectBookTaskDetail[props.index].imageLock = data.value.imageLock
data.value.imageLock = lock
}
/**
* 下载对应的分镜图片
*/
async function DownloadImage() {
// mj_message
dialog.warning({
title: '下载图片警告',
content: '单个图片的下载,只支持 MJ API 生图,当前有图片的话会被覆盖掉,是否继续?',
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
if (data.value.mj_message) {
debugger
//
// success error
if (data.value.mj_message.status == 'error') {
message.error('失败状态不能采集图片')
return
}
if (isEmpty(data.value.mj_message.message_id)) {
message.error('没有消息ID不能采集图片')
return
}
window.mj.GetGeneratedMJImageAndSplit(JSON.stringify([toRaw(data.value)]), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
//
for (let i = 0; i < value.data.length; i++) {
const element = value.data[i]
//
data.value.outImagePath =
'file://' +
element.outImagePath.replaceAll('\\', '/') +
'?time=' +
new Date().getTime()
data.value.subImagePath = element.subImagePath.map(
(item) => 'file://' + item.replaceAll('\\', '/') + '?time=' + new Date().getTime()
)
data.value.mj_message.image_click = element.result
data.value.mj_message.image_path = element.image_path
data.value.mj_message.progress = 100
data.value.mj_message.status = 'success'
}
})
} else if (data.value.mjMessage) {
message.error('暂时不支持')
return
} else {
message.error('没有生图数据')
}
}
})
}
</script>

View File

@ -159,11 +159,12 @@ export default defineComponent({
//
function TranslateBatchParams(from, to) {
debugger
let translateData = []
for (let i = index.value; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
// GPTGPT
if (!isEmpty(data.value.gptPrompt)) {
if (!isEmpty(element.gptPrompt)) {
translateData.push({
text: element.gptPrompt,
from: from,

View File

@ -0,0 +1,59 @@
<template>
<div style="display: flex">
<span>出图</span>
<n-select
style="width: 100px; margin-left: 5px"
size="tiny"
:options="image_options"
@update:value="UpdateImageGenerateCategory"
v-model:value="reverseManageStore.selectBookTask.imageCategory"
placeholder="选择生图模式"
></n-select>
<n-button
v-if="reverseManageStore.selectBookTask.imageCategory == 'mj'"
color="#7c461e"
size="tiny"
style="margin-left: 5px"
@click="MJGetImage"
>MJ采集图片</n-button
>
<n-button
v-if="reverseManageStore.selectBookTask.imageCategory == 'mj'"
color="#e18a3b"
size="tiny"
style="margin-left: 5px"
@click="OneSplitFour"
>1拆4</n-button
>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { NSelect, NButton, useMessage } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
let softwareStore = useSoftwareStore()
let message = useMessage()
let reverseManageStore = useReverseManageStore()
let image_options = ref([])
onMounted(async () => {
//
await window.api.GetImageGenerateCategory((value) => {
if (value.code == 0) {
message.error(value.message)
return
}
image_options.value = value.data
})
})
async function UpdateImageGenerateCategory(value, options) {
//
let res = await window.db.UpdateBookTaskData(reverseManageStore.selectBookTask.id, {
imageCategory: value
})
window.api.showGlobalMessage(res)
}
</script>

View File

@ -95,7 +95,6 @@ export default defineComponent({
//
async function SelectStyle() {
debugger
//
//
//

View File

@ -0,0 +1,303 @@
<template>
<div style="margin-top: 30px">
<div style="display: flex; align-items: center">
<n-button color="#b6a014" size="small" @click="formateWrite">一键格式化</n-button>
<n-popover trigger="hover">
<template #trigger>
<n-button quaternary circle size="tiny" color="#b6a014" @click="AddSplitChar">
<template #icon>
<n-icon size="25"> <AddCircleOutline /> </n-icon>
</template>
</n-button>
</template>
<span>添加分割标识符</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<n-input-number
style="margin-left: 10px; width: 100px"
size="small"
placeholder="输入合并的行数"
:max="10000"
:min="1"
@update:value="mergeCountChange"
:show-button="false"
v-model:value="write_setting.merge_count"
></n-input-number>
</template>
<span>设置将多少行进行合并</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<n-input
style="margin-left: 10px; width: 80px"
size="small"
placeholder="输入中间拼接的字符"
@update:value="mergeCountChange"
:show-button="false"
v-model:value="write_setting.merge_char"
></n-input>
</template>
<span>设置中间拼接的字符</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<n-input
style="margin-left: 10px; width: 80px"
size="small"
placeholder="输入结尾拼接的字符"
@update:value="mergeCountChange"
:show-button="false"
v-model:value="write_setting.end_char"
></n-input>
</template>
<span>设置结尾拼接的字符</span>
</n-popover>
<n-button color="#80a492" size="small" style="margin-left: 10px" @click="mergeWord"
>合并</n-button
>
<n-button color="#f3a694" size="small" style="margin-left: 10px" @click="CopyWord"
>一键复制内容</n-button
>
</div>
<n-input
style="margin-top: 5px"
v-model:value="word"
:autosize="{ minRows: 30, maxRows: 30 }"
type="textarea"
placeholder="输入分镜文案"
@update:value="ChangeWordInput"
/>
<div style="margin-top: 5px; display: flex">
<div style="flex: 1"> {{ rowCount }} 行分镜当前数量不是最后数量最后会删除空行</div>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw } from 'vue'
import {
NImage,
useMessage,
NButton,
useDialog,
NInput,
NIcon,
NPopover,
NInputNumber
} from 'naive-ui'
import { AddCircleOutline } from '@vicons/ionicons5'
import InputDialogContent from '../../../Original/Components/InputDialogContent.vue'
export default defineComponent({
components: {
NImage,
NButton,
NInput,
NInputNumber,
NIcon,
AddCircleOutline,
NPopover
},
props: ['initData'],
setup(props) {
let message = useMessage()
let dialog = useDialog()
let rowCount = ref(0)
let data = ref(props.initData)
let word = ref(null)
let word_arr = ref([])
let split_ref = ref(null)
let write_setting = ref({
split_char: '。,“”‘’!?【】《》()…—:;.,\'\'""!?[]<>()...-:;',
merge_count: 3,
merge_char: '',
end_char: '。'
})
onMounted(async () => {
// word
let tmp_arr = []
for (let i = 0; i < data.value.length; i++) {
const item = data.value[i]
tmp_arr.push(item.after_gpt)
}
word.value = tmp_arr.join('\n')
ChangeWordInput()
//
await window.api.GetDefineConfigJsonByProperty(
JSON.stringify(['clip_setting', 'write_setting', false, null]),
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
if (value.data) {
write_setting.value = value.data
}
}
)
})
/**
* 修改数据框时触发的事件
*/
async function ChangeWordInput() {
let word_arr = word.value.split('\n')
//
if (rowCount.value == 1 && word_arr[0] == '') {
rowCount.value = 0
} else {
rowCount.value = word_arr.length
}
}
/**
* 添加分割符号
*/
async function AddSplitChar() {
//
//
let dialogWidth = 400
let dialogHeight = 150
dialog.create({
title: '添加分割符',
showIcon: false,
closeOnEsc: false,
content: () =>
h(InputDialogContent, {
ref: split_ref,
initData: write_setting.value.split_char,
placeholder: '请输入分割符'
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
write_setting.value.split_char = split_ref.value.data
//
await window.api.SaveDefineConfigJsonByProperty(
JSON.stringify(['clip_setting', 'write_setting', toRaw(write_setting.value), false]),
(value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
}
)
}
})
}
/**
* 更新input-number事件
*/
let timer = null
async function mergeCountChange(value) {
//
clearTimeout(timer)
timer = setTimeout(async () => {
//
//
await window.api.SaveDefineConfigJsonByProperty(
JSON.stringify(['clip_setting', 'write_setting', toRaw(write_setting.value), false]),
(value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
}
)
}, 500)
}
/**
* 一键格式化
*/
async function formateWrite() {
let split_arr = Array.from(write_setting.value.split_char)
split_arr.forEach((item) => {
let specialCharacters = [
'.',
'*',
'?',
'+',
'^',
'$',
'[',
']',
'(',
')',
'{',
'}',
'|',
'\\'
]
let regex
if (specialCharacters.includes(item)) {
regex = new RegExp('\\' + item, 'g')
} else {
regex = new RegExp(item, 'g')
}
word.value = word.value.replace(regex, '\n')
})
//
let word_arr = word.value.split('\n')
word_arr = word_arr.filter((item) => item != '' && item != null)
word.value = word_arr.join('\n')
ChangeWordInput()
}
/**
* 合并数据
*/
async function mergeWord() {
let step = write_setting.value.merge_count ? write_setting.value.merge_count : 3
let tmp_arr = []
let word_arr = word.value.split('\n')
for (let i = 0; i < word_arr.length; i += step) {
let group = word_arr.slice(i, i + step)
tmp_arr.push(group.join(write_setting.value.merge_char) + write_setting.value.end_char)
}
word.value = tmp_arr.join('\n')
ChangeWordInput()
}
/**
* 复制数据到剪贴板
*/
async function CopyWord() {
// word
try {
await navigator.clipboard.writeText(word.value)
message.success('复制成功')
} catch (error) {
message.error('复制失败')
}
}
return {
data,
rowCount,
ChangeWordInput,
word,
word_arr,
AddSplitChar,
split_ref,
write_setting,
mergeCountChange,
formateWrite,
mergeWord,
CopyWord
}
}
})
</script>

View File

@ -0,0 +1,718 @@
<template>
<div id="import_word_and_srt">
<div style="height: 30px; margin-bottom: 5px">
<n-button
v-if="type == 'original' || type == 'new_original'"
color="#1e90ff"
size="small"
style="margin-left: 20px"
@click="OpenOneDialog"
>导入字幕</n-button
>
<n-button color="#1e90ff" size="small" style="margin-left: 20px" @click="ImportSrtAndGetTime"
>导入srt</n-button
>
<n-button
color="#1e90ff"
size="small"
style="margin-left: 20px"
@click="SaveCopywritingInformation"
>保存文案信息</n-button
>
</div>
<n-data-table
:max-height="maxHeight"
:row-key="rowKey"
:columns="columns"
:data="data"
:pagination="false"
:bordered="false"
@update:checked-row-keys="handleCheck"
/>
</div>
</template>
<script>
import { h, defineComponent, onMounted, ref, toRaw, watch, onUnmounted } from 'vue'
import { NButton, NDataTable, useMessage, NInput, NImage, useDialog } from 'naive-ui'
import { v4 as uuidv4 } from 'uuid'
import EditWord from './EditWord.vue'
import { checkStringValueDeleteSuffix } from '../../../../../../main/Public/generalTools'
import { useReverseManageStore } from '../../../../../../stores/reverseManage'
import InputDialogContent from '../../../Original/Components/InputDialogContent.vue'
import { isEmpty } from 'lodash'
import { OperateBookType } from '../../../../../../define/enum/bookEnum'
const buttonArr = [
{
title: '下对齐',
key: 'AlignNextWord',
color: '#8a2be0'
}
]
export default defineComponent({
components: {
NButton,
NDataTable,
NInput,
NImage
},
props: ['initData', 'menuType', 'height', 'type'],
setup(props) {
let hh = props.height
let maxHeight = props.height - 80
debugger
let data = ref(JSON.parse(JSON.stringify(props.initData)))
const message = useMessage()
let dialog = useDialog()
let selectKey = ref([])
let editWordRef = ref(null)
let wenkeRef = ref(null)
let type = ref(props.type ? props.type : 'original')
let reverseManageStore = useReverseManageStore()
const createColumns = ({ AlignNextWord }) => {
return [
{
type: 'selection',
disabled(row) {
return row.name === 'Edward King 3'
}
},
{
title: '编号',
key: 'no',
width: 70
},
{
title: '分镜文案',
key: 'after_gpt',
width: 300,
render(row, index) {
return h(NInput, {
value: row.word,
type: 'textarea',
autosize: true,
onUpdateValue(v) {
data.value[index].word = v
}
})
}
},
{
title: '字幕',
key: 'srt',
width: '300',
render(row) {
let tmp = row.subValue.map((item) =>
h('div', { style: 'display:flex; height : auto; margin: 2px 0' }, [
h(NInput, {
size: 'tiny',
value: item.srt_value
}),
h(
NButton,
{
style: 'margin : 0 2px',
size: 'tiny',
color: '#ecd452',
onClick: () => Upper(row, item, true)
},
{ default: () => '向上' }
),
h(
NButton,
{
style: 'margin : 0 2px',
size: 'tiny',
color: '#ee7959',
onClick: () => Down(row, item)
},
{ default: () => '向下' }
)
])
)
return tmp
}
},
{
title: '操作',
key: 'options',
width: 130,
render(row) {
const btn = buttonArr.map((item) => {
return h(
NButton,
{
style: 'margin : 2px',
strong: true,
size: 'tiny',
color: item.color,
onClick: () => {
if (item.key == 'AlignNextWord') {
AlignNextWord(row)
}
}
},
{
default: () => item.title
}
)
})
return btn
}
},
{
title: '时间范围',
key: 'timeLimit',
width: 120
}
]
}
//
function setHeight() {
let div = document.getElementById('import_word_and_srt')
div.style.height = hh + 'px'
}
onMounted(async () => {
setHeight()
// ctrl + alt + w
window.addEventListener('keydown', ImportWenKeSrt)
})
onUnmounted(() => {
window.removeEventListener('keydown', ImportWenKeSrt)
})
/**
* 导入文刻软件里面的srt
*/
async function ImportWenKeSrt(e) {
if (!(e.ctrlKey && e.altKey && e.key == 'k')) {
return
}
let dialogWidth = 400
let dialogHeight = 150
dialog.create({
name: 'importWenkeWord',
title: '导入外部对齐文件',
showIcon: false,
closeOnEsc: false,
content: () =>
h(InputDialogContent, {
ref: wenkeRef,
initData: null,
placeholder: '请导入外部对齐文件'
}),
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
debugger
console.log(wenkeRef.value.word)
let word = wenkeRef.value.data
if (word == null || word == '') {
message.error('数据为空')
return
}
word = JSON.parse(word)
//
data.value = []
let word_arr = word.rows
let lastId = ''
let end_word = []
for (let i = 0; i < word_arr.length; i++) {
const element = word_arr[i]
// text
let row_text = ''
let start_time = 0
let end_time = 0
let subValue = []
if (element.texts) {
for (let j = 0; j < element.texts.length; j++) {
const text = element.texts[j]
row_text += text.text + ','
if (j == 0) {
start_time = text.start
}
if (j == element.texts.length - 1) {
end_time = text.start + text.duration
}
subValue.push({
id: uuidv4(),
srt_value: text.text,
start_time: text.start / 1000,
end_time: text.start / 1000 + text.duration / 1000
})
}
}
let id = uuidv4()
lastId = id
data.value.push({
no: i + 1,
id: id,
lastId: lastId,
word: checkStringValueDeleteSuffix(row_text, ','),
after_gpt: checkStringValueDeleteSuffix(row_text, ','),
start_time: start_time / 1000,
end_time: end_time / 1000,
timeLimit: `${start_time / 1000} -- ${end_time / 1000}`,
subValue: subValue
})
end_word.push(checkStringValueDeleteSuffix(row_text, ','))
}
//
let local_word = end_word.filter((item) => item != '' && item != null)
await window.api.saveWordTxt(toRaw(local_word), (value) => {
if (value.code == 0) {
message.error('文案写入失败' + value.message)
return
}
})
return true
}
})
}
/**
* 重新计算指定索引值的时间
* @param {int} index
*/
function modifyTime(index) {
//
if (data.value[index]) {
let s_v = data.value[index].subValue
let len = s_v.length
if (len > 0) {
let s_t = s_v[0].start_time
let e_t = s_v[len - 1].end_time
data.value[index].start_time = s_t
data.value[index].end_time = e_t
data.value[index].timeLimit = `${s_t} -- ${e_t}`
} else {
data.value[index].start_time = null
data.value[index].end_time = null
data.value[index].timeLimit = ``
}
}
}
/**
* 判断当前行的子数据的长度是不是大于0不大于0 的话下面的字幕往上替补
* @param {data value 的索引} index
*/
function moveUp(index) {
let s_v = data.value[index].subValue
if (s_v.length == 0) {
//
for (let i = index; i < data.value.length; i++) {
// const element = data.value[i];
if (data.value[i + 1] != null) {
data.value[i].subValue = data.value[i + 1].subValue
} else {
//
// 稿
if (
data.value[i].word == null ||
data.value[i].word == '' ||
data.value[i].after_gpt == null ||
data.value[i].after_gpt == ''
) {
//
data.value.splice(i, 1)
}
}
}
}
}
/**
* 字幕文件向上
*/
async function Upper(row, item, isM) {
//
// item
//
// 稿
//
if (row.lastId == null || row.lastId == '') {
message.error('当前是第一个。不能向上合并')
return
}
//
let itemId = item.id
let itemIndex = row.subValue.findIndex((item) => item.id == itemId)
let thisIndex = data.value.findIndex((item) => item.id == row.id)
if (itemIndex < 0 && isM) {
message.error('数据错误')
return
} else {
//
let lastRowIndex = data.value.findIndex((item) => item.id == row.lastId)
//
for (let i = 0; i <= itemIndex; i++) {
const element = row.subValue[i]
data.value[lastRowIndex].subValue.push(element)
}
//
data.value[lastRowIndex + 1].subValue.splice(0, itemIndex + 1)
moveUp(thisIndex)
// 稿
if (
data.value[thisIndex] &&
data.value[thisIndex].subValue == null &&
(row.word == null || row.word == '' || row.after_gpt == null || row.after_gpt == '')
) {
// lastId lastIdlastId
if (data.value[thisIndex + 1] != null) {
data.value[thisIndex + 1].lastId = row.lastId
}
//
data.value.splice(thisIndex, 1)
//
for (let i = thisIndex; i < data.value.length; i++) {
data.value[i].no = data.value[i].no - 1
}
}
//
modifyTime(lastRowIndex)
modifyTime(thisIndex)
}
}
/**
* 字幕文件向下
*/
async function Down(row, item) {
//
// item
//
// 稿
//
let itemId = item.id
let itemIndex = row.subValue.findIndex((item) => item.id == itemId)
let thisIndex = data.value.findIndex((item) => item.id == row.id)
//
if (data.value[thisIndex + 1] == null) {
message.error('当前已经是最后一个。不能向下合并')
return
}
if (itemIndex < 0) {
message.error('数据错误')
return
} else {
//
let nextRowIndex = data.value.findIndex((item) => item.lastId == row.id)
//
for (let i = row.subValue.length - 1; i >= itemIndex; i--) {
const element = row.subValue[i]
data.value[nextRowIndex].subValue.unshift(element)
}
//
data.value[thisIndex].subValue.splice(itemIndex)
//
moveUp(thisIndex)
// 稿
if (
data.value[thisIndex].subValue == null &&
(row.word == null || row.word == '' || row.after_gpt == null || row.after_gpt == '')
) {
// lastId lastIdlastId
if (data.value[nextRowIndex] != null) {
data.value[nextRowIndex].lastId = row.lastId
}
//
data.value.splice(thisIndex, 1)
//
for (let i = thisIndex; i < data.value.length; i++) {
data.value[i].no = data.value[i].no - 1
}
}
//
modifyTime(nextRowIndex)
modifyTime(thisIndex)
}
}
function removePunctuationIncludingEllipsis(sentence) {
//
// 使
const punctuationRegExp = /[., \/#!$%\^&\*;:{}=\-_`~()\[\],。、;:?!‘’“”()【】《》…]+/g
// 使replace
return sentence.replace(punctuationRegExp, '')
}
/**
* 对齐当前列即下面的数据
*/
async function AlignNextWord(row) {
//
dialog.create({
type: 'warning',
title: '警告',
showIcon: true,
content: '会自动对齐下面的数据。没有对齐成功就要手动调整在自动对齐',
style: `width : 400px;`,
maskClosable: false,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
//
let itemId = row.id
let thisIndex = data.value.findIndex((item) => item.id == itemId)
if (thisIndex < 0) {
message.error('数据错误')
return
}
let nextSrt = []
//
for (let i = thisIndex; i < data.value.length; i++) {
const element = data.value[i]
nextSrt = nextSrt.concat(element.subValue)
}
let init_num = thisIndex
// let data
let tmp_str = ''
for (let i = 0; i < nextSrt.length; ) {
const element = nextSrt[i]
let current_text = `${thisIndex + 1} 数据。字幕:${
element.srt_value
}与文案不匹配亲检查上下文`
tmp_str += element.srt_value
if (
removePunctuationIncludingEllipsis(data.value[thisIndex].after_gpt).startsWith(
removePunctuationIncludingEllipsis(tmp_str)
)
) {
// data.value[thisIndex].subValue.push(element);
if (init_num < thisIndex || i > 0) {
Upper(data.value[thisIndex + 1], element, false)
}
i++
} else {
if (
removePunctuationIncludingEllipsis(data.value[thisIndex + 1].after_gpt).startsWith(
removePunctuationIncludingEllipsis(element.srt_value)
)
) {
thisIndex++
tmp_str = ''
} else {
window.api.showGlobalMessageDialog({
code: 0,
message: current_text
})
return
}
}
}
}
})
}
/**
* 保存字幕文案信息用于自动生成视频
*/
async function SaveCopywritingInformation() {
if (type.value == 'original') {
if (props.menuType == 'mj') {
await window.mj.SvaeMJWordSrt([toRaw(data.value), 'srt_time_information'], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('保存成功')
})
return
} else {
// config
await window.api.OriginalAddWebuiJson(JSON.stringify(data.value), async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
if (value.data) {
for (let i = 0; i < data.value.length; i++) {
let name = String(data.value[i].no).padStart(5, '0')
data.value[i].prompt_json = value.data + '/' + name + '.png.json'
data.value[i].name = name + '.png'
}
}
await window.api.SaveCopywritingInformation(
[toRaw(data.value), 'srt_time_information'],
(value) => {
if (value.code != 1) {
message.error(value.message)
return
}
message.success('保存成功')
}
)
})
}
} else if (type.value == 'mj_reverse' || type.value == 'sd_reverse') {
debugger
if (data.value.length != reverseManageStore.selectBookTaskDetail.length) {
message.error('检测到导入的字幕数据和分镜对不上,请先对齐')
return
}
//
for (let i = 0; i < data.value.length; i++) {
const element = data.value[i]
let res = await window.db.UpdateBookTaskDetailData(element.id, {
startTime: element.start_time,
endTime: element.end_time,
timeLimit: element.timeLimit,
subValue: JSON.stringify(element.subValue)
})
}
} else if (type.value == 'new_original') {
// srt
for (let i = 0; i < data.value.length; i++) {
const element = data.value[i]
if (
isEmpty(element.word) ||
isEmpty(element.after_gpt) ||
!element.hasOwnProperty('start_time') ||
!element.hasOwnProperty('end_time') ||
!element.hasOwnProperty('subValue') ||
element.subValue.length == 0
) {
message.error("保存数据失败,数据不完整,请检查'文案'、'时间'、'字幕' 等,数据是否完整")
return
}
}
//
let res = await window.book.SaveCopywriting(
reverseManageStore.selectBookTask.id,
toRaw(data.value),
OperateBookType.BOOKTASK
)
window.api.showGlobalMessageDialog(res)
} else {
message.error('未知的小说类型,请检查')
}
}
/**
* 导入字幕并进行分镜输出显示时间将分镜给他传进去
*/
async function ImportSrtAndGetTime() {
let srt_path = null
// SRT
await window.api.SelectFile(['srt'], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
srt_path = value.value
})
await window.api.ImportSrtAndGetTime([toRaw(data.value), srt_path], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
//
for (let i = 0; i < value.data.length; i++) {
const item = value.data[i]
if (i >= data.value.length) {
item.no = i + 1
item.timeLimit = `${item.start_time} -- ${item.end_time}`
data.value.push(item)
} else {
data.value[i].start_time = item.start_time
data.value[i].end_time = item.end_time
data.value[i].timeLimit = `${item.start_time} -- ${item.end_time}`
data.value[i].subValue = item.subValue
}
}
})
}
/**
* 打开添加字幕信息的窗口
*/
async function OpenOneDialog() {
//
//
let dialogWidth = window.innerWidth * 0.7
let dialogHeight = window.innerHeight * 0.9
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () => h(EditWord, { ref: editWordRef, initData: data }),
style: `width : ${dialogWidth}px; minHeight : ${dialogHeight}px`,
maskClosable: false,
positiveText: '保存',
negativeText: '取消',
onPositiveClick: async () => {
data.value = []
let word_arr = editWordRef.value.word.split('\n')
let lastId = ''
for (let i = 0; i < word_arr.length; i++) {
const element = word_arr[i]
if (element != '' && element != null) {
let id = uuidv4()
let obj = {
no: i + 1,
id: id,
lastId: lastId,
word: element,
after_gpt: element,
subValue: []
}
lastId = id
data.value.push(obj)
}
}
//
let local_word = word_arr.filter((item) => item != '' && item != null)
await window.api.saveWordTxt(toRaw(local_word), (value) => {
if (value.code == 0) {
message.error('文案写入失败' + value.message)
return
}
})
return true
}
})
}
return {
data,
columns: createColumns({
AlignNextWord
}),
rowKey: (row) => row.id,
handleCheck(rowKeys) {
selectKey.value = rowKeys
},
ImportSrtAndGetTime,
SaveCopywritingInformation,
OpenOneDialog,
maxHeight,
hh,
wenkeRef,
type,
reverseManageStore
}
}
})
</script>

View File

@ -119,6 +119,7 @@ import { useReverseManageStore } from '../../../../../../stores/reverseManage.ts
import { CloseSharp } from '@vicons/ionicons5'
import { cloneDeep } from 'lodash'
import { useSoftwareStore } from '../../../../../../stores/software.ts'
import { BookType } from '../../../../../../define/enum/bookEnum'
export default defineComponent({
components: {
@ -175,6 +176,16 @@ export default defineComponent({
} else if (propertyName == 'srtPath') {
ext = ['srt']
}
//
if (
reverseManageStore.selectBook.type == BookType.ORIGINAL &&
propertyName == 'oldVideoPath'
) {
message.error('原创小说不能选择视频文件')
return
}
// MP4
await window.api.SelectFile(ext, (value) => {
debugger
@ -212,8 +223,17 @@ export default defineComponent({
}
let rules = {
name: ruleObj('书名必填'),
oldVideoPath: ruleObj('反推的视频文件地址必填'),
type: ruleObj('小说类型必填')
type: ruleObj('小说类型必填'),
oldVideoPath: {
required: true,
validator(rule, value) {
//
if(reverseManageStore.selectBook.type == BookType.SD_REVERSE || reverseManageStore.selectBook.type == BookType.MJ_REVERSE){
return new Error("MJ反推和SD反推视频文件必选")
}
},
trigger: ['input', 'blur', 'change']
}
}
/**
@ -291,7 +311,8 @@ export default defineComponent({
bookRef,
softwareStore,
OpenFolder,
backgroundMusicOptions,loading
backgroundMusicOptions,
loading
}
}
})

View File

@ -774,7 +774,8 @@ export default defineComponent({
after_gpt: element.afterGpt,
subValue: element.subValue ? element.subValue : [],
name: element.name + '.png',
prompt: element.prompt
prompt: element.prompt,
timeLimit: element.timeLimit
})
}

View File

@ -0,0 +1,361 @@
<template>
<div>
<div style="width: 500px; position: relative">
<n-form ref="formRef" label-placement="top" :model="characterData" :rules="rules">
<n-form-item label="人物名称" path="label">
<n-input
style="width: 400px"
v-model:value="characterData.label"
placeholder="请输入人物名称"
/>
<n-checkbox style="margin-left: 20px" v-model:checked="characterData.isShow"
>显示</n-checkbox
>
<div
style="position: absolute; left: 520px; top: 0; display: flex; flex-direction: column"
>
<n-image
width="120"
height="120"
:src="png_base64 ? png_base64 : characterData.show_image"
/>
<n-button color="#e18a3b" style="margin-top: 5px" size="tiny" @click="SelecImage"
>本地上传图片</n-button
>
</div>
</n-form-item>
<n-form-item label="人物别名(可多个)">
<n-dynamic-tags type="success" v-model:value="alias_tags" />
</n-form-item>
<n-form-item label="人物提示词(中文)">
<n-input
type="textarea"
:rows="2"
v-model:value="characterData.chinese_prompt"
placeholder="请输入人物提示词"
/>
<!-- 里面的按钮设置上下居中 -->
<div style="width: 100px; margin-left: 20px">
<n-button color="#a76283" size="tiny" @click="TranslatePrompt" :loading="loading"
>翻译提示词</n-button
>
<n-button
color="#e18a3b"
size="tiny"
style="margin-top: 10px"
@click="GenerateCharacterImage"
:loading="imageLoading"
>SD生成图片</n-button
>
</div>
</n-form-item>
<n-form-item label="人物提示词(英文)" path="prompt">
<n-input
type="textarea"
:rows="2"
v-model:value="characterData.prompt"
placeholder="请输入人物提示词"
/>
</n-form-item>
<n-form-item label="MJ人物图片链接cref">
<n-input
v-model:value="characterData.image_url"
placeholder="请输入人物图片链接"
style="margin-right: 20px"
/>
<n-image
v-if="characterData.image_url"
:src="characterData.image_url"
style="width: 80px; height: 80px"
:alt="characterData.image_url"
/>
</n-form-item>
<n-form-item label="MJ角色迁移 cw 值0-100">
<n-input-number
:show-button="false"
v-model:value="characterData.cref_cw"
min="0"
max="100"
placeholder="请输入人物风格迁移的CW值"
/>
</n-form-item>
<n-form-item label="SDLora选择">
<n-select
v-model:value="characterData.lora"
style="width: 250px"
:options="lora_options"
placeholder="选择人物lora"
/>
<n-input-number
style="width: 120px; margin-left: 10px"
placeholder="权重"
v-model:value="characterData.lora_weight"
:show-button="false"
:max="2"
:step="0.01"
:precision="2"
S
:min="0"
/>
</n-form-item>
</n-form>
<div style="text-align: right">
<n-button type="success" @click="SaveCharacterTag">保存</n-button>
</div>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch, nextTick } from 'vue'
import {
NImage,
useMessage,
NButton,
useDialog,
NInput,
NForm,
NFormItem,
NDynamicTags,
NInputNumber,
NSelect,
NCheckbox
} from 'naive-ui'
import { v4 as uuidv4 } from 'uuid'
import { isEmpty } from 'lodash'
export default defineComponent({
components: {
NImage,
NButton,
NInput,
NForm,
NFormItem,
NDynamicTags,
NInputNumber,
NSelect,
NCheckbox
},
props: ['currentCharacter', 'initFunc', 'currentTags', 'lora_options'],
setup(props) {
let message = useMessage()
let alias_tags = ref(props.currentTags)
let characterData = ref(props.currentCharacter)
let lora_options = ref(props.lora_options)
let loading = ref(false)
let imageLoading = ref(false)
let formRef = ref(null)
let png_base64 = ref(null)
//
watch(
() => props.currentCharacter,
(value) => {
characterData.value = value
},
{ deep: true }
)
watch(
() => props.currentTags,
(value) => {
alias_tags.value = value
},
{ deep: true }
)
onMounted(async () => {})
async function SaveCharacterTag(e) {
e.preventDefault()
formRef.value?.validate(async (errors) => {
if (errors) {
message.error('请检查必填字段')
return
}
//
let children = []
alias_tags.value.forEach((item) => {
children.push({
label: item,
key: uuidv4(),
type: 'min',
chinese_prompt: characterData.value.chinese_prompt,
prompt: characterData.value.prompt,
image_url: characterData.value.image_url,
show_image: characterData.value.show_image
? characterData.value.show_image.split('?t')[0]
: null,
cref_cw: characterData.value.cref_cw,
lora: characterData.value.lora,
lora_weight: characterData.value.lora_weight
})
})
characterData.value['children'] = children
console.log(characterData.value)
characterData.value['type'] = 'character_main'
characterData.value['show_image'] = characterData.value.show_image
? characterData.value.show_image.split('?t')[0]
: null
//
await window.mj.SaveTagPropertyData(
[JSON.stringify(characterData.value), 'character_tags'],
(value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
//initFunc
props.initFunc()
//
characterData.value = {
label: null,
key: null,
type: 'character_main',
chinese_prompt: null,
prompt: null,
image_url: null,
show_image: window.config.space_image,
cref_cw: 20,
chinese_prompt: null,
lora: '无',
lora_weight: 1
}
png_base64.value = null
alias_tags.value = []
}
)
})
}
//
async function TranslatePrompt() {
loading.value = true
if (characterData.value.chinese_prompt == '' || characterData.value.chinese_prompt == null) {
message.error('请输入中文提示词')
loading.value = false
return
}
await window.api.TranslateReturnNow(
[characterData.value.chinese_prompt, 'zh', 'en', false],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
characterData.value.prompt = value.data[0].src
}
)
loading.value = false
}
let ruleObj = (errorMessage) => {
return [
{
required: true,
validator(rule, value) {
if (value == null || value == '') return new Error(errorMessage)
return true
},
trigger: ['input', 'blur', 'change']
}
]
}
let rules = {
label: ruleObj('必填人物名称'),
prompt: ruleObj('必填英文提示词')
}
/**
* 单张生成角色图片预览
*/
async function GenerateCharacterImage() {
// lora
let lora = ''
if (
characterData.value.lora != null &&
characterData.value.lora != '' &&
characterData.value.lora != '无'
) {
lora = `<lora:${characterData.value.lora}:${characterData.value.lora_weight}>`
}
let d = {
prompt: `${characterData.value.prompt}, ${lora}`,
width: 1024,
height: 1024
}
if (isEmpty(characterData.value.prompt)) {
message.error('请填写英文提示词')
return
}
imageLoading.value = true
await window.sd.txt2img(JSON.stringify([d]), async (value) => {
if (value.code == 0) {
message.error(value.message)
imageLoading.value = false
return
}
if (value.data && value.data.length > 0) {
let d = value.data[0]
characterData.value.show_image = d.image_path.replaceAll('\\', '/')
png_base64.value = 'data:image/png;base64,' + d.base64
console.log(characterData.value.show_image)
imageLoading.value = false
}
// 使 Vue.nextTick Vue
imageLoading.value = false
})
}
/**
* 选择本地图片
*/
async function SelecImage() {
await window.api.SelectFile(
[
'jpeg',
'jpg',
'png',
'gif',
'bmp',
'tiff',
'tif',
'webp',
'svg',
'raw',
'cr2',
'nef',
'arw'
],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
png_base64.value = null
characterData.value.show_image = value.value
}
)
}
return {
characterData,
SelecImage,
alias_tags,
SaveCharacterTag,
TranslatePrompt,
loading,
rules,
formRef,
lora_options,
imageLoading,
GenerateCharacterImage,
png_base64
}
}
})
</script>

View File

@ -0,0 +1,86 @@
<template>
<div style="width: 500px;">
<n-form label-placement="top">
<n-form-item label="前缀名称">
<n-input v-model:value="prefixData.label" placeholder="请输入场景名称" />
</n-form-item>
<n-form-item label="前缀提示词描述">
<n-input type="textarea" :rows="3" v-model:value="prefixData.prompt" placeholder="请输入前缀提示词描述" />
</n-form-item>
<!-- <n-form-item label="MJ风格垫图链接sref">
<n-input v-model:value="prefixData.image_url" placeholder="请输入风格垫图链接" style="margin-right: 20px;" />
<n-image v-if="prefixData.image_url" :src="prefixData.image_url" style="width: 80px; height: 80px" />
</n-form-item>
<n-form-item label="MJ风格迁移 cw 值0-1000">
<n-input-number :show-button="false" v-model:value="prefixData.sref_cw" min="0" max="1000"
placeholder="请输入人物风格迁移的CW值" />
</n-form-item>
<n-form-item label="SDLora选择">
<n-input v-model:value="prefixData.lora" placeholder="请输入风格Lora" />
<n-button color="#e18a3b" style="margin-left: 20px;">选择</n-button>
</n-form-item> -->
</n-form>
<div style="text-align: right;">
<n-button type="success" @click="SavePrefixTag">保存</n-button>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch } from "vue"
import { NImage, useMessage, NButton, useDialog, NInput, NForm, NFormItem, NDynamicTags, NInputNumber } from "naive-ui";
import { v4 as uuidv4 } from "uuid";
export default defineComponent({
components: {
NImage, NButton, NInput, NForm, NFormItem, NDynamicTags, NInputNumber
},
props: ["currentPrefix", "initFunc"],
setup(props) {
let message = useMessage();
let prefixData = ref(props.currentPrefix);
//
watch(() => props.currentPrefix, (value) => {
prefixData.value = value;
}, { deep: true })
onMounted(async () => {
})
async function SavePrefixTag() {
//
//
prefixData.value['type'] = "prefix_main";
await window.mj.SaveTagPropertyData([JSON.stringify(prefixData.value), "prefix_tags"], (value) => {
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
message.success(value.message);
//initFunc
props.initFunc();
//
prefixData.value = {
label: null,
key: null,
type: "prefix_main",
prompt: null,
// image_url: null,
// cref_cw: 20,
// lora: null,
};
})
}
return {
prefixData,
SavePrefixTag
}
}
})
</script>

View File

@ -0,0 +1,191 @@
<template>
<div style="width: 500px">
<n-form label-placement="top" ref="formRef" :rules="rules" :model="sceneData">
<n-form-item label="场景名称" path="label">
<n-input
style="width: 400px"
v-model:value="sceneData.label"
placeholder="请输入场景名称"
/>
<n-checkbox style="margin-left: 20px" v-model:checked="sceneData.isShow"
>显示</n-checkbox
>
</n-form-item>
<n-form-item label="场景提示词描述(中文)">
<n-input
type="textarea"
:rows="3"
v-model:value="sceneData.chinese_prompt"
placeholder="请输入场景提示词描述"
/>
<n-button
type="info"
size="small"
style="margin-left: 20px"
@click="TranslatePrompt"
:loading="loading"
>翻译提示词</n-button
>
</n-form-item>
<n-form-item label="场景提示词描述(英文)" path="prompt">
<n-input
type="textarea"
:rows="3"
v-model:value="sceneData.prompt"
placeholder="请输入场景提示词描述"
/>
</n-form-item>
<!-- <n-form-item label="MJ风格垫图链接sref">
<n-input v-model:value="sceneData.image_url" placeholder="请输入风格垫图链接" style="margin-right: 20px;" />
<n-image v-if="sceneData.image_url" :src="sceneData.image_url" style="width: 80px; height: 80px" />
</n-form-item>
<n-form-item label="MJ风格迁移 cw 值0-1000">
<n-input-number :show-button="false" v-model:value="sceneData.sref_cw" min="0" max="1000"
placeholder="请输入人物风格迁移的CW值" />
</n-form-item>
<n-form-item label="SDLora选择">
<n-input v-model:value="sceneData.lora" placeholder="请输入风格Lora" />
<n-button color="#e18a3b" style="margin-left: 20px;">选择</n-button>
</n-form-item> -->
</n-form>
<div style="text-align: right">
<n-button type="success" @click="SaveSceneTag">保存</n-button>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch } from 'vue'
import {
NImage,
useMessage,
NButton,
useDialog,
NInput,
NForm,
NFormItem,
NDynamicTags,
NInputNumber,
NCheckbox
} from 'naive-ui'
import { v4 as uuidv4 } from 'uuid'
export default defineComponent({
components: {
NImage,
NButton,
NInput,
NForm,
NFormItem,
NDynamicTags,
NInputNumber,
NCheckbox
},
props: ['currentScene', 'initFunc'],
setup(props) {
let message = useMessage()
let sceneData = ref(props.currentScene)
let loading = ref(false)
let formRef = ref(null)
//
watch(
() => props.currentScene,
(value) => {
sceneData.value = value
},
{ deep: true }
)
onMounted(async () => {})
//
async function SaveSceneTag(e) {
e.preventDefault()
formRef.value?.validate(async (errors) => {
if (errors) {
message.error('请检查必填字段')
return
}
//
//
sceneData.value['type'] = 'scene_main'
await window.mj.SaveTagPropertyData(
[JSON.stringify(sceneData.value), 'scene_tags'],
(value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
//initFunc
props.initFunc()
//
sceneData.value = {
label: null,
key: null,
type: 'scene_main',
prompt: null
// image_url: null,
// cref_cw: 20,
// lora: null,
}
}
)
})
}
//
async function TranslatePrompt() {
loading.value = true
if (sceneData.value.chinese_prompt == '' || sceneData.value.chinese_prompt == null) {
message.error('请输入中文提示词')
loading.value = false
return
}
await window.api.TranslateReturnNow(
[sceneData.value.chinese_prompt, 'zh', 'en', false],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
//
console.log(value.data)
sceneData.value.prompt = value.data[0].src
}
)
loading.value = false
}
let ruleObj = (errorMessage) => {
return [
{
required: true,
validator(rule, value) {
if (value == null || value == '') return new Error(errorMessage)
return true
},
trigger: ['input', 'blur', 'change']
}
]
}
let rules = {
label: ruleObj('必填人物名称'),
prompt: ruleObj('必填英文提示词')
}
return {
sceneData,
SaveSceneTag,
loading,
TranslatePrompt,
rules,
formRef
}
}
})
</script>

View File

@ -0,0 +1,327 @@
<template>
<div style="width: 500px">
<n-form label-placement="top" ref="formRef" :rules="rules" :model="styleData">
<n-form-item label="风格名称" path="label">
<n-input v-model:value="styleData.label" placeholder="请输入风格名称" />
<div style="position: absolute; left: 520px; top: 0; display: flex; flex-direction: column">
<n-image width="120" height="120" :src="png_base64 ? png_base64 : styleData.show_image" />
<n-button color="#e18a3b" style="margin-top: 5px" size="tiny" @click="SelecImage"
>本地上传图片</n-button
>
</div>
</n-form-item>
<n-form-item label="风格提示词描述(中文)">
<n-input
type="textarea"
:rows="2"
v-model:value="styleData.chinese_prompt"
placeholder="请输入风格提示词描述(中文)"
/>
<div style="width: 100px; margin-left: 20px">
<n-button type="info" size="tiny" @click="TranslatePrompt" :loading="loading"
>翻译提示词</n-button
>
<n-tooltip trigger="hover">
<template #trigger>
<n-button
color="#e18a3b"
size="tiny"
style="margin-top: 10px"
@click="GenerateStyleImage"
:loading="imageLoading"
>SD生成图片</n-button
>
</template>
使用SD出图模式生成风格图片提示词为1gril加上风格提示词
</n-tooltip>
</div>
</n-form-item>
<n-form-item label="风格提示词描述(英文)" path="prompt">
<n-input
type="textarea"
:rows="3"
v-model:value="styleData.prompt"
placeholder="请输入风格提示词描述(英文)"
/>
</n-form-item>
<n-form-item label="MJ风格垫图链接sref">
<n-input
v-model:value="styleData.image_url"
placeholder="请输入风格垫图链接"
style="margin-right: 20px"
/>
<n-image
v-if="styleData.image_url"
:src="styleData.image_url"
style="width: 80px; height: 80px"
/>
</n-form-item>
<n-form-item label="MJ风格迁移 sw 值0-1000">
<n-input-number
:show-button="false"
v-model:value="styleData.sref_sw"
min="0"
max="1000"
placeholder="请输入人物风格迁移的SW值"
/>
</n-form-item>
<n-form-item label="SDLora选择">
<n-select :options="lora_options" v-model:value="styleData.lora"> </n-select>
<n-input-number
style="width: 120px; margin-left: 10px"
placeholder="权重"
v-model:value="styleData.lora_weight"
:show-button="false"
:max="2"
:step="0.01"
:precision="2"
S
:min="0"
/>
</n-form-item>
</n-form>
<div style="text-align: right">
<n-button type="success" @click="SaveStyleTag">保存</n-button>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch } from 'vue'
import {
NImage,
useMessage,
NButton,
useDialog,
NInput,
NForm,
NFormItem,
NDynamicTags,
NInputNumber,
NSelect,
NTooltip
} from 'naive-ui'
import { isEmpty } from 'lodash'
export default defineComponent({
components: {
NImage,
NButton,
NInput,
NForm,
NFormItem,
NDynamicTags,
NInputNumber,
NSelect,
NTooltip
},
props: ['currentStyle', 'initFunc', 'lora_options'],
setup(props) {
let message = useMessage()
let styleData = ref(
props.currentStyle
? props.currentStyle
: { show_image: window.config.space_image, lora_weight: 1, sref_sw: 50, lora: '无' }
)
let loading = ref(false)
let imageLoading = ref(false)
let formRef = ref(null)
let lora_options = ref(props.lora_options)
let png_base64 = ref(null)
//
watch(
() => props.currentStyle,
(value) => {
styleData.value = value
},
{ deep: true }
)
onMounted(async () => {})
async function SaveStyleTag(e) {
e.preventDefault()
formRef.value?.validate(async (errors) => {
if (errors) {
message.error('请检查必填字段')
return
}
styleData.value['type'] = 'style_main'
styleData.value['show_image'] = styleData.value.show_image
? styleData.value.show_image.split('?t')[0]
: null
//
//
await window.mj.SaveTagPropertyData(
[JSON.stringify(styleData.value), 'style_tags'],
(value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
//initFunc
props.initFunc()
//
styleData.value = {
label: null,
key: null,
type: 'style_main',
prompt: null,
show_image: window.config.space_image,
image_url: null,
cref_cw: 20,
lora: '无'
}
png_base64.value = null
}
)
})
}
/**
* 翻译中文提示词
*/
async function TranslatePrompt() {
loading.value = true
if (styleData.value.chinese_prompt == '' || styleData.value.chinese_prompt == null) {
message.error('请输入中文提示词')
loading.value = false
return
}
await window.api.TranslateReturnNow(
[styleData.value.chinese_prompt, 'zh', 'en', false],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
console.log(value.data)
styleData.value.prompt = value.data[0].src
}
)
loading.value = false
}
let ruleObj = (errorMessage) => {
return [
{
required: true,
validator(rule, value) {
if (value == null || value == '') return new Error(errorMessage)
return true
},
trigger: ['input', 'blur', 'change']
}
]
}
let rules = {
label: ruleObj('必填人物名称')
}
/**
* 生成风格图片
*/
async function GenerateStyleImage() {
if (isEmpty(styleData.value.prompt)) {
message.error('请输入提示词')
imageLoading.value = false
return
}
// lora
let lora = ''
if (
styleData.value.lora != null &&
styleData.value.lora != '' &&
styleData.value.lora != '无'
) {
lora = `<lora:${styleData.value.lora}:${styleData.value.lora_weight}>`
}
let d = {
prompt: `1gril, ${styleData.value.prompt}, ${lora}`,
width: 1024,
height: 1024
}
imageLoading.value = true
await window.sd.txt2img(JSON.stringify([d]), async (value) => {
debugger
if (value.code == 0) {
message.error(value.message)
imageLoading.value = false
return
}
if (value.data && value.data.length > 0) {
let d = value.data[0]
styleData.value.show_image =
d.image_path.replaceAll('\\', '/') + '?t=' + new Date().getTime()
png_base64.value = 'data:image/png;base64,' + d.base64
console.log(styleData.value.show_image)
imageLoading.value = false
}
// 使 Vue.nextTick Vue
imageLoading.value = false
})
}
/**
* 选择本地的图片应用到风格图片
*/
async function SelecImage() {
await window.api.SelectFile(
[
'jpeg',
'jpg',
'png',
'gif',
'bmp',
'tiff',
'tif',
'webp',
'svg',
'raw',
'cr2',
'nef',
'arw'
],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
// Base64
fetch(value.value)
.then((response) => response.blob())
.then((blob) => {
const reader = new FileReader()
reader.onloadend = () => {
png_base64.value = reader.result
}
reader.readAsDataURL(blob)
})
//
png_base64.value = null
styleData.value.show_image = value.value + '?t=' + new Date().getTime()
}
)
}
return {
styleData,
SelecImage,
SaveStyleTag,
loading,
imageLoading,
rules,
formRef,
TranslatePrompt,
lora_options,
png_base64,
GenerateStyleImage
}
}
})
</script>

View File

@ -0,0 +1,86 @@
<template>
<div style="width: 500px;">
<n-form label-placement="top">
<n-form-item label="后缀名称">
<n-input v-model:value="suffixData.label" placeholder="请输入后缀名称" />
</n-form-item>
<n-form-item label="后缀提示词描述">
<n-input type="textarea" :rows="3" v-model:value="suffixData.prompt" placeholder="请输入后缀提示词描述" />
</n-form-item>
<!-- <n-form-item label="MJ风格垫图链接sref">
<n-input v-model:value="suffixData.image_url" placeholder="请输入风格垫图链接" style="margin-right: 20px;" />
<n-image v-if="suffixData.image_url" :src="suffixData.image_url" style="width: 80px; height: 80px" />
</n-form-item>
<n-form-item label="MJ风格迁移 cw 值0-1000">
<n-input-number :show-button="false" v-model:value="suffixData.sref_cw" min="0" max="1000"
placeholder="请输入人物风格迁移的CW值" />
</n-form-item>
<n-form-item label="SDLora选择">
<n-input v-model:value="suffixData.lora" placeholder="请输入风格Lora" />
<n-button color="#e18a3b" style="margin-left: 20px;">选择</n-button>
</n-form-item> -->
</n-form>
<div style="text-align: right;">
<n-button type="success" @click="SavePrefixTag">保存</n-button>
</div>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw, watch } from "vue"
import { NImage, useMessage, NButton, useDialog, NInput, NForm, NFormItem, NDynamicTags, NInputNumber } from "naive-ui";
import { v4 as uuidv4 } from "uuid";
export default defineComponent({
components: {
NImage, NButton, NInput, NForm, NFormItem, NDynamicTags, NInputNumber
},
props: ["currentSuffix", "initFunc"],
setup(props) {
let message = useMessage();
let suffixData = ref(props.currentSuffix);
//
watch(() => props.currentSuffix, (value) => {
suffixData.value = value;
}, { deep: true })
onMounted(async () => {
})
async function SavePrefixTag() {
//
//
suffixData.value['type'] = "suffix_main";
await window.mj.SaveTagPropertyData([JSON.stringify(suffixData.value), "suffix_tags"], (value) => {
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
message.success(value.message);
//initFunc
props.initFunc();
//
suffixData.value = {
label: null,
key: null,
type: "suffix_main",
prompt: null,
// image_url: null,
// cref_cw: 20,
// lora: null,
};
})
}
return {
suffixData,
SavePrefixTag
}
}
})
</script>

View File

@ -0,0 +1,118 @@
<template>
<div style="margin-top: 30px">
<n-card>
<n-tabs
class="card-tabs"
default-value="autoAnalyze"
size="large"
animated
pane-wrapper-style="margin: 0 -4px"
pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;"
>
<n-tab-pane name="autoAnalyze" tab="自动分析">
<n-button color="#ee7959" size="medium" @click="autoAnalyzeCharacter" :loading="loading"
>自动分析</n-button
>
<n-input
style="margin-top: 20px"
type="textarea"
:autosize="{
minRows: 10,
maxRows: 10
}"
placeholder="自动分析的数据"
v-model:value="autoCharacterString"
></n-input>
<div>
<div style="color: red">注意上面推理的不满意可以手动修改</div>
<div style="color: red">自动推理的的人物只能对内置的四种模式生效</div>
</div>
<div style="text-align: right">
<n-button type="success" @click="SaveAutoAnalyzeCharacter">保存</n-button>
</div>
</n-tab-pane>
<n-tab-pane name="Analyze" tab="手动设置标签集">
<CharacterTags :height="height"></CharacterTags>
</n-tab-pane>
</n-tabs>
</n-card>
</div>
</template>
<script setup>
import { ref, h, onMounted, toRaw } from 'vue'
import {
NImage,
useMessage,
NButton,
useDialog,
NInput,
NCard,
NTabs,
NTabPane,
NTree
} from 'naive-ui'
import { v4 as uuidv4 } from 'uuid'
import CharacterTags from './CharacterTags.vue'
import { useReverseManageStore } from '../../../../../../stores/reverseManage'
let reverseManageStore = useReverseManageStore()
let autoCharacterString = ref('')
let props = defineProps({
word: undefined,
height: undefined
})
let message = useMessage()
let word = ref(props.word)
let height = ref(props.height)
let loading = ref(false)
onMounted(async () => {
autoCharacterString.value = reverseManageStore.selectBookTask.autoAnalyzeCharacter
})
/**
* 自动分析角色
*/
async function autoAnalyzeCharacter() {
if (word.value == '' || word.value == null) {
message.error('请输入要分析的文案')
return
}
loading.value = true
await window.api.AutoAnalyzeCharacter(toRaw(word.value), (value) => {
loading.value = false
if (value.code == 0) {
message.error(value.message)
return
}
autoCharacterString.value = value.data
})
}
/**
* 保存自动分析的设置
*/
async function SaveAutoAnalyzeCharacter() {
if (autoCharacterString.value == '' || autoCharacterString.value == null) {
message.error('没有分析出来的人物信息')
return
}
//
reverseManageStore.selectBookTask.autoAnalyzeCharacter = autoCharacterString.value
let res = await window.db.UpdateBookTaskData(reverseManageStore.selectBookTask.id, {
autoAnalyzeCharacter: autoCharacterString.value
})
if (res.code == 1) {
message.success(res.message)
} else {
message.error(res.message)
}
}
</script>
<style scoped>
.card-tabs .n-tabs-nav--bar-type {
padding-left: 4px;
}
</style>

View File

@ -0,0 +1,470 @@
<template>
<div>
<n-split direction="horizontal" :max="0.3" :min="0.18" :default-size="0.18">
<template #1>
<div id="tree_define_content" style="display: flex; overflow-y: auto">
<n-tree :node-props="nodeProps" style="width: 200px" block-line :data="treeData" />
</div>
</template>
<template #2>
<div style="margin-left: 20px">
<AddCharacterTag
v-if="currentType.startsWith('character')"
:currentCharacter="currentCharacter"
:currentTags="currentTags"
:initFunc="InitData"
:lora_options="lora_options"
/>
<AddStyleTags
v-else-if="currentType.startsWith('style')"
:currentStyle="currentStyle"
:initFunc="InitData"
:lora_options="lora_options"
/>
<AddSceneTags
v-else-if="currentType.startsWith('scene')"
:currentScene="currentScene"
:initFunc="InitData"
/>
<AddPrefixTags
v-else-if="currentType.startsWith('prefix')"
:currentPrefix="currentPrefix"
:initFunc="InitData"
/>
<AddSuffixTags
v-else-if="currentType.startsWith('suffix')"
:currentSuffix="currentSuffix"
:initFunc="InitData"
/>
<n-empty v-else description="是一个飞机">
<template #icon>
<n-icon>
<airplane />
</n-icon>
</template>
</n-empty>
</div>
</template>
</n-split>
</div>
</template>
<script>
import { ref, h, onMounted, defineComponent, toRaw } from 'vue'
import {
NImage,
useMessage,
NButton,
useDialog,
NInput,
NCard,
NTabs,
NTabPane,
NTree,
NSplit,
NIcon,
NEmpty
} from 'naive-ui'
import { DEFINE_STRING } from '../../../../../../define/define_string'
import { v4 as uuidv4 } from 'uuid'
import AddCharacterTag from './AddCharacterTag.vue'
import AddStyleTags from './AddStyleTags.vue'
import AddSceneTags from './AddSceneTags.vue'
import AddPrefixTags from './AddPrefixTags.vue'
import AddSuffixTags from './AddSuffixTags.vue'
import { Trash, Airplane } from '@vicons/ionicons5'
export default defineComponent({
components: {
NImage,
NButton,
NInput,
NCard,
NTabs,
NTabPane,
NTree,
NSplit,
AddCharacterTag,
AddStyleTags,
AddSceneTags,
AddPrefixTags,
AddSuffixTags,
NIcon,
Trash,
NEmpty,
Airplane
},
props: ['height'],
setup(props) {
let message = useMessage()
let currentCharacter = ref({})
let currentStyle = ref({})
let currentScene = ref({})
let currentPrefix = ref({})
let currentSuffix = ref({})
let currentTags = ref([])
let currentType = ref('default')
let lora_options = ref([])
let treeData = ref([
{
label: '人物',
key: uuidv4(),
children: [],
type: 'character',
suffix: () =>
h(
NButton,
{
text: true,
color: '#e18a3b',
onClick: (e) => {
AddCharacterTags(e)
}
},
{ default: () => '新加' }
)
},
{
label: '场景',
key: uuidv4(),
children: [],
type: 'scene',
suffix: () =>
h(
NButton,
{
text: true,
color: '#e18a3b',
onClick: (e) => {
AddSceneTags(e)
}
},
{ default: () => '新加' }
)
},
{
label: '风格预设',
key: uuidv4(),
children: [],
type: 'style',
suffix: () =>
h(
NButton,
{
text: true,
color: '#e18a3b',
onClick: (e) => {
AddStyleTags(e)
}
},
{ default: () => '新加' }
)
},
{
label: '前缀',
key: uuidv4(),
children: [],
type: 'prefix',
suffix: () =>
h(
NButton,
{
text: true,
color: '#e18a3b',
onClick: (e) => {
AddPrefixTags(e)
}
},
{ default: () => '新加' }
)
},
{
label: '后缀',
key: uuidv4(),
children: [],
type: 'suffix',
suffix: () =>
h(
NButton,
{
text: true,
color: '#e18a3b',
onClick: (e) => {
AddSuffixTags(e)
}
},
{ default: () => '新加' }
)
}
])
/**
* 初始化标签数据
*/
async function InitData() {
//
await window.mj.GetTagDataByTypeAndProperty(['dynamic', null], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
//
if (value.data.hasOwnProperty('character_tags')) {
treeData.value[0].children = value.data.character_tags
}
if (value.data.hasOwnProperty('scene_tags')) {
treeData.value[1].children = value.data.scene_tags
}
if (value.data.hasOwnProperty('style_tags')) {
treeData.value[2].children = value.data.style_tags
}
if (value.data.hasOwnProperty('prefix_tags')) {
treeData.value[3].children = value.data.prefix_tags
}
if (value.data.hasOwnProperty('suffix_tags')) {
treeData.value[4].children = value.data.suffix_tags
}
for (let i = 0; i < treeData.value.length; i++) {
treeData.value[i].children.map((item) => {
item.suffix = () =>
h(
NButton,
{
text: true,
type: 'error',
size: 'medium',
onClick: (e) => {
DeleteTags(e, item)
}
},
{ default: () => h(NIcon, null, { default: () => h(Trash) }) }
)
})
}
})
// lora
// mj
await window.api.GetDefineConfigJsonByProperty(
JSON.stringify(['sd_setting', 'lora', false, []]),
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
//
if (value.data) {
for (let i = 0; i < value.data.length; i++) {
const element = value.data[i]
lora_options.value.push({
label: element.name,
value: element.name
})
}
}
}
)
}
onMounted(async () => {
SetAutoHeight()
await InitData()
})
//
function SetAutoHeight() {
let div = document.getElementById('tree_define_content')
div.style.height = props.height - 180 + 'px'
}
/**
* 删除指定的数据
*/
async function DeleteTags(e, value) {
if (e) {
e.stopPropagation()
}
let type = value.type.replace('main', 'tags')
await window.mj.DeleteTagPropertyData([value.key, type], async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
await InitData()
})
}
/**
* 新加人物标签
*/
async function AddCharacterTags(e) {
if (e) {
e.stopPropagation()
}
//
currentCharacter.value = {
label: null,
key: uuidv4(),
children: [],
type: 'character_main',
chinese_prompt: null,
prompt: null,
image_url: null,
show_image: window.config.space_image,
cref_cw: 50,
lora: '无',
lora_weight: 1,
isShow: false
}
currentTags.value = []
currentType.value = 'character_main'
}
/**
* 新加场景标签
*/
function AddSceneTags(e) {
if (e) {
e.stopPropagation()
}
currentScene.value = {
label: null,
key: uuidv4(),
type: 'scene_main',
prompt: null,
isShow: false
}
currentType.value = 'scene_main'
}
/**
* 新加 风格迁移预设
* @param {*} e
*/
function AddStyleTags(e) {
if (e) {
e.stopPropagation()
}
currentStyle.value = {
label: null,
key: uuidv4(),
type: 'style_main',
show_image: window.config.space_image,
prompt: null,
image_url: null,
sref_sw: 50,
lora: '无',
lora_weight: 1
}
}
/**
* 添加前缀
*/
function AddPrefixTags(e) {
if (e) {
e.stopPropagation()
}
currentPrefix.value = {
label: null,
key: uuidv4(),
type: 'prefix_main',
prompt: null
}
}
/**
* 添加后缀
* @param {*} e
*/
function AddSuffixTags(e) {
if (e) {
e.stopPropagation()
}
currentSuffix.value = {
label: null,
key: uuidv4(),
type: 'suffix_main',
prompt: null
}
}
function nodeProps({ option }) {
return {
onClick() {
//
if (option.type == 'style') {
currentType.value = 'style'
AddStyleTags(null)
return
} else if (option.type == 'scene') {
AddSceneTags(null)
currentType.value = 'scene'
// message.error("");
return
} else if (option.type == 'character') {
AddCharacterTags(null)
currentType.value = 'character'
return
} else if (option.type == 'prefix') {
AddPrefixTags(null)
currentType.value = 'prefix'
return
} else if (option.type == 'suffix') {
AddSuffixTags(null)
currentType.value = 'suffix'
return
} else if (option.type == 'min') {
message.error('别名节点不允许操作')
} else {
console.log(option)
currentType.value = option.type
//
if (currentType.value.startsWith('character')) {
// tags
currentCharacter.value = option
if (!currentCharacter.value.hasOwnProperty('isShow')) {
currentCharacter.value.isShow = false
}
currentTags.value = option.children.map((item) => {
return item.label
})
} else if (currentType.value.startsWith('scene')) {
currentScene.value = option
} else if (currentType.value.startsWith('style')) {
currentStyle.value = option
} else if (currentType.value.startsWith('prefix')) {
currentPrefix.value = option
} else if (currentType.value.startsWith('suffix')) {
currentSuffix.value = option
} else {
message.error('未知的类型')
}
}
}
}
}
return {
treeData,
AddCharacterTags,
AddSceneTags,
AddStyleTags,
nodeProps,
currentCharacter,
currentTags,
InitData,
currentType,
currentStyle,
currentScene,
currentPrefix,
currentSuffix,
lora_options
}
}
})
</script>

View File

@ -38,7 +38,7 @@ export default defineComponent({
{
title: 'No.',
key: 'name',
width: 80,
width: 50,
fixed: 'left'
},
{
@ -145,7 +145,7 @@ export default defineComponent({
debugger
//
let height = window.innerHeight
maxHeight.value = height - 120
maxHeight.value = height - 100
}
return {

Some files were not shown because too many files have changed in this diff Show More