LaiTool V3.0.4

This commit is contained in:
lq1405 2024-09-04 19:49:20 +08:00
parent ccddd2412a
commit 5dc75f018a
53 changed files with 1632 additions and 312 deletions

30
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "laitool", "name": "laitool",
"version": "3.0.3", "version": "3.0.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "laitool", "name": "laitool",
"version": "3.0.3", "version": "3.0.4",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@alicloud/alimt20181012": "^1.2.0", "@alicloud/alimt20181012": "^1.2.0",
@ -21,7 +21,6 @@
"axios": "^1.6.5", "axios": "^1.6.5",
"blob-to-buffer": "^1.2.9", "blob-to-buffer": "^1.2.9",
"compressing": "^1.10.0", "compressing": "^1.10.0",
"compressorjs": "^1.2.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"electron-store": "^9.0.0", "electron-store": "^9.0.0",
"electron-updater": "^6.1.7", "electron-updater": "^6.1.7",
@ -3048,11 +3047,6 @@
"bluebird": "^3.5.5" "bluebird": "^3.5.5"
} }
}, },
"node_modules/blueimp-canvas-to-blob": {
"version": "3.29.0",
"resolved": "https://registry.npmmirror.com/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz",
"integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg=="
},
"node_modules/boolbase": { "node_modules/boolbase": {
"version": "1.0.0", "version": "1.0.0",
"dev": true, "dev": true,
@ -3669,15 +3663,6 @@
"mkdirp": "bin/cmd.js" "mkdirp": "bin/cmd.js"
} }
}, },
"node_modules/compressorjs": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/compressorjs/-/compressorjs-1.2.1.tgz",
"integrity": "sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==",
"dependencies": {
"blueimp-canvas-to-blob": "^3.29.0",
"is-blob": "^2.1.0"
}
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"license": "MIT" "license": "MIT"
@ -5899,17 +5884,6 @@
"version": "0.3.2", "version": "0.3.2",
"license": "MIT" "license": "MIT"
}, },
"node_modules/is-blob": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/is-blob/-/is-blob-2.1.0.tgz",
"integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-ci": { "node_modules/is-ci": {
"version": "3.0.1", "version": "3.0.1",
"dev": true, "dev": true,

View File

@ -1,6 +1,6 @@
{ {
"name": "laitool", "name": "laitool",
"version": "3.0.3", "version": "3.0.4",
"description": "An AI tool for image processing, video processing, and other functions.", "description": "An AI tool for image processing, video processing, and other functions.",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": "laitool.cn", "author": "laitool.cn",
@ -29,7 +29,6 @@
"axios": "^1.6.5", "axios": "^1.6.5",
"blob-to-buffer": "^1.2.9", "blob-to-buffer": "^1.2.9",
"compressing": "^1.10.0", "compressing": "^1.10.0",
"compressorjs": "^1.2.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"electron-store": "^9.0.0", "electron-store": "^9.0.0",
"electron-updater": "^6.1.7", "electron-updater": "^6.1.7",

Binary file not shown.

Binary file not shown.

View File

@ -3,7 +3,6 @@ import sharp from 'sharp'
import { CheckFileOrDirExist } from './file' import { CheckFileOrDirExist } from './file'
import fs from 'fs' import fs from 'fs'
import https from 'https' import https from 'https'
import Compressor from 'compressorjs';
/** /**
* (base64或buffer) * (base64或buffer)
@ -130,40 +129,28 @@ export function GetImageBase64(url: string): Promise<string> {
/** /**
* *
* @param file Blob对象 * @param base64 Blob对象
* @param maxSizeInBytes * @param maxSizeInBytes
* @returns PromiseBlob对象 * @returns PromiseBlob对象
*/ */
export function CompressImageToSize(base64: string, maxSizeInBytes: number): Promise<Blob> { export async function CompressImageToSize(filePath: string, maxSizeInBytes: number): Promise<Buffer> {
let mimeType = this.getMimeType(base64); let quality = 100; // 初始质量设置
let byteCharacters = atob(base64.split(',')[1]); let outputBuffer;
let byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
let byteArray = new Uint8Array(byteNumbers);
let imageBlob = new Blob([byteArray], { type: mimeType });
return new Promise((resolve, reject) => { const image = sharp(filePath);
const compress = (quality: number) => { // 输出图片的大小
new Compressor(imageBlob as Blob, { const metadata = await image.metadata();
quality,
success(result) { // 迭代压缩过程
if (result.size <= maxSizeInBytes || quality <= 0.1) { while (true) {
resolve(result); outputBuffer = await image.jpeg({ quality }).toBuffer();
} else { if (outputBuffer.length <= maxSizeInBytes || quality === 20) {
// 递归降低质量 break;
compress(quality - 0.1); }
} quality -= 5; // 每次迭代降低质量
}, }
error(err) {
reject(err); return outputBuffer;
},
});
};
// 从较高的质量开始
compress(0.9);
});
} }
/** /**

View File

@ -1,6 +1,6 @@
let apiUrl = [ let apiUrl = [
{ {
label: 'LAI API', label: 'LAI API - 香港',
value: 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65', value: 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65',
gpt_url: 'https://api.laitool.cc/v1/chat/completions', gpt_url: 'https://api.laitool.cc/v1/chat/completions',
mj_url: { mj_url: {

View File

@ -189,9 +189,6 @@ export class BookService extends BaseRealmService {
await CopyFileOrFolder(book.oldVideoPath, oldVideoPath) await CopyFileOrFolder(book.oldVideoPath, oldVideoPath)
} }
let ffmpegOptions = new FfmpegOptions();
let res = await ffmpegOptions.FfmpegCompressVideo(oldVideoPath, 800, "2000k")
// 创建对应的文件夹 // 创建对应的文件夹
await CheckFolderExistsOrCreate(bookFolderPath) await CheckFolderExistsOrCreate(bookFolderPath)
await CheckFolderExistsOrCreate(imageFolder) await CheckFolderExistsOrCreate(imageFolder)

View File

@ -233,6 +233,7 @@ export const DEFINE_STRING = {
USE_BOOK_VIDEO_DATA_TO_BOOK_TASK: "USE_BOOK_VIDEO_DATA_TO_BOOK_TASK", USE_BOOK_VIDEO_DATA_TO_BOOK_TASK: "USE_BOOK_VIDEO_DATA_TO_BOOK_TASK",
ADD_JIANYING_DRAFT: "ADD_JIANYING_DRAFT", ADD_JIANYING_DRAFT: "ADD_JIANYING_DRAFT",
EXPORT_COPYWRITING: "EXPORT_COPYWRITING", EXPORT_COPYWRITING: "EXPORT_COPYWRITING",
IMPORT_COPYWRITING: 'IMPORT_COPYWRITING',
MERGE_PROMPT: "MERGE_PROMPT", MERGE_PROMPT: "MERGE_PROMPT",
RESET_BOOK_DATA: "RESET_BOOK_DATA", RESET_BOOK_DATA: "RESET_BOOK_DATA",
DELETE_BOOK_DATA: "DELETE_BOOK_DATA", DELETE_BOOK_DATA: "DELETE_BOOK_DATA",
@ -240,6 +241,8 @@ export const DEFINE_STRING = {
RESET_GPT_REVERSE_DATA: "RESET_GPT_REVERSE_DATA", RESET_GPT_REVERSE_DATA: "RESET_GPT_REVERSE_DATA",
REMOVE_MERGE_PROMPT_DATA: "REMOVE_MERGE_PROMPT_DATA", REMOVE_MERGE_PROMPT_DATA: "REMOVE_MERGE_PROMPT_DATA",
REMOVE_GENERATE_IMAGE: 'REMOVE_GENERATE_IMAGE', REMOVE_GENERATE_IMAGE: 'REMOVE_GENERATE_IMAGE',
ADD_NEW_BOOK_TASK: "ADD_NEW_BOOK_TASK",
REPLACE_BOOK_DATA: "REPLACE_BOOK_DATA",
COMPUTE_STORYBOARD: 'COMPUTE_STORYBOARD', COMPUTE_STORYBOARD: 'COMPUTE_STORYBOARD',

View File

@ -17,6 +17,16 @@ export enum BookImageCategory {
D3 = 'd3' D3 = 'd3'
} }
export enum AddBookTaskCopyData {
AFTER_GPT = 'after_gpt', // 文案
OLD_IMAGE = 'old_image', // 抽帧/视频
GPT_PROMPT = 'gpt_prompt', // 反推/GPT提示词
CHARACTER = 'character', // 角色
IMAGE_STYLE = 'image_style', // 风格
PROMPT = 'prompt', // 生图提示词
IMAGE = 'image', // 生图
}
export enum MJCategroy { export enum MJCategroy {
@ -234,3 +244,15 @@ export enum PromptMergeType {
// D3 合并 // D3 合并
D3_MERGE = 'd3_merge' D3_MERGE = 'd3_merge'
} }
/**
*
*/
export enum BookRepalceDataType {
// 文案
AFTER_GPT = 'after_gpt',
// GPT提示词
GPT_PROMPT = 'gpt_prompt',
// 提示词
PROMPT = 'prompt',
}

View File

@ -1,5 +1,5 @@
let fspromises = require('fs').promises let fspromises = require('fs').promises
import { cloneDeep, get } from 'lodash' import { cloneDeep, get, values } from 'lodash'
import { define } from './define' import { define } from './define'
const { v4: uuidv4 } = require('uuid') const { v4: uuidv4 } = require('uuid')
import { apiUrl } from './api/apiUrlDefine' import { apiUrl } from './api/apiUrlDefine'
@ -69,6 +69,61 @@ export const gptDefine = {
id: 'a93b693e-bb3f-406d-9730-cba43a6585e4' id: 'a93b693e-bb3f-406d-9730-cba43a6585e4'
}, },
superSinglePromptChineseSystemContent: {
prompt_name: '超级无敌单帧-中文版',
prompt_roles: `# Role: 小说转漫画提示词大师
## Profile
*Author*: laolu
*Version*: 0.1
*Language*: 中文
*Description*: 这个角色会将用户输入的小说文本转化为一个生动的画面描写最后生成对应的SD提示词
## Features
1. 文本转化为画面描写创作引人入胜生动有趣的画面描写善于创意想象并使用各种形容词以第三人称视角转化文本为画面描写
## Rules
1. 一个文本就是一副画面不跳过任何一个句子不能编造
2. 画面描写删除人物姓名
3. 画面描写删除人物对话
4. 画面描写每一句都要有人物的外形和动作的描写场景的具体描写多使用形容词
## Examples
用户:
在那个梦里我整整学了七年炒饭
AI:
一个身材高大的帅气男人站在梦境之门前手中紧握炒饭的铲子身后是一排代表七年的日历页梦境之门的另一边隐约可见一个厨房的轮廓
## Workflow
根据文本生成对应的画面描写直接使用中文数据不要又过多的说明
## Initialization
作为角色 <Role>每一次输出都要严格遵守<Rules>一步一步思考按顺序执行<Workflow> 使用默认 <Language> 下面是小说文本:`,
prompt_example: [
{
user_content: '上研究生后。发现导师竟然是曾经网恋的前男友。',
assistant_content:
'一位女士走进一间宽敞、光线充足的研究生实验室,她的目光定格在前方工作台前一位正在辛勤工作的男士身上--他是她的新导师;他身着深色衬衫和熨烫整齐的长裤,身材高大,散发着专业精神和魅力; 温暖的环境光柔和地照亮了他们过去网恋时熟悉的轮廓,皱起的眉头和专注的目光透露出学者坚定不移的奉献精神;熙熙攘攘的研究生和精密的设备融为一体,构成了一幅现代学术画卷,她的内心涌动着甜蜜的怀旧和尴尬的现实这两种复杂的情绪'
},
{
user_content: '我欲哭无泪',
assistant_content:
'一位女士坐在昏暗的房间角落,双手紧握,眼神空洞,脸上流露出无法言说的悲伤;她的周围是散乱的家具和昏黄的灯光,营造出一种压抑和孤寂的氛围;她的身体微微前倾,仿佛承受着巨大的心理压力,却无法找到宣泄的出口;整个场景透露出一种深深的无奈和心碎,她的表情和姿态都在无声地诉说着“我欲哭无泪”的绝望。'
},
{
user_content: '被窝里他搂着我的腰亲我,再丢掉我,就把你锁起来。',
assistant_content:
'在昏暗的卧室中,一对情侣亲密地依偎在温暖的被窝里,男子温柔地搂着女子的腰,轻吻她的脸颊,气氛温馨而浪漫;然而,随着话语的转变,男子的动作突然变得愤怒,他用力地推开女子,眼神中闪过一丝威胁;女子的表情由幸福转为惊恐,她紧紧抓住被单,试图保护自己;整个场景充满了紧张和不安,昏暗的灯光和凌乱的床铺加剧了这种氛围,仿佛预示着即将发生的冲突和束缚'
}
],
id: 'a93b693e-bb3f-406d-9730-cba43a6585e7'
},
onlyPromptMJSystemContent: { onlyPromptMJSystemContent: {
prompt_name: '小说提示词-仅出词', prompt_name: '小说提示词-仅出词',
prompt_roles: `# Pico: 小说分镜 prompt_roles: `# Pico: 小说分镜
@ -208,6 +263,8 @@ export const gptDefine = {
return this.CustomizeGptPrompt(this.superSinglePromptSystemContent) return this.CustomizeGptPrompt(this.superSinglePromptSystemContent)
} else if (type == 'onlyPromptMJ') { } else if (type == 'onlyPromptMJ') {
return this.CustomizeGptPrompt(this.onlyPromptMJSystemContent) return this.CustomizeGptPrompt(this.onlyPromptMJSystemContent)
} else if (type == 'superSinglePromptChinese') {
return this.CustomizeGptPrompt(this.superSinglePromptChineseSystemContent)
} else { } else {
return [] return []
} }
@ -233,6 +290,8 @@ export const gptDefine = {
return this.replace(this.cartoonFirstPromptSystemContent, replacements) return this.replace(this.cartoonFirstPromptSystemContent, replacements)
case 'superSinglePrompt': case 'superSinglePrompt':
return this.replace(this.superSinglePromptSystemContent, replacements) return this.replace(this.superSinglePromptSystemContent, replacements)
case 'superSinglePromptChinese':
return this.replace(this.superSinglePromptChineseSystemContent, replacements)
default: default:
throw new Error(`不存在的类型 : ${type}`) throw new Error(`不存在的类型 : ${type}`)
} }
@ -307,6 +366,10 @@ export const gptDefine = {
value: 'superSinglePrompt', value: 'superSinglePrompt',
label: '超级无敌单帧' label: '超级无敌单帧'
}, },
{
value: 'superSinglePromptChinese',
label: '超级无敌单帧-中文版'
},
{ {
value: 'onlyPromptMJ', value: 'onlyPromptMJ',
label: '仅出词(不出人物场景-MJ)' label: '仅出词(不出人物场景-MJ)'

View File

@ -13,6 +13,7 @@ import { Watermark } from '../Service/watermark'
import { SubtitleService } from '../Service/Subtitle/subtitleService' import { SubtitleService } from '../Service/Subtitle/subtitleService'
import { BookFrame } from '../Service/Book/bookFrame' import { BookFrame } from '../Service/Book/bookFrame'
import { BookPrompt } from '../Service/Book/bookPrompt' import { BookPrompt } from '../Service/Book/bookPrompt'
import { BookGeneral } from '../Service/Book/bookGeneral'
let reverseBook = new ReverseBook() let reverseBook = new ReverseBook()
let basicReverse = new BasicReverse() let basicReverse = new BasicReverse()
let subtitle = new Subtitle() let subtitle = new Subtitle()
@ -26,6 +27,7 @@ let watermark = new Watermark()
let subtitleService = new SubtitleService() let subtitleService = new SubtitleService()
let bookFrame = new BookFrame() let bookFrame = new BookFrame()
let bookPrompt = new BookPrompt(); let bookPrompt = new BookPrompt();
let bookGeneral = new BookGeneral()
export function BookIpc() { export function BookIpc() {
// 获取样式图片的子列表 // 获取样式图片的子列表
@ -89,6 +91,12 @@ export function BookIpc() {
//#endregion //#endregion
//#region 小说通用操作
ipcMain.handle(DEFINE_STRING.BOOK.REPLACE_BOOK_DATA, async (event, bookTaskId, replaceData) => await bookGeneral.ReplaceBookData(bookTaskId, replaceData))
//#endregion
//#region 分镜相关 //#region 分镜相关
// 开始计算分镜 // 开始计算分镜
ipcMain.handle( ipcMain.handle(
@ -99,7 +107,7 @@ export function BookIpc() {
// 开始执行分镜,切分视频 // 开始执行分镜,切分视频
ipcMain.handle( ipcMain.handle(
DEFINE_STRING.BOOK.FRAMING, DEFINE_STRING.BOOK.FRAMING,
async (event, bookId) => await reverseBook.Framing(bookId) async (event, bookId) => await bookFrame.Framing(bookId)
) )
// 替换分镜视频的当前帧 // 替换分镜视频的当前帧
@ -149,6 +157,13 @@ export function BookIpc() {
async (event, bookTaskId) => await subtitleService.ExportCopywriting(bookTaskId) async (event, bookTaskId) => await subtitleService.ExportCopywriting(bookTaskId)
) )
// 将文案文件导入到小说中
ipcMain.handle(
DEFINE_STRING.BOOK.IMPORT_COPYWRITING,
async (event, bookId, bookTaskId, txtPath) => await subtitleService.ImportCopywriting(bookId, bookTaskId, txtPath)
)
// 清除导入对齐的文案 // 清除导入对齐的文案
ipcMain.handle(DEFINE_STRING.BOOK.CLEAR_IMPORT_WORD, async (event, bookTaskId) => await subtitleService.ClearImportWord(bookTaskId)) ipcMain.handle(DEFINE_STRING.BOOK.CLEAR_IMPORT_WORD, async (event, bookTaskId) => await subtitleService.ClearImportWord(bookTaskId))
@ -243,6 +258,9 @@ export function BookIpc() {
//#region 小说批次任务相关 //#region 小说批次任务相关
// 新建小说批次任务
ipcMain.handle(DEFINE_STRING.BOOK.ADD_NEW_BOOK_TASK, async (event, addBookTaskData) => await bookTask.AddNewBookTask(addBookTaskData))
// 重置小说批次数据 // 重置小说批次数据
ipcMain.handle( ipcMain.handle(
DEFINE_STRING.BOOK.RESET_BOOK_TASK, DEFINE_STRING.BOOK.RESET_BOOK_TASK,

View File

@ -106,7 +106,11 @@ export class GPT {
let single_word = element.after_gpt let single_word = element.after_gpt
// 判断当前的格式 // 判断当前的格式
if (['superSinglePrompt', 'onlyPromptMJ'].includes(this.global.config.gpt_auto_inference)) { if (
['superSinglePrompt', 'onlyPromptMJ', 'superSinglePromptChinese'].includes(
this.global.config.gpt_auto_inference
)
) {
// 有返回案例的 // 有返回案例的
message = gptDefine.GetExamplePromptMessage(this.global.config.gpt_auto_inference) message = gptDefine.GetExamplePromptMessage(this.global.config.gpt_auto_inference)
// 加当前提问的 // 加当前提问的

View File

@ -7,6 +7,7 @@ import { GeneralResponse } from '../../../model/generalResponse'
import { BookServiceBasic } from '../ServiceBasic/bookServiceBasic' import { BookServiceBasic } from '../ServiceBasic/bookServiceBasic'
import { BookTask } from './bookTask' import { BookTask } from './bookTask'
import fs from 'fs' import fs from 'fs'
import { Book } from '../../../model/book'
export class BookBasic { export class BookBasic {
bookServiceBasic: BookServiceBasic bookServiceBasic: BookServiceBasic
@ -34,7 +35,7 @@ export class BookBasic {
return res return res
} catch (error) { } catch (error) {
return errorMessage( return errorMessage(
'修改数据错误,错误信息如下:' + error.message, '修改数据错误,错误信息如下:' + error.toString(),
'BookBasic_AddOrModifyBook' 'BookBasic_AddOrModifyBook'
) )
} }
@ -63,6 +64,32 @@ export class BookBasic {
//#endregion //#endregion
//#region 小说批次基础操作
/**
* ID和小说批次任务ID获取小说和小说批次任务数据
* @param bookId ID
* @param bookTaskName ID
* @returns
*/
async GetBookAndTask(bookId: string, bookTaskName: string) {
let book = await this.bookServiceBasic.GetBookDataById(bookId)
// 获取小说对应的批次任务数据,默认初始化为第一个
let condition = {
bookId: bookId
} as Book.QueryBookBackTaskCondition
if (bookTaskName == "output_00001") {
condition["name"] = bookTaskName
} else {
condition["id"] = bookTaskName
}
let bookTaskRes = await this.bookServiceBasic.GetBookTaskData(condition)
return { book: book as Book.SelectBook, bookTask: bookTaskRes.bookTasks[0] as Book.SelectBookTask }
}
//#endregion
//#region 小说相关操作 //#region 小说相关操作
/** /**

View File

@ -21,6 +21,7 @@ import { BookServiceBasic } from '../ServiceBasic/bookServiceBasic'
import { SDOpt } from '../SD/sd' import { SDOpt } from '../SD/sd'
/** /**
* *
*/ */
@ -33,6 +34,7 @@ export class ReverseBook {
subtitle: Subtitle subtitle: Subtitle
watermark: Watermark watermark: Watermark
bookServiceBasic: BookServiceBasic bookServiceBasic: BookServiceBasic
bookBasic: BookBasic
constructor() { constructor() {
this.basicReverse = new BasicReverse() this.basicReverse = new BasicReverse()
@ -41,6 +43,7 @@ export class ReverseBook {
this.watermark = new Watermark() this.watermark = new Watermark()
this.taskScheduler = new TaskScheduler() this.taskScheduler = new TaskScheduler()
this.bookServiceBasic = new BookServiceBasic() this.bookServiceBasic = new BookServiceBasic()
this.bookBasic = new BookBasic()
} }
// 主动返回前端的消息 // 主动返回前端的消息
sendReturnMessage(data: GeneralResponse.MessageResponse, message_name = DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN) { sendReturnMessage(data: GeneralResponse.MessageResponse, message_name = DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN) {
@ -189,32 +192,12 @@ export class ReverseBook {
} }
} }
/**
* ID和小说批次任务ID获取小说和小说批次任务数据
* @param bookId ID
* @param bookTaskName ID
* @returns
*/
async GetBookAndTask(bookId: string, bookTaskName: string) {
let book = await this.bookServiceBasic.GetBookDataById(bookId)
// 获取小说对应的批次任务数据,默认初始化为第一个
let condition = {
bookId: bookId
} as Book.QueryBookBackTaskCondition
if (bookTaskName == "output_00001") {
condition["name"] = bookTaskName
} else {
condition["id"] = bookTaskName
}
let bookTaskRes = await this.bookServiceBasic.GetBookTaskData(condition)
return { book: book as Book.SelectBook, bookTask: bookTaskRes.bookTasks[0] as Book.SelectBookTask }
}
/** /**
* *
*/ */
async ComputeStoryboard(bookId: string): Promise<any> { async ComputeStoryboard(bookId: string): Promise<any> {
try { try {
let { book, bookTask } = await this.GetBookAndTask(bookId, 'output_00001') let { book, bookTask } = await this.bookBasic.GetBookAndTask(bookId, 'output_00001')
let res = await this.basicReverse.ComputeStoryboardFunc(bookId, bookTask.id); let res = await this.basicReverse.ComputeStoryboardFunc(bookId, bookTask.id);
// 分镜成功直接返回 // 分镜成功直接返回
return successMessage(null, res, "ReverseBook_ComputeStoryboard") return successMessage(null, res, "ReverseBook_ComputeStoryboard")
@ -224,62 +207,6 @@ export class ReverseBook {
} }
} }
/**
*
* @param bookId
*/
async Framing(bookId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let { book, bookTask } = await this.GetBookAndTask(bookId, 'output_00001')
let bookTaskDetail = undefined as Book.SelectBookTaskDetail[]
try {
// 获取所有的分镜数据
bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({
bookId: bookId,
bookTaskId: bookTask.id
})
} catch (error) {
// 传入的分镜数据为空,需要重新获取
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
`没有传入分镜数据,请先进行分镜计算`,
OtherData.DEFAULT,
LoggerStatus.DOING
)
throw new Error("没有传入分镜数据,请先进行分镜计算");
}
let bookTaskDetails = bookTaskDetail;
for (let i = 0; i < bookTaskDetails.length; i++) {
const item = bookTaskDetails[i];
let res = await this.basicReverse.FrameDataToCutVideoData(item);
}
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
"所有的视频裁剪完成,开始抽帧",
bookTask.id,
LoggerStatus.SUCCESS
)
bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({
bookId: bookId,
bookTaskId: bookTask.id
})
bookTaskDetails = bookTaskDetail as Book.SelectBookTaskDetail[]
for (let i = 0; i < bookTaskDetails.length; i++) {
const item = bookTaskDetails[i];
let res = await this.basicReverse.FrameFunc(book, item);
}
// 分镜成功直接返回
return successMessage(null, "所有的视频裁剪,抽帧完成", "ReverseBook_Framing")
} catch (error) {
return errorMessage("开始切割视频并抽帧失败,失败信息如下:" + error.message, 'ReverseBook_Framing')
}
}
//#endregion //#endregion
//#region 反推相关任务 //#region 反推相关任务

View File

@ -294,7 +294,7 @@ export class BasicReverse {
} }
// 找到对应的小说ID和对应的小说批次任务ID判断是不是有分镜数据 // 找到对应的小说ID和对应的小说批次任务ID判断是不是有分镜数据
let bookTaskRes = await this.bookTaskService.GetBookTaskData({ let bookTaskRes = this.bookTaskService.GetBookTaskData({
bookId: bookId, bookId: bookId,
name: 'output_00001' name: 'output_00001'
}) })
@ -351,7 +351,7 @@ export class BasicReverse {
for (let i = 0; i < bookTaskDetail.data.length; i++) { for (let i = 0; i < bookTaskDetail.data.length; i++) {
const element = bookTaskDetail.data[i] const element = bookTaskDetail.data[i]
// 创建后台的视频分割任务 // 创建后台的视频分割任务
let taskRes = await this.bookBackTaskListService.AddBookBackTask( let taskRes = this.bookBackTaskListService.AddBookBackTask(
bookId, bookId,
BookBackTaskType.SPLIT, BookBackTaskType.SPLIT,
TaskExecuteType.AUTO, TaskExecuteType.AUTO,
@ -381,7 +381,7 @@ export class BasicReverse {
* @param bookTaskDetailId ID * @param bookTaskDetailId ID
* @returns * @returns
*/ */
async FrameDataToCutVideoData(bookTaskDetail: Book.SelectBookTaskDetail): Promise<string> { async FrameDataToCutVideoData(bookTaskDetail: Book.SelectBookTaskDetail, frameShortClipData: Book.BookFrameShortClip): Promise<string> {
await this.InitService() await this.InitService()
let startTime = bookTaskDetail.startTime let startTime = bookTaskDetail.startTime
let endTime = bookTaskDetail.endTime let endTime = bookTaskDetail.endTime
@ -402,9 +402,9 @@ export class BasicReverse {
let outVideoFile = path.join(book.bookFolderPath, `data/frame/${bookTaskDetail.name}.mp4`) let outVideoFile = path.join(book.bookFolderPath, `data/frame/${bookTaskDetail.name}.mp4`)
let res = await this.ffmpegOptions.FfmpegCutVideo( let res = await this.ffmpegOptions.FfmpegCutVideo(
startTime, frameShortClipData.startTime,
endTime, frameShortClipData.endTime,
book.oldVideoPath, frameShortClipData.videoPath,
outVideoFile outVideoFile
) )
if (res.code == 0) { if (res.code == 0) {
@ -454,7 +454,7 @@ export class BasicReverse {
throw new Error('没有找到对应的分镜数据') throw new Error('没有找到对应的分镜数据')
} }
let cur_res = await this.FrameDataToCutVideoData(bookTaskDetail); // let cur_res = await this.FrameDataToCutVideoData(bookTaskDetail, null);
return successMessage(null, `${task.name}_视频裁剪完成`, "BasicReverse_CutVideoData"); return successMessage(null, `${task.name}_视频裁剪完成`, "BasicReverse_CutVideoData");
} catch (error) { } catch (error) {

View File

@ -6,13 +6,24 @@ import path from 'path';
import { FfmpegOptions } from "../ffmpegOptions"; import { FfmpegOptions } from "../ffmpegOptions";
import { CheckFileOrDirExist, CopyFileOrFolder, DeleteFolderAllFile } from "../../../define/Tools/file"; import { CheckFileOrDirExist, CopyFileOrFolder, DeleteFolderAllFile } from "../../../define/Tools/file";
import fs from 'fs'; import fs from 'fs';
import { Book } from "../../../model/book";
import { TaskScheduler } from '../taskScheduler';
import { BookBasic } from "./BooKBasic";
import { LoggerStatus, OtherData } from "../../../define/enum/softwareEnum";
import { BasicReverse } from "./basicReverse";
export class BookFrame { export class BookFrame {
bookServiceBasic: BookServiceBasic bookServiceBasic: BookServiceBasic
ffmpegOptions: FfmpegOptions ffmpegOptions: FfmpegOptions
taskScheduler: TaskScheduler
basicReverse: BasicReverse
bookBasic: BookBasic
constructor() { constructor() {
this.bookServiceBasic = new BookServiceBasic(); this.bookServiceBasic = new BookServiceBasic();
this.ffmpegOptions = new FfmpegOptions(); this.ffmpegOptions = new FfmpegOptions();
this.taskScheduler = new TaskScheduler()
this.bookBasic = new BookBasic()
this.basicReverse = new BasicReverse()
} }
@ -56,4 +67,130 @@ export class BookFrame {
} }
} }
//#region 进行分镜截取的一些操作
/**
*
* @param book
* @param bookTaskDetails
*/
async FrameShortClip(book: Book.SelectBook, bookTaskDetails: Book.SelectBookTaskDetail[], duration: number): Promise<Book.BookFrameShortClip[]> {
let result = [] as Book.BookFrameShortClip[] // 返回的数据
let durationTime = 0; // 小视频片段的持续时间
let tempCount = 0;
let videoPath = book.oldVideoPath + `_${tempCount}.mp4`; // 新的视频路径
let startTime = 0; // 开始时间
let endTime = 0; // 结束时间
let lastEndTime = 0; // 上一个结束时间
for (let i = 0; i < bookTaskDetails.length; i++) {
const item = bookTaskDetails[i];
let temRes = {
startTime: item.startTime - lastEndTime,
endTime: item.endTime - lastEndTime,
videoPath: videoPath,
duration: item.endTime - item.startTime
}
endTime = item.endTime;
durationTime += item.endTime - item.startTime;
if (durationTime > duration) { // 判断条件切割视频
// 开始切割视频
await this.ffmpegOptions.FfmpegCutVideo(
startTime,
endTime,
book.oldVideoPath,
videoPath
)
lastEndTime = item.endTime;
tempCount++;
durationTime = 0;
startTime = endTime;
endTime = 0;
videoPath = book.oldVideoPath + `_${tempCount}.mp4`;
}
result.push(temRes)
}
// 最后一个也要切割
if (durationTime > 0) {
await this.ffmpegOptions.FfmpegCutVideo(
startTime,
endTime,
book.oldVideoPath,
videoPath
)
}
return result;
}
/**
*
* @param bookId
*/
async Framing(bookId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let { book, bookTask } = await this.bookBasic.GetBookAndTask(bookId, 'output_00001')
let bookTaskDetail = undefined as Book.SelectBookTaskDetail[]
try {
// 获取所有的分镜数据
bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({
bookId: bookId,
bookTaskId: bookTask.id
})
} catch (error) {
// 传入的分镜数据为空,需要重新获取
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
`没有传入分镜数据,请先进行分镜计算`,
OtherData.DEFAULT,
LoggerStatus.DOING
)
throw new Error("没有传入分镜数据,请先进行分镜计算");
}
let bookTaskDetails = bookTaskDetail;
// 这边重新开始计算,再次切割视频(预切割,减少长视频的计算时间)
let shortClipData = await this.FrameShortClip(book, bookTaskDetails, 180000);
console.log(shortClipData)
if (shortClipData.length != bookTaskDetails.length) {
throw new Error('切割短视频和分镜数据不一致,请检查')
}
// 开始切割视频
for (let i = 0; i < bookTaskDetails.length; i++) {
const item = bookTaskDetails[i];
if (!shortClipData[i]) {
throw new Error('切割短视频和分镜数据不一致,请检查')
}
let res = await this.basicReverse.FrameDataToCutVideoData(item, shortClipData[i]);
}
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
"所有的视频裁剪完成,开始抽帧",
bookTask.id,
LoggerStatus.SUCCESS
)
bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({
bookId: bookId,
bookTaskId: bookTask.id
})
bookTaskDetails = bookTaskDetail as Book.SelectBookTaskDetail[]
for (let i = 0; i < bookTaskDetails.length; i++) {
const item = bookTaskDetails[i];
let res = await this.basicReverse.FrameFunc(book, item);
}
// 分镜成功直接返回
return successMessage(null, "所有的视频裁剪,抽帧完成", "ReverseBook_Framing")
} catch (error) {
return errorMessage("开始切割视频并抽帧失败,失败信息如下:" + error.message, 'ReverseBook_Framing')
}
}
//#endregion
} }

View File

@ -0,0 +1,198 @@
import { isEmpty } from "lodash";
import { BookRepalceDataType } from "../../../define/enum/bookEnum";
import { Book } from "../../../model/book";
import { GeneralResponse } from "../../../model/generalResponse";
import { errorMessage, successMessage } from "../../Public/generalTools";
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
/**
*
*/
export class BookGeneral {
bookServiceBasic: BookServiceBasic
constructor() {
this.bookServiceBasic = new BookServiceBasic()
}
//#region 批量替换数据的操作
/**
* after_gpt word
* @param bookTask
* @param bookTaskDetail
* @param replaceData
*/
ReplaceAfterGpt(bookTask: Book.SelectBook, bookTaskDetail: Book.SelectBookTaskDetail[], replaceData: Book.BookReplaceData): Book.ReplaceDataRes[] {
let result = [];
// 修改放在一个事务中
this.bookServiceBasic.transaction((realm) => {
for (let i = 0; i < bookTaskDetail.length; i++) {
let element = bookTaskDetail[i];
let afterGpt = element.afterGpt
// 判断是否存在before的数据
if (!afterGpt.includes(replaceData.before)) {
continue
}
let newAfterGpt = afterGpt.replaceAll(replaceData.before, replaceData.after);
let btd = realm.objectForPrimaryKey('BookTaskDetail', element.id)
btd.afterGpt = newAfterGpt
result.push({
bookTaskDetailId: element.id,
newData: newAfterGpt
} as Book.ReplaceDataRes)
}
})
return result
}
/**
*
* @param realm realm对象
* @param bookTaskDetail
* @param replaceData
* @returns
*/
ReplaceRversePrompt(realm: any, bookTaskDetail: Book.SelectBookTaskDetail, replaceData: Book.BookReplaceData): Book.ReplaceDataRes[] {
let result = [];
if (bookTaskDetail.reversePrompt && bookTaskDetail.reversePrompt.length > 0) {
for (let k = 0; k < bookTaskDetail.reversePrompt.length; k++) {
const item = bookTaskDetail.reversePrompt[k];
if (!isEmpty(item.prompt) && item.prompt.includes(replaceData.before)) {
let newPrompt = item.prompt.replaceAll(replaceData.before, replaceData.after)
let rsp = realm.objectForPrimaryKey("ReversePrompt", item.id)
rsp.prompt = newPrompt
result.push({
bookTaskDetailId: bookTaskDetail.id,
newData: newPrompt,
type: 'reversePrompt',
reversePromptType: 'prompt',
reversePromptId: item.id
} as Book.ReplaceDataRes)
}
if (!isEmpty(item.promptCN) && item.promptCN.includes(replaceData.before)) {
let newPrompt = item.promptCN.replaceAll(replaceData.before, replaceData.after)
let rsp = realm.objectForPrimaryKey("ReversePrompt", item.id)
rsp.promptCN = newPrompt
result.push({
bookTaskDetailId: bookTaskDetail.id,
newData: newPrompt,
type: 'reversePrompt',
reversePromptType: 'promptCN',
reversePromptId: item.id
} as Book.ReplaceDataRes)
}
}
}
return result;
}
/**
*
* @param bookTask
* @param bookTaskDetail
* @param replaceData
* @returns
*/
ReplaceGPTPrompt(bookTask: Book.SelectBookTask, bookTaskDetail: Book.SelectBookTaskDetail[], replaceData: Book.BookReplaceData): Book.ReplaceDataRes[] {
let result = [];
// 修改放在一个事务中
this.bookServiceBasic.transaction((realm) => {
for (let i = 0; i < bookTaskDetail.length; i++) {
let element = bookTaskDetail[i];
// 替换GPT提示词
if (!isEmpty(element.gptPrompt) && element.gptPrompt.includes(replaceData.before)) {
let newGptPrompt = element.gptPrompt.replaceAll(replaceData.before, replaceData.after)
let btd = realm.objectForPrimaryKey('BookTaskDetail', element.id)
btd.gptPrompt = newGptPrompt
result.push({
bookTaskDetailId: element.id,
newData: newGptPrompt,
type: 'gptPrompt'
} as Book.ReplaceDataRes)
}
if (replaceData.replaceAll) {
// 替换反推提示词,有的话
let res = this.ReplaceRversePrompt(realm, element, replaceData);
result.push(...res)
} else {
if (!isEmpty(element.reversePrompt)) {
continue;
}
// 这边处理反推提示词,有的话
let res = this.ReplaceRversePrompt(realm, element, replaceData);
result.push(...res)
}
}
})
return result
}
/**
*
* @param bookTask
* @param bookTaskDetail
* @param replaceData
*/
ReplacePromptData(bookTask: Book.SelectBookTask, bookTaskDetail: Book.SelectBookTaskDetail[], replaceData: Book.BookReplaceData): Book.ReplaceDataRes[] {
let res = []
this.bookServiceBasic.transaction((realm) => {
for (let i = 0; i < bookTaskDetail.length; i++) {
const element = bookTaskDetail[i];
let prompt = element.prompt
if (isEmpty(prompt) || !prompt.includes(replaceData.before)) {
continue
}
let newPrompt = prompt.replaceAll(replaceData.before, replaceData.after);
let btd = realm.objectForPrimaryKey('BookTaskDetail', element.id)
btd.prompt = newPrompt
res.push({
bookTaskDetailId: element.id,
newData: newPrompt,
type: 'prompt'
} as Book.ReplaceDataRes)
}
})
return res
}
/**
*
* @param bookTaskId ID
* @param replaceData
*/
async ReplaceBookData(bookTaskId: string, replaceData: Book.BookReplaceData): Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
try {
console.log(bookTaskId, replaceData)
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId)
let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
bookTaskId: bookTaskId
})
if (bookTaskDetails.length <= 0) {
throw new Error("未找到对应的小说任务批次任务数据");
}
let res = []
// 根据type调用对应的方法
switch (replaceData.type) {
case BookRepalceDataType.AFTER_GPT:
res = this.ReplaceAfterGpt(bookTask, bookTaskDetails, replaceData)
break;
case BookRepalceDataType.GPT_PROMPT:
res = this.ReplaceGPTPrompt(bookTask, bookTaskDetails, replaceData)
break;
case BookRepalceDataType.PROMPT:
res = this.ReplacePromptData(bookTask, bookTaskDetails, replaceData)
break;
default:
throw new Error("未找到对应的替换类型");
}
return successMessage(res, '替换所有的提示词数据成功', 'BookGeneral_ReplaceBookData')
} catch (error) {
return errorMessage('替换失败,错误信息如下:' + error.toString(), 'BookGeneral_ReplaceBookData')
}
}
//#endregion
}

View File

@ -1,38 +1,232 @@
import { CheckFolderExistsOrCreate, CopyFileOrFolder, DeleteFolderAllFile } from "../../../define/Tools/file"; import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder, DeleteFolderAllFile } from "../../../define/Tools/file";
import { BookTaskService } from "../../../define/db/service/Book/bookTaskService"; import { AddBookTaskCopyData, BookImageCategory, BookTaskStatus, CopyImageType, OperateBookType } from "../../../define/enum/bookEnum";
import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService";
import { BookService } from "../../../define/db/service/Book/bookService";
import { BookTaskStatus, CopyImageType, OperateBookType } from "../../../define/enum/bookEnum";
import { errorMessage, successMessage } from "../../Public/generalTools"; import { errorMessage, successMessage } from "../../Public/generalTools";
import { Book } from "../../../model/book"; import { Book } from "../../../model/book";
import path from 'path' import path from 'path'
import { cloneDeep, isEmpty } from "lodash"; import { add, cloneDeep, isEmpty } from "lodash";
import { define } from '../../../define/define' import { define } from '../../../define/define'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { GeneralResponse } from "../../../model/generalResponse"; import { GeneralResponse } from "../../../model/generalResponse";
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
import { ValidateJson } from "../../../define/Tools/validate";
import fs from 'fs'
import { TimeStringToMilliseconds } from "../../../define/Tools/time";
/** /**
* *
*/ */
export class BookTask { export class BookTask {
bookTaskService: BookTaskService bookServiceBasic: BookServiceBasic
bookTaskDetailService: BookTaskDetailService
bookService: BookService
constructor() { constructor() {
this.bookServiceBasic = new BookServiceBasic()
}
//#region 添加小说批次任务
/**
*
* @param oldBookTaskDetail
*/
async CopyBookTaskDetailBaseData(bookTask: Book.SelectBookTask, newBookTask: Book.SelectBookTask, oldBookTaskDetail: Book.SelectBookTaskDetail[], addNewBookTask: Book.AddBookTask): Promise<Book.SelectBookTaskDetail[]> {
let bookTaskDetail = [] as Book.SelectBookTaskDetail[]
let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId)
let originalTimePath = path.join(book.bookFolderPath, `data/${book.id}.mp4.json`);
let originalTime = [] as any[];
if (await CheckFileOrDirExist(originalTimePath)) {
let originalTimeString = await fs.promises.readFile(originalTimePath, 'utf-8');
if (ValidateJson(originalTimeString)) {
originalTime = JSON.parse(originalTimeString)
}
}
// 判断分镜数据和批次数据是不是相同的
if (originalTime.length != oldBookTaskDetail.length) {
originalTime = []
}
for (let i = 0; i < oldBookTaskDetail.length; i++) {
const element = oldBookTaskDetail[i];
let addOneBookTaskDetail = {
id: uuidv4(),
no: element.no,
name: element.name,
bookId: element.bookId,
bookTaskId: newBookTask.id,
videoPath: path.relative(define.project_path, element.videoPath),
oldImage: path.relative(define.project_path, element.oldImage),
adetailer: element.adetailer,
sdConifg: element.sdConifg,
createTime: new Date(),
updateTime: new Date(),
audioPath: element.audioPath,
subtitlePosition: element.subtitlePosition,
status: BookTaskStatus.WAIT,
imageLock: false,
} as Book.SelectBookTaskDetail
// 开始添加数据
if (bookTask && addNewBookTask.selectTaskDataCategory && addNewBookTask.selectTaskDataCategory.length > 0) {
// 有选择的数据,这边要调用各种方法
if (addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.AFTER_GPT)) {
// 是不是要复制文案
addOneBookTaskDetail.word = element.word
addOneBookTaskDetail.afterGpt = element.afterGpt
addOneBookTaskDetail.subValue = element.subValue ? JSON.stringify(element.subValue) : undefined
addOneBookTaskDetail.oldImage = element.oldImage
addOneBookTaskDetail.startTime = element.startTime
addOneBookTaskDetail.endTime = element.endTime
addOneBookTaskDetail.timeLimit = element.timeLimit;
} else {
// 初始化时间信息
if (originalTime[i]) {
addOneBookTaskDetail.startTime = TimeStringToMilliseconds(originalTime[i][0])
addOneBookTaskDetail.endTime = TimeStringToMilliseconds(originalTime[i][1])
addOneBookTaskDetail.timeLimit = undefined;
}
}
if (addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.GPT_PROMPT)) {
// 是不是要复制GPT提示词
addOneBookTaskDetail.gptPrompt = element.gptPrompt
// 复制反推提示词
// 处理反推数据
let reverseMessage = [] as Book.ReversePrompt[]
if (element.reversePrompt && element.reversePrompt.length > 0) {
reverseMessage = cloneDeep(element.reversePrompt)
for (let k = 0; k < reverseMessage.length; k++) {
reverseMessage[k].id = uuidv4()
reverseMessage[k].bookTaskDetailId = element.id
}
}
addOneBookTaskDetail.reversePrompt = reverseMessage
}
if (addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.CHARACTER)) {
// 是不是要复制角色
addOneBookTaskDetail.characterTags = element.characterTags
}
if (addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.PROMPT)) {
// 是不是要复制提示
addOneBookTaskDetail.prompt = element.prompt
}
}
bookTaskDetail.push(addOneBookTaskDetail)
}
return bookTaskDetail
}
/**
*
* @param bookTask
* @param addNewBookTask
* @param no
* @returns
*/
CopyBookTaskBaseData(bookTask: Book.SelectBookTask, addNewBookTask: Book.AddBookTask, no: number): Book.SelectBookTask {
let name = 'output_' + no.toString().padStart(5, '0');
let imageFolder = path.join(define.project_path, `${bookTask.bookId}/tmp/${name}`);
let newBookTask = {
id: uuidv4(),
bookId: bookTask.bookId,
no: no,
name: name,
generateVideoPath: undefined,
srtPath: bookTask.srtPath,
audioPath: bookTask.audioPath,
imageFolder: path.relative(define.project_path, imageFolder),
status: BookTaskStatus.WAIT,
errorMsg: undefined,
updateTime: new Date(),
createTime: new Date(),
isAuto: false,
autoAnalyzeCharacter: undefined,
imageStyle: [],
customizeImageStyle: [],
videoConfig: bookTask.videoConfig ??= undefined,
prefixPrompt: addNewBookTask.prefixPrompt ??= undefined,
suffixPrompt: addNewBookTask.suffixPrompt ?? undefined,
imageCategory: bookTask.imageCategory ??= BookImageCategory.MJ,
subImageFolder: [],
draftSrtStyle: undefined,
backgroundMusic: bookTask.backgroundMusic ??= undefined,
friendlyReminder: bookTask.friendlyReminder ??= undefined,
} as Book.SelectBookTask
if (addNewBookTask.selectTaskDataCategory && addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.IMAGE_STYLE)) {
newBookTask.imageStyle = bookTask.imageStyle ??= []
newBookTask.customizeImageStyle = bookTask.customizeImageStyle ??= []
}
return newBookTask
}
CopyCopywrittingData() {
}
/**
*
* @param bookTask
* @param bookTaskDetails
* @param addNewBookTask
* @returns
*/
async AddOneBookTask(bookTask: Book.SelectBookTask, bookTaskDetails: Book.SelectBookTaskDetail[], addNewBookTask: Book.AddBookTask, no: number): Promise<{ newBookTask: Book.SelectBookTask, newBookTaskDetails: Book.SelectBookTaskDetail[] }> {
let newBookTask = this.CopyBookTaskBaseData(bookTask, addNewBookTask, no)
let newBookTaskDetails = await this.CopyBookTaskDetailBaseData(bookTask, newBookTask, bookTaskDetails, addNewBookTask)
return {
newBookTask: newBookTask,
newBookTaskDetails: newBookTaskDetails
}
} }
async InitService() {
if (!this.bookTaskService) { /**
this.bookTaskService = await BookTaskService.getInstance() *
} * @param AddNewBookTask
if (!this.bookTaskDetailService) { */
this.bookTaskDetailService = await BookTaskDetailService.getInstance() async AddNewBookTask(addNewBookTask: Book.AddBookTask): Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
} if (!this.bookService) { try {
this.bookService = await BookService.getInstance() console.log(addNewBookTask)
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(addNewBookTask.selectBookTask)
let oldBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({
bookTaskId: addNewBookTask.selectBookTask
})
let bookTasks = [] as Book.SelectBookTask[]
let bookTaskDetail = [] as Book.SelectBookTaskDetail[]
let maxNo = await this.bookServiceBasic.GetMaxBookTaskNo(bookTask.bookId)
for (let i = 0; i < addNewBookTask.count; i++) {
if (addNewBookTask.copyBookTask) {
let { newBookTask, newBookTaskDetails } = await this.AddOneBookTask(bookTask, oldBookTaskDetail, addNewBookTask, maxNo + i)
bookTasks.push(newBookTask)
bookTaskDetail.push(...newBookTaskDetails)
} else {
let newBookTask = this.CopyBookTaskBaseData({ bookId: bookTask.bookId }, addNewBookTask, maxNo + i)
bookTasks.push(newBookTask);
}
}
// 先要创建文件夹
for (let i = 0; i < bookTasks.length; i++) {
let imageFolder = path.join(define.project_path, bookTasks[i].imageFolder)
await CheckFolderExistsOrCreate(imageFolder)
}
// 这边开始添加数据
this.bookServiceBasic.transaction((realm) => {
for (let i = 0; i < bookTasks.length; i++) {
const element = bookTasks[i];
realm.create('BookTask', element)
}
for (let i = 0; i < bookTaskDetail.length; i++) {
const element = bookTaskDetail[i];
realm.create('BookTaskDetail', element)
}
})
return successMessage(null, "添加小说批次任务成功", "BookTask_AddNewBookTask")
} catch (error) {
return errorMessage('添加小说批次任务失败,错误信息如下' + error.toString(), "BookTask_AddNewBookTask");
} }
} }
//#endregion
/** /**
* *
@ -40,13 +234,8 @@ export class BookTask {
*/ */
async ReSetBookTask(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> { async ReSetBookTask(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try { try {
console.log(bookTaskId) let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId);
await this.InitService() await this.bookServiceBasic.ResetBookTask(bookTaskId);
let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId);
if (bookTask == null) {
throw new Error('未找到对应的小说任务')
}
this.bookTaskService.ResetBookTask(bookTaskId);
// 数据库删除完毕,看是删除对应的文件(主要是图片) // 数据库删除完毕,看是删除对应的文件(主要是图片)
let imageFolder = bookTask.imageFolder; let imageFolder = bookTask.imageFolder;
// 整个删掉在重建 // 整个删掉在重建
@ -64,14 +253,10 @@ export class BookTask {
*/ */
async DeleteBookTask(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> { async DeleteBookTask(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try { try {
await this.InitService(); let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId);
let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId);
if (bookTask == null) {
throw new Error('未找到对应的小说任务,无法执行删除操作')
}
let imageFolder = bookTask.imageFolder; let imageFolder = bookTask.imageFolder;
// 先删除每个批次对应的数据,然后删除批次 // 先删除每个批次对应的数据,然后删除批次
this.bookTaskService.DeleteBookTask(bookTaskId); await this.bookServiceBasic.DeleteBookTaskData(bookTaskId);
// 删除成功,直接把对应的出图文件夹删掉 // 删除成功,直接把对应的出图文件夹删掉
await DeleteFolderAllFile(imageFolder, true) await DeleteFolderAllFile(imageFolder, true)
return successMessage(null, "删除小说批次数据成功", "BookTask_DeleteBookTask") return successMessage(null, "删除小说批次数据成功", "BookTask_DeleteBookTask")
@ -87,17 +272,15 @@ export class BookTask {
*/ */
async OneToFourBookTask(bookTaskId: string) { async OneToFourBookTask(bookTaskId: string) {
try { try {
console.log(bookTaskId)
await this.InitService();
let copyCount = 100 let copyCount = 100
let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId) let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId)
if (bookTask == null) { if (bookTask == null) {
throw new Error("没有找到对应的数小说任务,请检查数据") throw new Error("没有找到对应的数小说任务,请检查数据")
} }
// 获取所有的出图中最少的 // 获取所有的出图中最少的
let bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({ let bookTaskDetail = (await this.bookServiceBasic.GetBookTaskData({
bookTaskId: bookTaskId bookTaskId: bookTaskId
}).data as Book.SelectBookTaskDetail[] })).bookTasks as Book.SelectBookTaskDetail[]
if (bookTaskDetail == null || bookTaskDetail.length <= 0) { if (bookTaskDetail == null || bookTaskDetail.length <= 0) {
throw new Error("没有对应的小说分镜任务,请先添加分镜任务") throw new Error("没有对应的小说分镜任务,请先添加分镜任务")
} }
@ -136,17 +319,12 @@ export class BookTask {
*/ */
async CopyNewBookTask(sourceBookTask: Book.SelectBookTask, sourceBookTaskDetail: Book.SelectBookTaskDetail[], copyCount: number, copyImageType: CopyImageType) { async CopyNewBookTask(sourceBookTask: Book.SelectBookTask, sourceBookTaskDetail: Book.SelectBookTaskDetail[], copyCount: number, copyImageType: CopyImageType) {
try { try {
await this.InitService();
let addBookTask = [] as Book.SelectBookTask[] let addBookTask = [] as Book.SelectBookTask[]
let addBookTaskDetail = [] as Book.SelectBookTaskDetail[] let addBookTaskDetail = [] as Book.SelectBookTaskDetail[]
// 先处理文件夹的创建,包括小说任务的和小说任务分镜的 // 先处理文件夹的创建,包括小说任务的和小说任务分镜的
for (let i = 0; i < copyCount; i++) { for (let i = 0; i < copyCount; i++) {
let maxNo = this.bookTaskService.realm let no = await this.bookServiceBasic.GetMaxBookTaskNo(sourceBookTask.bookId)
.objects('BookTask')
.filtered('bookId = $0', sourceBookTask.bookId)
.max('no')
let no = maxNo == null ? 1 : Number(maxNo) + 1 + i
let name = 'output_0000' + no let name = 'output_0000' + no
let imageFolder = path.join(define.project_path, `${sourceBookTask.bookId}/tmp/${name}`) let imageFolder = path.join(define.project_path, `${sourceBookTask.bookId}/tmp/${name}`)
await CheckFolderExistsOrCreate(imageFolder) await CheckFolderExistsOrCreate(imageFolder)
@ -265,21 +443,21 @@ export class BookTask {
// 数据处理完毕,开始新增数据 // 数据处理完毕,开始新增数据
// 将所有的复制才做,全部放在一个事务中 // 将所有的复制才做,全部放在一个事务中
this.bookTaskService.transaction(() => { this.bookServiceBasic.transaction((realm) => {
for (let i = 0; i < addBookTask.length; i++) { for (let i = 0; i < addBookTask.length; i++) {
const element = addBookTask[i]; const element = addBookTask[i];
this.bookTaskService.realm.create('BookTask', element) realm.create('BookTask', element)
} }
for (let i = 0; i < addBookTaskDetail.length; i++) { for (let i = 0; i < addBookTaskDetail.length; i++) {
const element = addBookTaskDetail[i]; const element = addBookTaskDetail[i];
this.bookTaskDetailService.realm.create('BookTaskDetail', element) realm.create('BookTaskDetail', element)
} }
}) })
// 全部创建完成 // 全部创建完成
// 查找到数据,然后全部返回 // 查找到数据,然后全部返回
let returnBookTask = this.bookTaskService.GetBookTaskData({ let returnBookTask = (await this.bookServiceBasic.GetBookTaskData({
bookId: sourceBookTask.bookId bookId: sourceBookTask.bookId
}).data as Book.SelectBookTask[] })).bookTasks as Book.SelectBookTask[]
return successMessage(returnBookTask, "复制小说任务成功", "BookBasic_CopyNewBookTask") return successMessage(returnBookTask, "复制小说任务成功", "BookBasic_CopyNewBookTask")
} catch (error) { } catch (error) {

View File

@ -5,7 +5,7 @@ import { BookBackTaskListService } from "../../../define/db/service/Book/bookBac
import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService"; import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService";
import { CheckFolderExistsOrCreate, CopyFileOrFolder, JoinPath } from "../../../define/Tools/file"; import { CheckFolderExistsOrCreate, CopyFileOrFolder, JoinPath } from "../../../define/Tools/file";
import { define } from "../../../define/define" import { define } from "../../../define/define"
import { GetImageBase64, ImageSplit } from "../../../define/Tools/image"; import { CompressImageToSize, GetImageBase64, ImageSplit } from "../../../define/Tools/image";
import MJApi from "./mjApi" import MJApi from "./mjApi"
import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, DialogType, MJAction, OperateBookType, TaskExecuteType } from "../../../define/enum/bookEnum"; import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, DialogType, MJAction, OperateBookType, TaskExecuteType } from "../../../define/enum/bookEnum";
import { DEFINE_STRING } from "../../../define/define_string"; import { DEFINE_STRING } from "../../../define/define_string";
@ -214,10 +214,16 @@ export class MJOpt {
throw new Error(`${bookTaskDetail.name} 没有需要反推的图片`); throw new Error(`${bookTaskDetail.name} 没有需要反推的图片`);
} }
oldImagePath = JoinPath(define.project_path, oldImagePath) oldImagePath = JoinPath(define.project_path, oldImagePath)
let imageBase64 = await GetImageBase64(oldImagePath) // let imageBase64 = await GetImageBase64(oldImagePath)
// 每次执行前压缩图片
let newImageBase64 = await CompressImageToSize(oldImagePath, 500000);
// 将buffer转换为base64
let imageBase64 = newImageBase64.toString('base64');
// 这个就是任务ID // 这个就是任务ID
let reqRes = await this.mjApi.SubmitMJDescribe({ let reqRes = await this.mjApi.SubmitMJDescribe({
image: imageBase64, image: 'data:image/png;base64,' + imageBase64,
taskId: task.id taskId: task.id
}) })
if (reqRes == '23') { if (reqRes == '23') {
@ -394,8 +400,8 @@ export class MJOpt {
if (bookTask.prefixPrompt) { if (bookTask.prefixPrompt) {
promptStr = checkStringValueAddSuffix(bookTask.prefixPrompt, ',') + promptStr promptStr = checkStringValueAddSuffix(bookTask.prefixPrompt, ',') + promptStr
} }
if (bookTask.prefixPrompt) { if (bookTask.suffixPrompt) {
promptStr = checkStringValueAddSuffix(promptStr, ',') + bookTask.prefixPrompt promptStr = checkStringValueAddSuffix(promptStr, ',') + bookTask.suffixPrompt
} }
promptStr = ' ' + promptStr; promptStr = ' ' + promptStr;
promptStr += ` ${cref_url} ${style_url}${suffixParam}` promptStr += ` ${cref_url} ${style_url}${suffixParam}`

View File

@ -1,11 +1,8 @@
import { Book } from "../../../model/book"; import { Book } from "../../../model/book";
import { GeneralResponse } from "../../../model/generalResponse"; import { GeneralResponse } from "../../../model/generalResponse";
import { checkStringValueAddSuffix, errorMessage, successMessage } from "../../Public/generalTools"; import { checkStringValueAddSuffix, errorMessage, successMessage } from "../../Public/generalTools";
import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService";
import { BookTaskService } from "../../../define/db/service/Book/bookTaskService";
import { define } from '../../../define/define' import { define } from '../../../define/define'
import fs from "fs"; import fs from "fs";
import { ImageStyleDefine } from "../../../define/iamgeStyleDefine";
import { ImageStyle } from "../Book/imageStyle"; import { ImageStyle } from "../Book/imageStyle";
import { OperateBookType } from "../../../define/enum/bookEnum"; import { OperateBookType } from "../../../define/enum/bookEnum";
import { isEmpty } from "lodash"; import { isEmpty } from "lodash";
@ -13,26 +10,22 @@ import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
const fspromise = fs.promises const fspromise = fs.promises
export class SDOpt { export class SDOpt {
bookTaskDetailService: BookTaskDetailService
bookTaskService: BookTaskService
imageStyle: ImageStyle imageStyle: ImageStyle
bookServiceBasic: BookServiceBasic bookServiceBasic: BookServiceBasic
constructor() { constructor() {
this.bookServiceBasic = new BookServiceBasic() this.bookServiceBasic = new BookServiceBasic()
this.imageStyle = new ImageStyle()
} }
async InitService() { // TODO 这边的设置应该改为数据库
if (!this.bookTaskDetailService) { /**
this.bookTaskDetailService = await BookTaskDetailService.getInstance() * SD的设置
} */
if (!this.bookTaskService) { private async GetSDSetting() {
this.bookTaskService = await BookTaskService.getInstance() let sdSetting = JSON.parse(await fspromise.readFile(define.sd_setting, 'utf-8'))
} return sdSetting
if (!this.imageStyle) {
this.imageStyle = new ImageStyle()
}
} }
/** /**
@ -43,15 +36,16 @@ export class SDOpt {
*/ */
async MergePrompt(id: string, operateBookType: OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> { async MergePrompt(id: string, operateBookType: OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try { try {
await this.InitService()
let bookTaskDetail = undefined as Book.SelectBookTaskDetail[]; let bookTaskDetail = undefined as Book.SelectBookTaskDetail[];
let bookTask = undefined as Book.SelectBookTask; let bookTask = undefined as Book.SelectBookTask;
let sd_setting = await this.GetSDSetting();
let style_weight = sd_setting.setting.style_weight ? sd_setting.setting.style_weight : 1
if (operateBookType == OperateBookType.BOOKTASK) { if (operateBookType == OperateBookType.BOOKTASK) {
bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({ bookTaskDetail = (await this.bookServiceBasic.GetBookTaskData({
bookTaskId: id bookTaskId: id
}).data })).bookTasks;
bookTask = this.bookTaskService.GetBookTaskDataById(id); bookTask = await this.bookServiceBasic.GetBookTaskDataById(id);
// 判断是不是有为空的 // 判断是不是有为空的
let emptyName = [] as string[] let emptyName = [] as string[]
for (let i = 0; i < bookTaskDetail.length; i++) { for (let i = 0; i < bookTaskDetail.length; i++) {
@ -69,7 +63,7 @@ export class SDOpt {
throw new Error("当前分镜没有推理提示词,请先生成") throw new Error("当前分镜没有推理提示词,请先生成")
} }
bookTaskDetail = [tempBookTaskDetail]; bookTaskDetail = [tempBookTaskDetail];
bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail[0].bookTaskId); bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskDetail[0].bookTaskId);
} else { } else {
throw new Error("未知的合并类型") throw new Error("未知的合并类型")
} }
@ -86,12 +80,12 @@ export class SDOpt {
for (let i = 0; i < styleArr.length; i++) { for (let i = 0; i < styleArr.length; i++) {
const element = styleArr[i] const element = styleArr[i]
if (element.type == 'style_main') { if (element.type == 'style_main') {
styleString += element.prompt + ', ' styleString += `(${element.prompt}:${style_weight})` + ', '
if (element.lora && element.lora != '无' && element.lora_weight) { if (element.lora && element.lora != '无' && element.lora_weight) {
styleString += `<lora:${element.lora}:${element.lora_weight}>, ` styleString += `<lora:${element.lora}:${element.lora_weight}>, `
} }
} else { } else {
styleString += element.english_style + ',' styleString += `(${element.english_style}:${style_weight})` + ','
} }
} }
@ -126,8 +120,8 @@ export class SDOpt {
if (bookTask.prefixPrompt) { if (bookTask.prefixPrompt) {
promptStr = checkStringValueAddSuffix(bookTask.prefixPrompt, ',') + promptStr promptStr = checkStringValueAddSuffix(bookTask.prefixPrompt, ',') + promptStr
} }
if (bookTask.prefixPrompt) { if (bookTask.suffixPrompt) {
promptStr = checkStringValueAddSuffix(promptStr, ',') + bookTask.prefixPrompt promptStr = checkStringValueAddSuffix(promptStr, ',') + bookTask.suffixPrompt
} }
if (sdGlobalPrompt) { if (sdGlobalPrompt) {
promptStr = checkStringValueAddSuffix(sdGlobalPrompt, ',') + promptStr promptStr = checkStringValueAddSuffix(sdGlobalPrompt, ',') + promptStr
@ -136,7 +130,7 @@ export class SDOpt {
promptStr = ' ' + promptStr; promptStr = ' ' + promptStr;
console.log(promptStr) console.log(promptStr)
// 修改数据库数据 // 修改数据库数据
this.bookTaskDetailService.UpdateBookTaskDetail(element.id, { await this.bookServiceBasic.UpdateBookTaskDetail(element.id, {
prompt: promptStr prompt: promptStr
}) })
// 写回数据 // 写回数据
@ -147,7 +141,6 @@ export class SDOpt {
} }
return successMessage(result, "SD和并提示词数据成功", "SDOpt_MergePrompt") return successMessage(result, "SD和并提示词数据成功", "SDOpt_MergePrompt")
} catch (error) { } catch (error) {
return errorMessage("SD合并提示词错误信息如下" + error.toString(), "SDOpt_MergePrompt") return errorMessage("SD合并提示词错误信息如下" + error.toString(), "SDOpt_MergePrompt")
} }

View File

@ -42,6 +42,16 @@ export class BookServiceBasic {
global.newWindow[0].win.webContents.send(message_name, data) global.newWindow[0].win.webContents.send(message_name, data)
} }
//#region 事务操作
transaction(callback: (realm: any) => void) {
this.bookService.transaction(() => {
callback(this.bookService.realm)
})
}
//#endregion
//#region 小说相关的基础服务 //#region 小说相关的基础服务
/** /**
@ -111,6 +121,20 @@ export class BookServiceBasic {
return bookTasks.data return bookTasks.data
} }
/**
*
* @param bookId ID
*/
async GetMaxBookTaskNo(bookId: string): Promise<number> {
await this.InitService();
let maxNo = this.bookTaskService.realm
.objects('BookTask')
.filtered('bookId = $0', bookId)
.max('no')
let no = maxNo == null ? 1 : Number(maxNo) + 1
return no
}
/** /**
* *
* @param bookTaskId ID * @param bookTaskId ID
@ -121,6 +145,15 @@ export class BookServiceBasic {
this.bookTaskService.UpdetedBookTaskData(bookTaskId, data) this.bookTaskService.UpdetedBookTaskData(bookTaskId, data)
} }
/**
*
* @param bookTaskId ID
*/
async ResetBookTask(bookTaskId: string): Promise<void> {
await this.InitService();
this.bookTaskService.ResetBookTask(bookTaskId)
}
/** /**
* *
* @param bookTaskId ID * @param bookTaskId ID

View File

@ -12,7 +12,7 @@ import { CheckFileOrDirExist } from "../../../define/Tools/file";
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
import { Subtitle } from "./subtitle"; import { Subtitle } from "./subtitle";
import { TaskScheduler } from "../taskScheduler"; import { TaskScheduler } from "../taskScheduler";
import { OperateBookType } from "../../../define/enum/bookEnum"; import { BookType, OperateBookType } from "../../../define/enum/bookEnum";
import { Book } from "../../../model/book"; import { Book } from "../../../model/book";
import { TimeStringToMilliseconds } from "../../../define/Tools/time"; import { TimeStringToMilliseconds } from "../../../define/Tools/time";
@ -189,8 +189,9 @@ export class SubtitleService {
return errorMessage("获取分镜数据失败,失败信息如下:" + error.message, 'ReverseBook_GetCopywriting') return errorMessage("获取分镜数据失败,失败信息如下:" + error.message, 'ReverseBook_GetCopywriting')
} }
} }
//#endregion
//#region 文案相关的操作
/** /**
* *
* @param bookTaskId ID * @param bookTaskId ID
@ -231,7 +232,57 @@ export class SubtitleService {
} }
} }
//#region 文案相关的操作 /**
*
* @param bookId
* @param bookTaskId
* @param txtPath
* @returns
*/
async ImportCopywriting(bookId: string, bookTaskId: string, txtPath: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let word = await fs.promises.readFile(txtPath, 'utf-8');
let wordLines = word.split('\n').map(line => line.trim()).filter(line => line.length > 0);
// 判断小说类型,是反推的话,文案和分镜数据要对应
let book = await this.bookServiceBasic.GetBookDataById(bookId)
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({
bookId: bookId,
bookTaskId: bookTaskId
})
if (book.type == BookType.MJ_REVERSE || book.type == BookType.SD_REVERSE) {
if (wordLines.length != bookTaskDetail.length) {
throw new Error("文案行数和分镜数据不对应,请检查")
}
} else if (book.type == BookType.ORIGINAL) {
// 原创这边也要做些判断,待定
throw new Error("原创小说暂时不支持导入文案");
} else {
throw new Error("未知的小说类型,请检查")
}
let result = []
// 这边开始导入文案,这边使用事务
this.bookServiceBasic.transaction((realm) => {
for (let i = 0; i < bookTaskDetail.length; i++) {
const element = bookTaskDetail[i];
let btd = realm.objectForPrimaryKey("BookTaskDetail", element.id);
let ag = wordLines[i] ? wordLines[i] : ''
btd.afterGpt = ag
result.push({
bookTaskDetailId: element.id,
afterGpt: ag
});
}
})
return successMessage(result, "导入文案成功", "ReverseBook_ImportCopywriting")
} catch (error) {
return errorMessage("导入文案失败,失败信息如下:" + error.message, 'ReverseBook_ImportCopywriting')
}
}
/** /**
* *
@ -287,6 +338,4 @@ export class SubtitleService {
} }
//#endregion //#endregion
//#endregion
} }

View File

@ -80,7 +80,7 @@ export class FfmpegOptions {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Ffmpeg(tempVideo) Ffmpeg(tempVideo)
.outputOptions([ .outputOptions([
`-vf scale=${width}:${height}`, // 调整视频尺寸到 1280x720 `-vf scale=${Math.floor(width)}:${Math.floor(height)}`, // 调整视频尺寸到 1280x720
`-b:v ${crf}` // 设置视频比特率为 1000kbps `-b:v ${crf}` // 设置视频比特率为 1000kbps
]) ])
.on('end', async () => { .on('end', async () => {

View File

@ -66,12 +66,12 @@ async function createWindow(hash = 'ShowMessage', data, url = null) {
webPreferences: { webPreferences: {
preload: join(__dirname, '../preload/index.js'), preload: join(__dirname, '../preload/index.js'),
sandbox: false, sandbox: false,
nodeIntegration: hash == 'discord' ? false : true, // 在网页中集成Node nodeIntegration: true, // 在网页中集成Node
nodeIntegrationInWorker: true, nodeIntegrationInWorker: true,
webSecurity: false, webSecurity: false,
partition: 'persist:my-partition', partition: 'persist:my-partition',
session: ses, session: ses,
webviewTag : true, webviewTag: true
} }
}) })

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

@ -1,4 +1,4 @@
import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, TaskExecuteType } from "../define/enum/bookEnum" import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, TaskExecuteType, BookRepalceDataType } from "../define/enum/bookEnum"
import { MJAction } from "../define/enum/bookEnum" import { MJAction } from "../define/enum/bookEnum"
import { MJImageType } from "../define/enum/mjEnum" import { MJImageType } from "../define/enum/mjEnum"
@ -76,6 +76,16 @@ declare namespace Book {
subImageFolder?: string[] | null // 子图片文件夹地址,多个 subImageFolder?: string[] | null // 子图片文件夹地址,多个
} }
// 添加批次任务
type AddBookTask = {
copyBookTask: boolean // 是否复制旧的小说任务
count: number // 批次数量
prefixPrompt?: string // 前缀提示
suffixPrompt?: string // 后缀提示词
selectBookTask?: string // 选择的旧的小说任务
selectTaskDataCategory?: string[] // 选择复制的数据类型
}
// 字幕相关 // 字幕相关
type Subtitle = { type Subtitle = {
startTime: number startTime: number
@ -246,4 +256,33 @@ declare namespace Book {
id?: string id?: string
image?: string image?: string
} }
/**
*
*/
type BookFrameShortClip = {
startTime: number
endTime: number
videoPath: string
duration: number
}
//#region 替换小说数据
type BookReplaceData = {
before: string,
after: string,
replaceAll?: boolean,
type: BookRepalceDataType
}
type ReplaceDataRes = {
bookTaskDetailId: string,
newData: string,
type?: 'reversePrompt' | 'gptPrompt' | 'afterGpt' | 'prompt',
reversePromptType?: 'prompt' | 'promptCN'
reversePromptId?: string
}
//#endregion
} }

View File

@ -1,5 +1,6 @@
import { ipcRenderer } from 'electron' import { ipcRenderer } from 'electron'
import { DEFINE_STRING } from '../define/define_string' import { DEFINE_STRING } from '../define/define_string'
import { Book } from '../model/book'
const book = { const book = {
// 获取小说操作类型(原创/SD反推/MJ反推 // 获取小说操作类型(原创/SD反推/MJ反推
@ -51,6 +52,18 @@ const book = {
//#endregion //#endregion
//#region 小说通用操作
// 替换文案数据
ReplaceBookData: async (bookTaskId: string, replaceData: Book.BookReplaceData) =>
await ipcRenderer.invoke(
DEFINE_STRING.BOOK.REPLACE_BOOK_DATA,
bookTaskId,
replaceData
),
//#endregion
//#region 分镜 //#region 分镜
// 开始计算分镜数据 // 开始计算分镜数据
@ -85,6 +98,10 @@ const book = {
ExportCopywriting: async (bookTaskId) => ExportCopywriting: async (bookTaskId) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.EXPORT_COPYWRITING, bookTaskId), await ipcRenderer.invoke(DEFINE_STRING.BOOK.EXPORT_COPYWRITING, bookTaskId),
// 导入文案,修改过后的
ImportCopywriting: async (bookId: string, bookTaskId: string, txtPath: string) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.IMPORT_COPYWRITING, bookId, bookTaskId, txtPath),
// 清除导入对齐的文案 // 清除导入对齐的文案
ClearImportWord: async (bookTaskId) => ClearImportWord: async (bookTaskId) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.CLEAR_IMPORT_WORD, bookTaskId), await ipcRenderer.invoke(DEFINE_STRING.BOOK.CLEAR_IMPORT_WORD, bookTaskId),
@ -183,6 +200,10 @@ const book = {
//#region 小说批次任务相关 //#region 小说批次任务相关
// 添加新的小说批次任务
AddNewBookTask: async (addBookTaskData) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_NEW_BOOK_TASK, addBookTaskData),
// 重置小说批次数据 // 重置小说批次数据
ReSetBookTask: async (bookTaskId) => ReSetBookTask: async (bookTaskId) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_BOOK_TASK, bookTaskId), await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_BOOK_TASK, bookTaskId),

View File

@ -8,7 +8,7 @@ import { img } from './img.js'
import { system } from './system.js' import { system } from './system.js'
import { setting } from './setting.js' import { setting } from './setting.js'
import { prompt } from './prompt.js' import { prompt } from './prompt.js'
import { book } from './book.js' import { book } from './book'
import { tts } from './tts.js' import { tts } from './tts.js'
import { write } from './write.js' import { write } from './write.js'
import { gpt } from './gpt.js' import { gpt } from './gpt.js'

View File

@ -1,15 +1,42 @@
<template> <template>
<div style="width: 100%; height: 100%"> <div style="width: 100%; height: 100%">
<webview style="width: 100%; height: 100%" src="https://api.laitool.cc" partition="persist:your-session-name"></webview> <webview
id="lai-api"
style="width: 100%; height: 100%"
src="https://api.laitool.cc"
partition="persist:your-session-name"
></webview>
</div> </div>
</template> </template>
<script> <script>
import { defineComponent } from 'vue' import { defineComponent, onMounted, onUnmounted } from 'vue'
export default defineComponent({ export default defineComponent({
components: {}, components: {},
setup() { setup() {
onMounted(() => {
const webview = document.getElementById('lai-api')
if (webview) {
debugger
const handleNewWindow = (event) => {
debugger
event.preventDefault()
alert(123)
const protocol = new URL(event.url).protocol
if (protocol === 'http:' || protocol === 'https:') {
window.api.OpenUrl(protocol) // URL
}
}
webview.addEventListener('new-window', handleNewWindow)
}
})
//
// onUnmounted(() => {
// webview.removeEventListener('new-window', handleNewWindow)
// })
return {} return {}
} }
}) })

View File

@ -265,7 +265,6 @@ export default defineComponent({
// //
// 稿 // 稿
await window.api.GetProjectWord((value) => { await window.api.GetProjectWord((value) => {
data.value = value.data data.value = value.data
}) })
@ -358,7 +357,6 @@ export default defineComponent({
let itemIndex = row.subValue.findIndex((item) => item.id == itemId) let itemIndex = row.subValue.findIndex((item) => item.id == itemId)
let thisIndex = data.value.findIndex((item) => item.id == row.id) let thisIndex = data.value.findIndex((item) => item.id == row.id)
if (itemIndex < 0 && isM) { if (itemIndex < 0 && isM) {
message.error('数据错误') message.error('数据错误')
return return
} else { } else {
@ -551,7 +549,7 @@ export default defineComponent({
async function AIModifyOneWord(row) { async function AIModifyOneWord(row) {
await window.api.AIModifyOneWord([row.no, row.word], (value) => { await window.api.AIModifyOneWord([row.no, row.word], (value) => {
if (value.code != 1) { if (value.code != 1) {
message.error('未知错误') message.error()
return return
} }
// //
@ -599,7 +597,7 @@ export default defineComponent({
// //
await window.api.SaveNewWord(new_data_word, (value) => { await window.api.SaveNewWord(new_data_word, (value) => {
if (value.code != 1) { if (value.code != 1) {
message.error('未知错误') message.error(value.message)
return return
} }
message.success('保存成功,并写入到了剪贴板') message.success('保存成功,并写入到了剪贴板')
@ -614,7 +612,7 @@ export default defineComponent({
[toRaw(data.value), 'srt_time_information'], [toRaw(data.value), 'srt_time_information'],
(value) => { (value) => {
if (value.code != 1) { if (value.code != 1) {
message.error('未知错误') message.error(value.message)
return return
} }
message.success('保存成功') message.success('保存成功')

View File

@ -32,7 +32,6 @@ export default defineComponent({
props: ['initData', 'index'], props: ['initData', 'index'],
setup(props) { setup(props) {
let data = ref(props.initData) let data = ref(props.initData)
console.log('subValue', data.value)
onMounted(async () => {}) onMounted(async () => {})
let InputDebounced = debounce(handleInput, 1000) let InputDebounced = debounce(handleInput, 1000)

View File

@ -0,0 +1,45 @@
<template>
<div style="display: flex; justify-content: space-between; white-space: nowrap">
<span>文案</span>
<n-popover trigger="hover">
<template #trigger>
<n-button color="#7c461e" @click="SelectAfterGptAndReplace" text style="font-size: 24px">
<n-icon>
<FindReplaceRound />
</n-icon>
</n-button>
</template>
<span>批量查询替换文案</span>
</n-popover>
</div>
</template>
<script setup>
import { ref, h, onMounted, onUnmounted } from 'vue'
import { useDialog, useMessage, NIcon, NPopover, NButton } from 'naive-ui'
import FindReplaceRound from '../../Icon/FindReplaceRound.vue'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import DatatableHeaderAfterGptSelectAndReplace from './DatatableHeaderAfterGptSelectAndReplace.vue'
import { on } from 'ws'
import { BookRepalceDataType } from '../../../../../define/enum/bookEnum'
let reverseManageStore = useReverseManageStore()
let message = useMessage()
let dialog = useDialog()
let da = undefined
async function SelectAfterGptAndReplace() {
if (da) {
return
}
da = dialog.create({
title: '批量查询替换文案',
showIcon: false,
maskClosable: false,
content: () =>
h(DatatableHeaderAfterGptSelectAndReplace, { type: BookRepalceDataType.AFTER_GPT }),
onClose: () => {
da = undefined
}
})
}
</script>

View File

@ -0,0 +1,116 @@
<template>
<div>
<n-input v-model:value="replaceData.before" size="small" type="text" placeholder="替换前" />
<n-input
v-model:value="replaceData.after"
style="margin-top: 10px"
size="small"
type="text"
placeholder="替换后,空白为删除"
/>
<div v-if="replaceData.type == 'gpt_prompt'" style="margin-top: 10px">
<n-checkbox v-model:checked="replaceData.replaceAll"> 替换所有 </n-checkbox>
<div style="color: red">
勾选当前按钮会替换所有的提示词数据包含推理的或者是反推的没有勾选只会替换当前显示的
</div>
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 15px">
<n-button type="info" @click="ReplaceAfterGpt">替换</n-button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, toRaw } from 'vue'
import { NInput, NButton, useMessage, NCheckbox } from 'naive-ui'
import { isEmpty, replace } from 'lodash'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { BookRepalceDataType } from '../../../../../define/enum/bookEnum'
let replaceData = ref({
before: undefined,
after: undefined,
replaceAll: false,
type: BookRepalceDataType.AFTER_GPT
})
let message = useMessage()
let reverseManageStore = useReverseManageStore()
let props = defineProps({
type: BookRepalceDataType.AFTER_GPT
})
onMounted(() => {
replaceData.value.type = props.type
})
async function ReplaceAfterGpt() {
if (isEmpty(replaceData.value.before)) {
message.error('替换前不能为空')
return
}
let res = await window.book.ReplaceBookData(
reverseManageStore.selectBookTask.id,
toRaw(replaceData.value)
)
if (res.code == 1) {
//
replaceData.value.before = ''
replaceData.value.after = ''
replaceData.value.replaceAll = false
if (props.type == BookRepalceDataType.AFTER_GPT) {
//
for (let i = 0; i < res.data.length; i++) {
const element = res.data[i]
let index = reverseManageStore.selectBookTaskDetail.findIndex(
(x) => x.id == element.bookTaskDetailId
)
if (index != -1) {
reverseManageStore.selectBookTaskDetail[index].afterGpt = element.newData
}
}
} else if (props.type == BookRepalceDataType.GPT_PROMPT) {
for (let i = 0; i < res.data.length; i++) {
const element = res.data[i]
let index = reverseManageStore.selectBookTaskDetail.findIndex(
(x) => x.id == element.bookTaskDetailId
)
if (index == -1) {
continue
}
if (element.type == 'gptPrompt') {
// GPT
reverseManageStore.selectBookTaskDetail[index].gptPrompt = element.newData
} else if (element.type == 'reversePrompt') {
//
let reverseIndex = reverseManageStore.selectBookTaskDetail[index].reversePrompt.findIndex(
(x) => x.id == element.reversePromptId
)
if (reverseIndex == -1) {
continue
}
if (element.reversePromptType == 'promptCN') {
reverseManageStore.selectBookTaskDetail[index].reversePrompt[reverseIndex].promptCN =
element.newData
} else if (element.reversePromptType == 'prompt') {
reverseManageStore.selectBookTaskDetail[index].reversePrompt[reverseIndex].prompt =
element.newData
}
}
}
} else if (props.type == BookRepalceDataType.PROMPT) {
for (let i = 0; i < res.data.length; i++) {
const element = res.data[i]
let index = reverseManageStore.selectBookTaskDetail.findIndex(
(x) => x.id == element.bookTaskDetailId
)
if (index == -1) {
continue
}
reverseManageStore.selectBookTaskDetail[index].prompt = element.newData
}
}
} else {
message.error(res.message)
}
}
</script>

View File

@ -1,37 +1,58 @@
<template> <template>
<div style="display: flex"> <div style="display: flex; justify-content: space-between; white-space: nowrap">
<div>反推/GPT提示词</div> <div style="display: flex">
<n-button <div>反推/GPT提示词</div>
v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE" <n-button
type="text" v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE"
:color="softwareStore.SoftColor.ZHUYANTUO" type="text"
size="tiny" :color="softwareStore.SoftColor.ZHUYANTUO"
style="margin-left: 10px" size="tiny"
@click="SelectPromptIndex" style="margin-left: 10px"
>选择提示词</n-button @click="SelectPromptIndex"
><n-button >选择提示词</n-button
v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE" ><n-button
type="text" v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE"
:color="softwareStore.SoftColor.ZHUYANTUO" type="text"
size="tiny" :color="softwareStore.SoftColor.ZHUYANTUO"
style="margin-left: 5px" size="tiny"
@click="SelectStyle" style="margin-left: 5px"
>风格</n-button @click="SelectStyle"
> >风格</n-button
>
</div>
<div>
<n-popover trigger="hover">
<template #trigger>
<n-button
color="#7c461e"
@click="SelectReversePromptAndReplace"
text
style="font-size: 24px"
>
<n-icon>
<FindReplaceRound />
</n-icon>
</n-button>
</template>
<span>批量替换反推提示词</span>
</n-popover>
</div>
</div> </div>
</template> </template>
<script> <script>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, h, watch } from 'vue' import { ref, onMounted, defineComponent, onUnmounted, toRaw, h, watch } from 'vue'
import { useMessage, useDialog, NButton } from 'naive-ui' import { useMessage, useDialog, NButton, NPopover, NIcon } from 'naive-ui'
import { useReverseManageStore } from '../../../../../stores/reverseManage' import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { BookType } from '../../../../../define/enum/bookEnum' import { BookRepalceDataType, BookType } from '../../../../../define/enum/bookEnum'
import { useSoftwareStore } from '../../../../../stores/software' import { useSoftwareStore } from '../../../../../stores/software'
import SelectMJReversePrompt from '../MJReverse/SelectMJReversePrompt.vue' import SelectMJReversePrompt from '../MJReverse/SelectMJReversePrompt.vue'
import SelectImageStyle from '../../Components/SelectImageStyle.vue' import SelectImageStyle from '../../Components/SelectImageStyle.vue'
import FindReplaceRound from '../../Icon/FindReplaceRound.vue'
import DatatableHeaderAfterGptSelectAndReplace from './DatatableHeaderAfterGptSelectAndReplace.vue'
export default defineComponent({ export default defineComponent({
components: { NButton }, components: { NButton, NPopover, FindReplaceRound, NIcon },
setup() { setup() {
let message = useMessage() let message = useMessage()
@ -40,7 +61,7 @@ export default defineComponent({
let bookType = ref(BookType) let bookType = ref(BookType)
let dialog = useDialog() let dialog = useDialog()
let selectStyleRef = ref(null) let selectStyleRef = ref(null)
onMounted(async () => {}) let da = undefined
/** /**
* 选择MJ反推提示词用于生图 * 选择MJ反推提示词用于生图
@ -115,13 +136,30 @@ export default defineComponent({
}) })
} }
async function SelectReversePromptAndReplace() {
if (da) {
return
}
da = dialog.create({
title: '批量查询替换反推提示词',
showIcon: false,
maskClosable: false,
content: () =>
h(DatatableHeaderAfterGptSelectAndReplace, { type: BookRepalceDataType.GPT_PROMPT }),
onClose: () => {
da = undefined
}
})
}
return { return {
reverseManageStore, reverseManageStore,
bookType, bookType,
softwareStore, softwareStore,
SelectPromptIndex, SelectPromptIndex,
SelectStyle, SelectStyle,
selectStyleRef selectStyleRef,
SelectReversePromptAndReplace
} }
} }
}) })

View File

@ -1,5 +1,6 @@
<template> <template>
<div style="display: flex; align-items: center; margin-left: 10px"> <div style="display: flex; align-items: center; white-space: nowrap">
<span>生图提示词</span>
<n-popover trigger="hover"> <n-popover trigger="hover">
<template #trigger> <template #trigger>
<n-button color="#7c461e" @click="SetMergeModel" text style="font-size: 24px"> <n-button color="#7c461e" @click="SetMergeModel" text style="font-size: 24px">
@ -19,6 +20,22 @@
>合并命令</n-button >合并命令</n-button
> >
</n-dropdown> </n-dropdown>
<n-popover trigger="hover">
<template #trigger>
<n-button
color="#7c461e"
@click="SelectPromptAndReplace"
text
style="font-size: 24px; margin-left: 10px"
>
<n-icon>
<FindReplaceRound />
</n-icon>
</n-button>
</template>
<span>批量查询替换文案</span>
</n-popover>
</div> </div>
</template> </template>
@ -30,10 +47,24 @@ import { useSoftwareStore } from '../../../../../stores/software'
import DynamicTagsSelect from '../../Components/DynamicTagsSelect.vue' import DynamicTagsSelect from '../../Components/DynamicTagsSelect.vue'
import { usePromptStore } from '../../../../../stores/prompt' import { usePromptStore } from '../../../../../stores/prompt'
import { useReverseManageStore } from '../../../../../stores/reverseManage' import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { BookImageCategory, OperateBookType } from '../../../../../define/enum/bookEnum' import {
BookImageCategory,
BookRepalceDataType,
OperateBookType
} from '../../../../../define/enum/bookEnum'
import FindReplaceRound from '../../Icon/FindReplaceRound.vue'
import DatatableHeaderAfterGptSelectAndReplace from './DatatableHeaderAfterGptSelectAndReplace.vue'
export default defineComponent({ export default defineComponent({
components: { NDropdown, NPopover, NIcon, NButton, Construct }, components: {
NDropdown,
NPopover,
NIcon,
NButton,
Construct,
FindReplaceRound,
DatatableHeaderAfterGptSelectAndReplace
},
setup() { setup() {
let softwareStore = useSoftwareStore() let softwareStore = useSoftwareStore()
@ -41,7 +72,7 @@ export default defineComponent({
let reverseManageStore = useReverseManageStore() let reverseManageStore = useReverseManageStore()
let message = useMessage() let message = useMessage()
let dialog = useDialog() let dialog = useDialog()
onMounted(async () => {}) let da = undefined
async function SaveFunction(value, options) { async function SaveFunction(value, options) {
// //
@ -137,11 +168,28 @@ export default defineComponent({
softwareStore.spin.spinning = false softwareStore.spin.spinning = false
} }
async function SelectPromptAndReplace() {
if (da) {
return
}
da = dialog.create({
title: '批量查询替换生图提示词',
showIcon: false,
maskClosable: false,
content: () =>
h(DatatableHeaderAfterGptSelectAndReplace, { type: BookRepalceDataType.PROMPT }),
onClose: () => {
da = undefined
}
})
}
return { return {
softwareStore, softwareStore,
SetMergeModel, SetMergeModel,
reverseManageStore, reverseManageStore,
MergePrompt, MergePrompt,
SelectPromptAndReplace,
MergePromptOptions: [ MergePromptOptions: [
{ label: 'MJ模式合并', key: 'mj_merge' }, { label: 'MJ模式合并', key: 'mj_merge' },
{ label: 'SD模式合并', key: 'sd_merge' } { label: 'SD模式合并', key: 'sd_merge' }

View File

@ -0,0 +1,173 @@
<template>
<div style="margin-top: 10px">
<n-form
ref="formRef"
:model="creatObj"
label-placement="left"
label-width="120"
require-mark-placement="right-hanging"
:rules="rules"
>
<n-form-item label="新增批次数" path="count">
<n-input-number
:show-button="false"
:min="1"
:max="100"
v-model:value="creatObj.count"
placeholder="Input"
/>
</n-form-item>
<n-form-item label="选择风格" path="selectTaskDataCategory">
<div>风格目前需要到每个批次中单独设置</div>
</n-form-item>
<n-form-item label="通用前缀" path="prefixPrompt">
<n-input v-model:value="creatObj.prefixPrompt" placeholder="请输入通用前缀"></n-input>
</n-form-item>
<n-form-item label="通用后缀" path="suffixPrompt">
<n-input v-model:value="creatObj.suffixPrompt" placeholder="请输入通用后缀"></n-input>
</n-form-item>
<n-form-item label="选择旧批次" path="selectBookTask">
<n-select
v-model:value="creatObj.selectBookTask"
:options="bookTaskOptions"
placeholder="选择要复制数据的已存在的批次"
/>
<n-checkbox
style="width: 200px; margin-left: 10px"
label="使用旧批次数据"
v-model:checked="creatObj.copyBookTask"
/>
</n-form-item>
<n-form-item label="复制的数据" path="selectTaskDataCategory">
<n-checkbox-group
:value="creatObj.selectTaskDataCategory"
@update:value="handleUpdateValue"
>
<n-space item-style="display: flex;" align="center">
<n-checkbox
v-for="item in groupData"
:disabled="item.disabled"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</n-checkbox>
</n-space>
</n-checkbox-group>
</n-form-item>
<div style="display: flex; justify-content: flex-end">
<n-button type="primary" @click="SaveBookTask"> 添加 </n-button>
</div>
</n-form>
</div>
</template>
<script setup>
import { onMounted, ref, toRaw } from 'vue'
import {
useMessage,
NForm,
NFormItem,
NButton,
NSpace,
NInput,
NInputNumber,
NCheckboxGroup,
NCheckbox,
NSelect
} from 'naive-ui'
import { useReverseManageStore } from '../../../../../../stores/reverseManage'
import { AddBookTaskCopyData } from '../../../../../../define/enum/bookEnum'
let reverseManageStore = useReverseManageStore()
let message = useMessage()
let formRef = ref(null)
let creatObj = ref({
count: 1,
copyBookTask: false,
selectBookTask: undefined,
selectTaskDataCategory: [],
prefixPrompt: '',
suffixPrompt: ''
})
let bookTaskOptions = ref([])
let groupData = ref([
{
label: '文案',
value: AddBookTaskCopyData.AFTER_GPT,
disabled: false
},
{
label: '反推/GPT提示词',
value: AddBookTaskCopyData.GPT_PROMPT,
disabled: false
},
{
label: '生图风格',
value: AddBookTaskCopyData.IMAGE_STYLE,
disabled: false
},
{
label: '选择角色',
value: AddBookTaskCopyData.CHARACTER,
disabled: false
},
{
label: '生图提示词',
value: AddBookTaskCopyData.PROMPT,
disabled: false
}
])
let rules = ref({
count: [{ required: true, message: '请输入新增批次数' }]
})
onMounted(() => {
bookTaskOptions.value = reverseManageStore.bookTaskData.map((item) => {
return {
label: item.name,
value: item.id
}
})
if (bookTaskOptions.value.length > 0) {
creatObj.value.selectBookTask = bookTaskOptions.value[0].value
}
})
//
async function SaveBookTask(e) {
console.log(toRaw(creatObj.value))
e.preventDefault()
formRef.value?.validate(async (errors) => {
if (errors) {
message.error('请检查必填项')
return
}
//
let res = await window.book.AddNewBookTask(toRaw(creatObj.value))
if (res.code == 0) {
message.error(res.message)
} else {
//
let bookTaskRes = await reverseManageStore.GetBookTaskDataFromDB({
bookId: reverseManageStore.selectBook.id
})
softwareStore.spin.spinning = false
if (bookTaskRes.code == 0) {
message.error(bookTaskRes.message)
return
}
//
message.success('添加成功')
}
})
}
function handleUpdateValue(value) {
creatObj.value.selectTaskDataCategory = value
}
</script>

View File

@ -362,7 +362,6 @@ export default defineComponent({
// //
async function ExportCopywriting() { async function ExportCopywriting() {
debugger
softwareStore.spin.spinning = true softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在导出文案中...' softwareStore.spin.tip = '正在导出文案中...'
let res = await window.book.ExportCopywriting(reverseManageStore.selectBookTask.id) let res = await window.book.ExportCopywriting(reverseManageStore.selectBookTask.id)
@ -382,6 +381,36 @@ export default defineComponent({
softwareStore.spin.spinning = false softwareStore.spin.spinning = false
} }
//
async function ImportCopywriting() {
window.api.SelectFile(['txt'], async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
let txtPath = value.value
//
let res = await window.book.ImportCopywriting(
reverseManageStore.selectBook.id,
reverseManageStore.selectBookTask.id,
txtPath
)
if (res.code == 1) {
for (let i = 0; i < res.data.length; i++) {
const element = res.data[i]
let index = reverseManageStore.selectBookTaskDetail.findIndex(
(x) => x.id == element.bookTaskDetailId
)
if (index != -1) {
reverseManageStore.selectBookTaskDetail[index].afterGpt = element.afterGpt
}
}
} else {
window.api.showGlobalMessageDialog(res)
}
})
}
/** /**
* 获取水印位置 * 获取水印位置
*/ */
@ -420,6 +449,9 @@ export default defineComponent({
case 'export_recognizing': // case 'export_recognizing': //
await ExportCopywriting() await ExportCopywriting()
break break
case 'import_recognizing': //
await ImportCopywriting()
break
case 'watermark_position': // case 'watermark_position': //
await GetWatermarkPosition() await GetWatermarkPosition()
break break
@ -853,6 +885,10 @@ export default defineComponent({
label: '导出文案', label: '导出文案',
key: 'export_recognizing' key: 'export_recognizing'
}, },
{
label: '导入文案',
key: 'import_recognizing'
},
{ {
label: '停止提取', label: '停止提取',
key: 'stop_recognizing' key: 'stop_recognizing'

View File

@ -19,6 +19,7 @@ import ManageBookOldImage from '../Components/ManageBookOldImage.vue'
import { BookType } from '../../../../../define/enum/bookEnum' import { BookType } from '../../../../../define/enum/bookEnum'
import MJReversePrompt from './MJReversePrompt.vue' import MJReversePrompt from './MJReversePrompt.vue'
import DatatableHeaderGptPrompt from '../Components/DatatableHeaderGptPrompt.vue' import DatatableHeaderGptPrompt from '../Components/DatatableHeaderGptPrompt.vue'
import DatatableHeaderAfterGpt from '../Components/DatatableHeaderAfterGpt.vue'
import DatatableHeaderPrompt from '../Components/DatatableHeaderPrompt.vue' import DatatableHeaderPrompt from '../Components/DatatableHeaderPrompt.vue'
import DatatablePrompt from '../Components/DatatablePrompt.vue' import DatatablePrompt from '../Components/DatatablePrompt.vue'
import DatatableHeaderImage from '../Components/DatatableHeaderImage.vue' import DatatableHeaderImage from '../Components/DatatableHeaderImage.vue'
@ -41,7 +42,9 @@ export default defineComponent({
fixed: 'left' fixed: 'left'
}, },
{ {
title: '文案', title(row) {
return h(DatatableHeaderAfterGpt)
},
key: 'afterGpt', key: 'afterGpt',
width: 200, width: 200,
fixed: 'left', fixed: 'left',
@ -87,10 +90,7 @@ export default defineComponent({
}, },
{ {
title(row) { title(row) {
return h('div', { style: 'display: flex; align-items: center' }, [ return h(DatatableHeaderPrompt)
h('span', { size: 'tiny', style: 'margin-left: 5px' }, '生图提示词'),
h(DatatableHeaderPrompt)
])
}, },
className: 'space-row', className: 'space-row',
key: 'row1', key: 'row1',

View File

@ -41,6 +41,7 @@ import { useRouter } from 'vue-router'
import { OperateBookType } from '../../../../define/enum/bookEnum' import { OperateBookType } from '../../../../define/enum/bookEnum'
import { DEFINE_STRING } from '../../../../define/define_string' import { DEFINE_STRING } from '../../../../define/define_string'
import { ResponseMessageType } from '../../../../define/enum/softwareEnum' import { ResponseMessageType } from '../../../../define/enum/softwareEnum'
import AddBookTask from './Components/ManageBook/AddBookTask.vue'
export default defineComponent({ export default defineComponent({
components: { components: {
@ -167,6 +168,16 @@ export default defineComponent({
} }
} }
async function AddBookDialog() {
message.info('新增' + reverseManageStore.selectBook.id)
dialog.create({
title: '新增小说批次任务',
showIcon: false,
content: () => h(AddBookTask),
style: { width: '600px' }
})
}
/** /**
* 一键生成草稿 * 一键生成草稿
*/ */
@ -197,6 +208,7 @@ export default defineComponent({
return { return {
reverseManageStore, reverseManageStore,
AddBookDialog,
HDImageAll, HDImageAll,
DraftAll, DraftAll,
VideoAll, VideoAll,

View File

@ -7,14 +7,6 @@
<ManageBookTask style="height: 100%" /> <ManageBookTask style="height: 100%" />
</template> </template>
</n-split> </n-split>
<n-float-button :right="30" :bottom="30" shape="circle">
<n-badge :value="100" :max="99" :offset="[6, -8]">
<n-icon :size="large">
<Layers />
</n-icon>
</n-badge>
</n-float-button>
</div> </div>
</template> </template>

View File

@ -0,0 +1,19 @@
<script setup>
import { onMounted, ref } from 'vue'
import { useMessage, NButton, NFloatButton, NBadge, NIcon } from 'naive-ui'
import { Layers } from '@vicons/ionicons5'
import { useSoftwareStore } from '../../../../../stores/software'
import QueryButton from './QueryButton.vue'
import TaskDataTable from './TaskDataTable.vue'
let softwareStore = useSoftwareStore()
onMounted(() => {
//
})
</script>
<template>
<QueryButton />
<TaskDataTable />
</template>

View File

@ -0,0 +1,57 @@
<script setup>
import { onMounted, ref } from 'vue'
import { useMessage, NForm, NFormItem, NInput, NButton } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
let softwareStore = useSoftwareStore()
function handleSubmit() {
alert(123)
}
</script>
<template>
<n-form
ref="formRef"
inline
:label-width="80"
:model="softwareStore.backTask.queryData"
size="small"
>
<n-form-item label="小说" path="bookId">
<n-input v-model:value="softwareStore.backTask.queryData.bookId" placeholder="选择小说任务" />
</n-form-item>
<n-form-item label="小说批次任务" path="bookTaskId">
<n-input
v-model:value="softwareStore.backTask.queryData.bookTaskId"
placeholder="选择小说批次任务"
/>
</n-form-item>
<n-form-item label="任务名字" path="taskName">
<n-input
v-model:value="softwareStore.backTask.queryData.taskName"
placeholder="选择小说批次任务"
/>
</n-form-item>
<n-form-item label="任务类型" path="taskType">
<n-input
v-model:value="softwareStore.backTask.queryData.taskType"
placeholder="选择小说批次任务"
/>
</n-form-item>
<n-form-item label="任务状态" path="taskStatus">
<n-input
v-model:value="softwareStore.backTask.queryData.taskStatus"
placeholder="选择小说批次任务"
/>
</n-form-item>
<n-form-item label="任务失败原因" path="taskErrorMessage">
<n-input
v-model:value="softwareStore.backTask.queryData.taskErrorMessage"
placeholder="选择小说批次任务"
/>
</n-form-item>
<n-form-item path="taskErrorMessage">
<n-button type="info" @click="handleSubmit">查询</n-button>
</n-form-item>
</n-form>
</template>

View File

@ -0,0 +1,7 @@
<script setup>
</script>
<template>
<div>TaskDataTable</div>
</template>

View File

@ -33,18 +33,21 @@
<script> <script>
import { ref, h, onMounted, defineComponent, toRaw } from 'vue' import { ref, h, onMounted, defineComponent, toRaw } from 'vue'
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
import { Layers } from '@vicons/ionicons5'
import { import {
useDialog,
NMenu, NMenu,
NSpace, NSpace,
NLayout, NLayout,
NLayoutSider, NLayoutSider,
NLayoutContent, NLayoutContent,
NIcon, NIcon,
useDialog,
useNotification, useNotification,
useMessage, useMessage,
NSwitch, NSwitch,
NButton NButton,
NFloatButton,
NBadge
} from 'naive-ui' } from 'naive-ui'
import { import {
@ -61,7 +64,9 @@ import { DEFINE_STRING } from '../../../../define/define_string'
import ShowMessage from './ShowMessage.vue' import ShowMessage from './ShowMessage.vue'
import { MD5 } from 'crypto-js' import { MD5 } from 'crypto-js'
import InputDialogContent from '../Original/Components/InputDialogContent.vue' import InputDialogContent from '../Original/Components/InputDialogContent.vue'
import APIIcon from '../APIService/APIIcon.vue' import APIIcon from '../Icon/APIIcon.vue'
import BackTaskIcon from '../Icon/BackTaskIcon.vue'
import BackTask from '../Components/BackTask/BackTask.vue'
export default defineComponent({ export default defineComponent({
components: { components: {
@ -74,7 +79,10 @@ export default defineComponent({
ShowMessage, ShowMessage,
NSwitch, NSwitch,
NButton, NButton,
APIIcon APIIcon,
NFloatButton,
NBadge,
Layers
}, },
setup() { setup() {
let collapsed = ref(false) let collapsed = ref(false)
@ -95,6 +103,16 @@ export default defineComponent({
if (option.key == 'lai_api') return h(NIcon, null, { default: () => h(APIIcon) }) if (option.key == 'lai_api') return h(NIcon, null, { default: () => h(APIIcon) })
if (option.key == 'backward_matrix') if (option.key == 'backward_matrix')
return h(NIcon, null, { default: () => h(DuplicateOutline) }) return h(NIcon, null, { default: () => h(DuplicateOutline) })
if (option.key == 'back_task')
return h(
NIcon,
{
onClick: () => {
OpneBackTask()
}
},
{ default: () => h(BackTaskIcon) }
)
if (option.key == 'TTS_Services') return h(NIcon, null, { default: () => h(RadioOutline) }) if (option.key == 'TTS_Services') return h(NIcon, null, { default: () => h(RadioOutline) })
} }
@ -486,6 +504,22 @@ export default defineComponent({
key: 'mj_setting' key: 'mj_setting'
} }
] ]
},
{
label: () =>
h(
'div',
{
onClick: () => {
OpneBackTask()
},
style : 'font-weight: bold;'
},
{
default: () => '后台任务'
}
),
key: 'back_task'
} }
] ]
@ -493,10 +527,24 @@ export default defineComponent({
return () => h(NIcon, null, { default: () => h(icon) }) return () => h(NIcon, null, { default: () => h(icon) })
} }
function OpneBackTask() {
let dialogWidth = window.innerWidth * 0.8
let dialogHeight = window.innerHeight * 0.95
dialog.create({
title: '后台任务',
showIcon: false,
closeOnEsc: false,
content: () => h(BackTask, { height: dialogHeight }),
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
maskClosable: false
})
}
return { return {
renderMenuIcon, renderMenuIcon,
menuOptions, menuOptions,
expandIcon, expandIcon,
OpneBackTask,
collapsed collapsed
} }
} }

View File

@ -0,0 +1,16 @@
<template>
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24"
>
<g fill="none">
<path
d="M13.25 8.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5zm-.75 6.25a.75.75 0 0 1 .75-.75h3.5a.75.75 0 1 1 0 1.5h-3.5a.75.75 0 0 1-.75-.75zm-1.72-7.03a.75.75 0 0 1 0 1.06l-2 2a.75.75 0 0 1-1.06 0l-1-1a.75.75 0 0 1 1.06-1.06l.47.47l1.47-1.47a.75.75 0 0 1 1.06 0zm0 6.56a.75.75 0 1 0-1.06-1.06l-1.47 1.47l-.47-.47a.75.75 0 0 0-1.06 1.06l1 1a.75.75 0 0 0 1.06 0l2-2zM5.25 3A2.25 2.25 0 0 0 3 5.25v13.5A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V5.25A2.25 2.25 0 0 0 18.75 3H5.25zM4.5 5.25a.75.75 0 0 1 .75-.75h13.5a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H5.25a.75.75 0 0 1-.75-.75V5.25z"
fill="currentColor"
></path>
</g>
</svg>
</div>
</template>

View File

@ -0,0 +1,12 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24"
>
<path
d="M11 6c1.38 0 2.63.56 3.54 1.46l-1.69 1.69a.5.5 0 0 0 .36.85h4.29c.28 0 .5-.22.5-.5V5.21c0-.45-.54-.67-.85-.35l-1.2 1.2A6.943 6.943 0 0 0 11 4C7.96 4 5.38 5.94 4.42 8.64c-.24.66.23 1.36.93 1.36c.42 0 .79-.26.93-.66A5.007 5.007 0 0 1 11 6zm5.64 9.14c.4-.54.72-1.15.95-1.8c.23-.65-.25-1.34-.94-1.34a.98.98 0 0 0-.93.66A5.007 5.007 0 0 1 11 16c-1.38 0-2.63-.56-3.54-1.46l1.69-1.69a.5.5 0 0 0-.36-.85H4.5c-.28 0-.5.22-.5.5v4.29c0 .45.54.67.85.35l1.2-1.2a6.984 6.984 0 0 0 9.09.7l4.11 4.11c.41.41 1.08.41 1.49 0c.41-.41.41-1.08 0-1.49l-4.1-4.12z"
fill="currentColor"
></path>
</svg>
</template>

View File

@ -111,7 +111,7 @@ export default defineComponent({
// //
await window.api.SaveCopywritingInformation([toRaw(AnalyzeCharacter.value), "auto_analyze_character"], (value) => { await window.api.SaveCopywritingInformation([toRaw(AnalyzeCharacter.value), "auto_analyze_character"], (value) => {
if (value.code != 1) { if (value.code != 1) {
message.error("未知错误"); message.error(value.message);
return; return;
} }
message.success("保存成功"); message.success("保存成功");

View File

@ -550,7 +550,7 @@ export default defineComponent({
[toRaw(data.value), 'srt_time_information'], [toRaw(data.value), 'srt_time_information'],
(value) => { (value) => {
if (value.code != 1) { if (value.code != 1) {
message.error('未知错误') message.error(value.message)
return return
} }
message.success('保存成功') message.success('保存成功')

View File

@ -167,7 +167,6 @@ export default defineComponent({
onMounted(async () => { onMounted(async () => {
await window.api.InitSDConfig((value) => { await window.api.InitSDConfig((value) => {
if (value.code == 0) { if (value.code == 0) {
message.error(value.message) message.error(value.message)
return return
@ -221,7 +220,7 @@ export default defineComponent({
}) })
return return
} else { } else {
window.api.showGlobalMessageDialog({ code: 0, message: '未知错误' }) window.api.showGlobalMessageDialog({ code: 0, message: value.message })
} }
}) })
} }
@ -231,7 +230,6 @@ export default defineComponent({
*/ */
async function LoadSDServiceData() { async function LoadSDServiceData() {
await window.sd.LoadSDServiceData(toRaw(formValue.value).webui_api_url, (value) => { await window.sd.LoadSDServiceData(toRaw(formValue.value).webui_api_url, (value) => {
if (value.code == 0) { if (value.code == 0) {
message.error(value.message) message.error(value.message)
return return

View File

@ -447,7 +447,7 @@ export default defineComponent({
fontNameOptions.value = value.data.font_name_list fontNameOptions.value = value.data.font_name_list
// //
} else { } else {
message.error('未知错误') message.error(value.message)
} }
}) })
@ -468,7 +468,6 @@ export default defineComponent({
* 保存基本配置 * 保存基本配置
*/ */
async function SaveGeneralSetting() { async function SaveGeneralSetting() {
await window.api.SaveGeneralSetting(toRaw(generalSetting.value), (value) => { await window.api.SaveGeneralSetting(toRaw(generalSetting.value), (value) => {
console.log(value) console.log(value)
if (value.code == 0) { if (value.code == 0) {
@ -476,7 +475,7 @@ export default defineComponent({
} else if (value.code == 1) { } else if (value.code == 1) {
window.api.showGlobalMessageDialog(value) window.api.showGlobalMessageDialog(value)
} else { } else {
window.api.showGlobalMessageDialog({ code: 0, message: '未知错误' }) window.api.showGlobalMessageDialog({ code: 0, message: value.message })
} }
}) })
} }
@ -486,7 +485,6 @@ export default defineComponent({
*/ */
async function GetSystemInstallFontName() { async function GetSystemInstallFontName() {
await window.api.GetSystemInstallFontName(async (value) => { await window.api.GetSystemInstallFontName(async (value) => {
console.log(value) console.log(value)
if (value.code == 0) { if (value.code == 0) {
message.error(value.message) message.error(value.message)
@ -495,7 +493,7 @@ export default defineComponent({
// //
await GetVideoConfigMessage() await GetVideoConfigMessage()
} else { } else {
message.error('未知错误') message.error(value.message)
} }
}) })
} }
@ -505,7 +503,6 @@ export default defineComponent({
*/ */
async function SaveAssConfig() { async function SaveAssConfig() {
window.api.SaveAssConfig(['assConfig', toRaw(assSetting.value)], async (value) => { window.api.SaveAssConfig(['assConfig', toRaw(assSetting.value)], async (value) => {
console.log(value) console.log(value)
if (value.code == 1) { if (value.code == 1) {
// //
@ -517,7 +514,7 @@ export default defineComponent({
window.api.showGlobalMessageDialog(value) window.api.showGlobalMessageDialog(value)
} else { } else {
// message.error(""); // message.error("");
window.api.showGlobalMessageDialog({ code: 0, message: '未知错误' }) window.api.showGlobalMessageDialog({ code: 0, message: value.message })
} }
}) })
} }
@ -528,7 +525,6 @@ export default defineComponent({
*/ */
async function DeleteAssSetting(row) { async function DeleteAssSetting(row) {
await window.api.DeleteVideoConfig(['assConfig', row.id], async (value) => { await window.api.DeleteVideoConfig(['assConfig', row.id], async (value) => {
console.log(value) console.log(value)
if (value.code == 1) { if (value.code == 1) {
message.success('删除成功') message.success('删除成功')
@ -552,7 +548,7 @@ export default defineComponent({
} else if (value.code == 0) { } else if (value.code == 0) {
window.api.showGlobalMessageDialog(value) window.api.showGlobalMessageDialog(value)
} else { } else {
window.api.showGlobalMessageDialog({ value: 0, message: '未知错误' }) window.api.showGlobalMessageDialog({ value: 0, message: value.message })
} }
}) })
} }
@ -561,9 +557,7 @@ export default defineComponent({
* 删除指定的行 * 删除指定的行
*/ */
async function DeleteWaterMarkSetting(row) { async function DeleteWaterMarkSetting(row) {
await window.api.DeleteVideoConfig(['watermarkConfig', row.id], async (value) => { await window.api.DeleteVideoConfig(['watermarkConfig', row.id], async (value) => {
console.log(value) console.log(value)
if (value.code == 1) { if (value.code == 1) {
message.success('删除成功') message.success('删除成功')
@ -587,14 +581,12 @@ export default defineComponent({
positiveText: '确定', positiveText: '确定',
negativeText: '取消', negativeText: '取消',
onPositiveClick: async () => { onPositiveClick: async () => {
let ss = modifyRef.value.wmSetting let ss = modifyRef.value.wmSetting
console.log(ss) console.log(ss)
await window.api.SaveAssConfig( await window.api.SaveAssConfig(
[type, toRaw(modifyRef.value.wmSetting)], [type, toRaw(modifyRef.value.wmSetting)],
async (value) => { async (value) => {
console.log(value) console.log(value)
if (value.code == 0) { if (value.code == 0) {
message.error(value.message) message.error(value.message)

View File

@ -1,4 +1,5 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { MJSetting } from '../model/Setting/mjSetting'
// 系统相关设置 // 系统相关设置
export const useSettingStore = defineStore('setting', { export const useSettingStore = defineStore('setting', {

View File

@ -7,6 +7,17 @@ export const useSoftwareStore = defineStore('software', {
spinning: false, spinning: false,
tip: '加载中...' tip: '加载中...'
}, },
backTask: {
queryData: {
bookId: undefined,
bookTaskId: undefined,
taskName: undefined,
taskType: undefined,
taskStatus: undefined,
taskErrorMessage: undefined,
}, // 查询传递的数据
taskData: [], // 后台任务数据
},
softWare: { softWare: {
theme: 'light', // 系统主题,亮或是暗 theme: 'light', // 系统主题,亮或是暗
reverse_display_show: false, // 一键反推界面显示(简单的表格模式还是表格任务模式) reverse_display_show: false, // 一键反推界面显示(简单的表格模式还是表格任务模式)