3.1.9 2024.10.28
1. (聚合推文)MJ反推、SD反推 添加剪映分镜 2. (聚合推文)完善SD反推分类(界面同MJ反推,些许不一致) 3. (聚合推文)完成一键合成视频(单个和批量) 4. 修改聚合推文进入界面小说批次任务表格样式 5. (聚合推文)完善一键重置 6. (聚合推文)完善一键删除
This commit is contained in:
parent
0c5988ed41
commit
22cfe65dde
1
.gitignore
vendored
1
.gitignore
vendored
@ -40,3 +40,4 @@ resources/scripts/db/*
|
||||
resources/scripts/localWhisper/.venv/*
|
||||
resources/scripts/localWhisper/_internal/*
|
||||
resources/scripts/localWhisper/local_whisper.exe
|
||||
resources/scripts/joyCaption/.venv/*
|
||||
|
||||
@ -2,16 +2,17 @@ import { resolve } from 'path'
|
||||
import { defineConfig, externalizeDepsPlugin, bytecodePlugin } from 'electron-vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import Jsx from '@vitejs/plugin-vue-jsx'
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [externalizeDepsPlugin(), bytecodePlugin()]
|
||||
plugins: [externalizeDepsPlugin(), bytecodePlugin(),tsconfigPaths()]
|
||||
},
|
||||
discord: {
|
||||
plugins: [externalizeDepsPlugin(), bytecodePlugin()]
|
||||
plugins: [externalizeDepsPlugin(), bytecodePlugin(),tsconfigPaths()]
|
||||
},
|
||||
preload: {
|
||||
plugins: [externalizeDepsPlugin(), bytecodePlugin()]
|
||||
plugins: [externalizeDepsPlugin(), bytecodePlugin(),tsconfigPaths()]
|
||||
},
|
||||
renderer: {
|
||||
resolve: {
|
||||
|
||||
50
package-lock.json
generated
50
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "laitool",
|
||||
"version": "3.1.8",
|
||||
"version": "3.1.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "laitool",
|
||||
"version": "3.1.8",
|
||||
"version": "3.1.9",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@alicloud/alimt20181012": "^1.2.0",
|
||||
@ -39,6 +39,7 @@
|
||||
"systeminformation": "^5.22.10",
|
||||
"tencentcloud-sdk-nodejs": "^4.0.821",
|
||||
"uuid": "^9.0.1",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"vue-router": "^4.2.5",
|
||||
"wav-file-info": "^0.0.10",
|
||||
"winston": "^3.13.0",
|
||||
@ -5084,6 +5085,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/globrex": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/globrex/-/globrex-0.1.2.tgz",
|
||||
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
@ -10341,6 +10348,26 @@
|
||||
"typescript": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tsconfck": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/tsconfck/-/tsconfck-3.1.4.tgz",
|
||||
"integrity": "sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"tsconfck": "bin/tsconfck.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || >=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"license": "0BSD",
|
||||
@ -10532,6 +10559,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-tsconfig-paths": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/vite-tsconfig-paths/-/vite-tsconfig-paths-5.0.1.tgz",
|
||||
"integrity": "sha512-yqwv+LstU7NwPeNqajZzLEBVpUFU6Dugtb2P84FXuvaoYA+/70l9MHE+GYfYAycVyPSDYZ7mjOFuYBRqlEpTig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
"globrex": "^0.1.2",
|
||||
"tsconfck": "^3.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vooks": {
|
||||
"version": "0.2.12",
|
||||
"dev": true,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "laitool",
|
||||
"version": "3.1.8",
|
||||
"version": "3.1.9",
|
||||
"description": "An AI tool for image processing, video processing, and other functions.",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "laitool.cn",
|
||||
@ -47,6 +47,7 @@
|
||||
"systeminformation": "^5.22.10",
|
||||
"tencentcloud-sdk-nodejs": "^4.0.821",
|
||||
"uuid": "^9.0.1",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"vue-router": "^4.2.5",
|
||||
"wav-file-info": "^0.0.10",
|
||||
"winston": "^3.13.0",
|
||||
|
||||
@ -16,9 +16,33 @@ if len(sys.argv) < 2:
|
||||
sys.argv = [
|
||||
"C:\\Users\\27698\\Desktop\\LAITool\\resources\\scripts\\Lai.exe",
|
||||
"-c",
|
||||
"D:/来推项目集/7.4/娱乐:江湖大哥退休,去拍电影/scripts/output_crop_00001.json",
|
||||
"NVIDIA",
|
||||
"C:/Users/27698/Desktop/LAITool/project/73bca563-fd8f-42e3-b08b-e4b2ecbd0d71/scripts/output_00008_video_config.json",
|
||||
"OTHER",
|
||||
]
|
||||
# sys.argv = [
|
||||
# "C:\\Users\\27698\\Desktop\\LAITool\\resources\\scripts\\Lai.exe",
|
||||
# "-p",
|
||||
# "C:/Users/27698/Desktop/LAITool/resources/config/sd_config.json",
|
||||
# "input",
|
||||
# "C:\\Users\\27698\\Desktop\\测试\\123",
|
||||
# ]
|
||||
# sys.argv = [
|
||||
# "C:\\Users\\27698\\Desktop\\LAITool\\resources\\scripts\\Lai.exe",
|
||||
# "-pf",
|
||||
# "C:\\Users\\27698\\Desktop\\测试\\123\\tmp\\input_crop",
|
||||
# ]
|
||||
# sys.argv = [
|
||||
# "C:\\Users\\27698\\Desktop\\LAITool\\resources\\scripts\\Lai.exe",
|
||||
# "-ps",
|
||||
# "C:\\Users\\27698\\Desktop\\测试\\123\\tmp\\input_crop\\00008.png",
|
||||
# ]
|
||||
# sys.argv = [
|
||||
# "C:\\Users\\27698\\Desktop\\LAITool\\resources\\scripts\\Lai.exe",
|
||||
# "-pt",
|
||||
# "C:\\Users\\27698\\Desktop\\测试\\123\\tmp\\input_crop\\1.txt",
|
||||
# ]
|
||||
|
||||
|
||||
|
||||
print(sys.argv)
|
||||
|
||||
@ -73,11 +97,26 @@ elif sys.argv[1] == "-f":
|
||||
with open(sys.argv[2], "w", encoding="utf-8") as file:
|
||||
json.dump(data, file, ensure_ascii=False, indent=4)
|
||||
|
||||
# 反推提示词
|
||||
# 反推提示词(指定项目下面的input_crop文件夹)
|
||||
elif sys.argv[1] == "-p":
|
||||
Push_back_Prompt.init(sys.argv[2], sys.argv[3], sys.argv[4])
|
||||
pass
|
||||
|
||||
# 反推指定的文件夹
|
||||
elif sys.argv[1] == "-pf":
|
||||
Push_back_Prompt.getAssignDir(sys.argv[2])
|
||||
pass
|
||||
|
||||
# 反推指定的图片文件
|
||||
elif sys.argv[1] == "-ps":
|
||||
Push_back_Prompt.getAssignImage(sys.argv[2])
|
||||
pass
|
||||
|
||||
# 反推指定的图片文件
|
||||
elif sys.argv[1] == "-pt":
|
||||
Push_back_Prompt.getAssignTxt(sys.argv[2])
|
||||
pass
|
||||
|
||||
elif sys.argv[1] == "-ka":
|
||||
shotSplit.get_fram(sys.argv[2], sys.argv[3], sys.argv[4])
|
||||
pass
|
||||
|
||||
@ -148,10 +148,11 @@ class WaifuDiffusionInterrogator(Interrogator):
|
||||
Dict[str, float], Dict[str, float] # rating confidents # tag confidents
|
||||
]:
|
||||
# init model
|
||||
if not hasattr(self, 'model') or self.model is None:
|
||||
if not hasattr(self, "model") or self.model is None:
|
||||
model_path = os.path.join(cript_directory, "model/tag/model.onnx")
|
||||
tags_path = os.path.join(cript_directory, "model/tag/selected_tags.csv")
|
||||
from onnxruntime import InferenceSession
|
||||
|
||||
providers = ["CUDAExecutionProvider", "CPUExecutionProvider"]
|
||||
self.model = InferenceSession(str(model_path), providers=providers)
|
||||
print(f"从{model_path} 读取 {self.name}模型")
|
||||
@ -223,7 +224,89 @@ def filter_action(tag_actions: [], tags: []):
|
||||
return action_tags, other_tags
|
||||
|
||||
|
||||
def init(sd_setting,m,project_path):
|
||||
def getAssignTxt(txtPath):
|
||||
if not os.path.exists(txtPath):
|
||||
os.makedirs(txtPath)
|
||||
|
||||
# load model
|
||||
model = WaifuDiffusionInterrogator(
|
||||
"wd14-convnextv2-v2",
|
||||
repo_id="SmilingWolf/wd-v1-4-convnextv2-tagger-v2",
|
||||
revision="v2.0",
|
||||
)
|
||||
|
||||
frame_files = []
|
||||
with open(txtPath, 'r', encoding='utf-8') as file:
|
||||
for line in file:
|
||||
frame_files.append(line.strip()) # 使用 strip() 去除每行的换行符和多余的空白
|
||||
|
||||
# 轮询开始输出
|
||||
frame_files.sort()
|
||||
|
||||
for frame_file in frame_files:
|
||||
txt = getTags(model, frame_file)
|
||||
# tags = txt.split(",")
|
||||
|
||||
# save tag
|
||||
txt_file = os.path.join(os.path.dirname(frame_file), f"{Path(frame_file).stem}.txt")
|
||||
with open(txt_file, "w", encoding="utf-8") as tags:
|
||||
tags.write(txt)
|
||||
print(f"{frame_file} 提示词反推完成")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
|
||||
def getAssignImage(imagePath):
|
||||
if not os.path.exists(imagePath):
|
||||
os.makedirs(imagePath)
|
||||
|
||||
# load model
|
||||
model = WaifuDiffusionInterrogator(
|
||||
"wd14-convnextv2-v2",
|
||||
repo_id="SmilingWolf/wd-v1-4-convnextv2-tagger-v2",
|
||||
revision="v2.0",
|
||||
)
|
||||
|
||||
txt = getTags(model, imagePath)
|
||||
# tags = txt.split(",")
|
||||
|
||||
# save tag
|
||||
txt_file = os.path.join(os.path.dirname(imagePath), f"{Path(imagePath).stem}.txt")
|
||||
with open(txt_file, "w", encoding="utf-8") as tags:
|
||||
tags.write(txt)
|
||||
print(f"{imagePath} 提示词反推完成")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def getAssignDir(imagePath):
|
||||
if not os.path.exists(imagePath):
|
||||
os.makedirs(imagePath)
|
||||
|
||||
# load model
|
||||
model = WaifuDiffusionInterrogator(
|
||||
"wd14-convnextv2-v2",
|
||||
repo_id="SmilingWolf/wd-v1-4-convnextv2-tagger-v2",
|
||||
revision="v2.0",
|
||||
)
|
||||
|
||||
# 轮询开始输出
|
||||
frame_files = [f for f in os.listdir(imagePath) if f.endswith(".png")]
|
||||
frame_files.sort()
|
||||
|
||||
for frame in frame_files:
|
||||
frame_file = os.path.join(imagePath, frame)
|
||||
txt = getTags(model, frame_file)
|
||||
# tags = txt.split(",")
|
||||
|
||||
# save tag
|
||||
txt_file = os.path.join(imagePath, f"{Path(frame_file).stem}.txt")
|
||||
with open(txt_file, "w", encoding="utf-8") as tags:
|
||||
tags.write(txt)
|
||||
print(f"{frame} 提示词反推完成")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def init(sd_setting, m, project_path):
|
||||
try:
|
||||
setting_json = public_tools.read_config(sd_setting, webui=False)
|
||||
except Exception as e:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
29
resources/scripts/joyCaption/0.26.0
Normal file
29
resources/scripts/joyCaption/0.26.0
Normal file
@ -0,0 +1,29 @@
|
||||
Collecting accelerate
|
||||
Downloading accelerate-1.0.1-py3-none-any.whl.metadata (19 kB)
|
||||
Requirement already satisfied: numpy<3.0.0,>=1.17 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from accelerate) (2.1.2)
|
||||
Requirement already satisfied: packaging>=20.0 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from accelerate) (24.1)
|
||||
Collecting psutil (from accelerate)
|
||||
Downloading psutil-6.1.0-cp37-abi3-win_amd64.whl.metadata (23 kB)
|
||||
Requirement already satisfied: pyyaml in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from accelerate) (6.0.2)
|
||||
Requirement already satisfied: torch>=1.10.0 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from accelerate) (2.5.0)
|
||||
Requirement already satisfied: huggingface-hub>=0.21.0 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from accelerate) (0.26.1)
|
||||
Requirement already satisfied: safetensors>=0.4.3 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from accelerate) (0.4.5)
|
||||
Requirement already satisfied: filelock in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from huggingface-hub>=0.21.0->accelerate) (3.16.1)
|
||||
Requirement already satisfied: fsspec>=2023.5.0 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from huggingface-hub>=0.21.0->accelerate) (2024.10.0)
|
||||
Requirement already satisfied: requests in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from huggingface-hub>=0.21.0->accelerate) (2.32.3)
|
||||
Requirement already satisfied: tqdm>=4.42.1 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from huggingface-hub>=0.21.0->accelerate) (4.66.5)
|
||||
Requirement already satisfied: typing-extensions>=3.7.4.3 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from huggingface-hub>=0.21.0->accelerate) (4.12.2)
|
||||
Requirement already satisfied: networkx in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from torch>=1.10.0->accelerate) (3.4.2)
|
||||
Requirement already satisfied: jinja2 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from torch>=1.10.0->accelerate) (3.1.4)
|
||||
Requirement already satisfied: sympy==1.13.1 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from torch>=1.10.0->accelerate) (1.13.1)
|
||||
Requirement already satisfied: mpmath<1.4,>=1.1.0 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from sympy==1.13.1->torch>=1.10.0->accelerate) (1.3.0)
|
||||
Requirement already satisfied: colorama in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from tqdm>=4.42.1->huggingface-hub>=0.21.0->accelerate) (0.4.6)
|
||||
Requirement already satisfied: MarkupSafe>=2.0 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from jinja2->torch>=1.10.0->accelerate) (3.0.2)
|
||||
Requirement already satisfied: charset-normalizer<4,>=2 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from requests->huggingface-hub>=0.21.0->accelerate) (3.4.0)
|
||||
Requirement already satisfied: idna<4,>=2.5 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from requests->huggingface-hub>=0.21.0->accelerate) (3.10)
|
||||
Requirement already satisfied: urllib3<3,>=1.21.1 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from requests->huggingface-hub>=0.21.0->accelerate) (2.2.3)
|
||||
Requirement already satisfied: certifi>=2017.4.17 in c:\users\27698\desktop\laitool\resources\scripts\joycaption\.venv\lib\site-packages (from requests->huggingface-hub>=0.21.0->accelerate) (2024.8.30)
|
||||
Downloading accelerate-1.0.1-py3-none-any.whl (330 kB)
|
||||
Downloading psutil-6.1.0-cp37-abi3-win_amd64.whl (254 kB)
|
||||
Installing collected packages: psutil, accelerate
|
||||
Successfully installed accelerate-1.0.1 psutil-6.1.0
|
||||
0
resources/scripts/joyCaption/0.26.0'
Normal file
0
resources/scripts/joyCaption/0.26.0'
Normal file
336
resources/scripts/joyCaption/app.py
Normal file
336
resources/scripts/joyCaption/app.py
Normal file
@ -0,0 +1,336 @@
|
||||
import spaces
|
||||
import gradio as gr
|
||||
from huggingface_hub import InferenceClient
|
||||
from torch import nn
|
||||
from transformers import AutoModel, AutoProcessor, AutoTokenizer, PreTrainedTokenizer, PreTrainedTokenizerFast, AutoModelForCausalLM
|
||||
from pathlib import Path
|
||||
import torch
|
||||
import torch.amp.autocast_mode
|
||||
from PIL import Image
|
||||
import os
|
||||
import torchvision.transforms.functional as TVF
|
||||
|
||||
|
||||
CLIP_PATH = "google/siglip-so400m-patch14-384"
|
||||
CHECKPOINT_PATH = Path("cgrkzexw-599808")
|
||||
TITLE = "<h1><center>JoyCaption Alpha Two (2024-09-26a)</center></h1>"
|
||||
CAPTION_TYPE_MAP = {
|
||||
"Descriptive": [
|
||||
"Write a descriptive caption for this image in a formal tone.",
|
||||
"Write a descriptive caption for this image in a formal tone within {word_count} words.",
|
||||
"Write a {length} descriptive caption for this image in a formal tone.",
|
||||
],
|
||||
"Descriptive (Informal)": [
|
||||
"Write a descriptive caption for this image in a casual tone.",
|
||||
"Write a descriptive caption for this image in a casual tone within {word_count} words.",
|
||||
"Write a {length} descriptive caption for this image in a casual tone.",
|
||||
],
|
||||
"Training Prompt": [
|
||||
"Write a stable diffusion prompt for this image.",
|
||||
"Write a stable diffusion prompt for this image within {word_count} words.",
|
||||
"Write a {length} stable diffusion prompt for this image.",
|
||||
],
|
||||
"MidJourney": [
|
||||
"Write a MidJourney prompt for this image.",
|
||||
"Write a MidJourney prompt for this image within {word_count} words.",
|
||||
"Write a {length} MidJourney prompt for this image.",
|
||||
],
|
||||
"Booru tag list": [
|
||||
"Write a list of Booru tags for this image.",
|
||||
"Write a list of Booru tags for this image within {word_count} words.",
|
||||
"Write a {length} list of Booru tags for this image.",
|
||||
],
|
||||
"Booru-like tag list": [
|
||||
"Write a list of Booru-like tags for this image.",
|
||||
"Write a list of Booru-like tags for this image within {word_count} words.",
|
||||
"Write a {length} list of Booru-like tags for this image.",
|
||||
],
|
||||
"Art Critic": [
|
||||
"Analyze this image like an art critic would with information about its composition, style, symbolism, the use of color, light, any artistic movement it might belong to, etc.",
|
||||
"Analyze this image like an art critic would with information about its composition, style, symbolism, the use of color, light, any artistic movement it might belong to, etc. Keep it within {word_count} words.",
|
||||
"Analyze this image like an art critic would with information about its composition, style, symbolism, the use of color, light, any artistic movement it might belong to, etc. Keep it {length}.",
|
||||
],
|
||||
"Product Listing": [
|
||||
"Write a caption for this image as though it were a product listing.",
|
||||
"Write a caption for this image as though it were a product listing. Keep it under {word_count} words.",
|
||||
"Write a {length} caption for this image as though it were a product listing.",
|
||||
],
|
||||
"Social Media Post": [
|
||||
"Write a caption for this image as if it were being used for a social media post.",
|
||||
"Write a caption for this image as if it were being used for a social media post. Limit the caption to {word_count} words.",
|
||||
"Write a {length} caption for this image as if it were being used for a social media post.",
|
||||
],
|
||||
}
|
||||
|
||||
HF_TOKEN = os.environ.get("HF_TOKEN", None)
|
||||
|
||||
|
||||
class ImageAdapter(nn.Module):
|
||||
def __init__(self, input_features: int, output_features: int, ln1: bool, pos_emb: bool, num_image_tokens: int, deep_extract: bool):
|
||||
super().__init__()
|
||||
self.deep_extract = deep_extract
|
||||
|
||||
if self.deep_extract:
|
||||
input_features = input_features * 5
|
||||
|
||||
self.linear1 = nn.Linear(input_features, output_features)
|
||||
self.activation = nn.GELU()
|
||||
self.linear2 = nn.Linear(output_features, output_features)
|
||||
self.ln1 = nn.Identity() if not ln1 else nn.LayerNorm(input_features)
|
||||
self.pos_emb = None if not pos_emb else nn.Parameter(torch.zeros(num_image_tokens, input_features))
|
||||
|
||||
# Other tokens (<|image_start|>, <|image_end|>, <|eot_id|>)
|
||||
self.other_tokens = nn.Embedding(3, output_features)
|
||||
self.other_tokens.weight.data.normal_(mean=0.0, std=0.02) # Matches HF's implementation of llama3
|
||||
|
||||
def forward(self, vision_outputs: torch.Tensor):
|
||||
if self.deep_extract:
|
||||
x = torch.concat((
|
||||
vision_outputs[-2],
|
||||
vision_outputs[3],
|
||||
vision_outputs[7],
|
||||
vision_outputs[13],
|
||||
vision_outputs[20],
|
||||
), dim=-1)
|
||||
assert len(x.shape) == 3, f"Expected 3, got {len(x.shape)}" # batch, tokens, features
|
||||
assert x.shape[-1] == vision_outputs[-2].shape[-1] * 5, f"Expected {vision_outputs[-2].shape[-1] * 5}, got {x.shape[-1]}"
|
||||
else:
|
||||
x = vision_outputs[-2]
|
||||
|
||||
x = self.ln1(x)
|
||||
|
||||
if self.pos_emb is not None:
|
||||
assert x.shape[-2:] == self.pos_emb.shape, f"Expected {self.pos_emb.shape}, got {x.shape[-2:]}"
|
||||
x = x + self.pos_emb
|
||||
|
||||
x = self.linear1(x)
|
||||
x = self.activation(x)
|
||||
x = self.linear2(x)
|
||||
|
||||
# <|image_start|>, IMAGE, <|image_end|>
|
||||
other_tokens = self.other_tokens(torch.tensor([0, 1], device=self.other_tokens.weight.device).expand(x.shape[0], -1))
|
||||
assert other_tokens.shape == (x.shape[0], 2, x.shape[2]), f"Expected {(x.shape[0], 2, x.shape[2])}, got {other_tokens.shape}"
|
||||
x = torch.cat((other_tokens[:, 0:1], x, other_tokens[:, 1:2]), dim=1)
|
||||
|
||||
return x
|
||||
|
||||
def get_eot_embedding(self):
|
||||
return self.other_tokens(torch.tensor([2], device=self.other_tokens.weight.device)).squeeze(0)
|
||||
|
||||
|
||||
|
||||
# Load CLIP
|
||||
print("Loading CLIP")
|
||||
clip_processor = AutoProcessor.from_pretrained(CLIP_PATH)
|
||||
clip_model = AutoModel.from_pretrained(CLIP_PATH)
|
||||
clip_model = clip_model.vision_model
|
||||
|
||||
assert (CHECKPOINT_PATH / "clip_model.pt").exists()
|
||||
print("Loading VLM's custom vision model")
|
||||
checkpoint = torch.load(CHECKPOINT_PATH / "clip_model.pt", map_location='cpu')
|
||||
checkpoint = {k.replace("_orig_mod.module.", ""): v for k, v in checkpoint.items()}
|
||||
clip_model.load_state_dict(checkpoint)
|
||||
del checkpoint
|
||||
|
||||
clip_model.eval()
|
||||
clip_model.requires_grad_(False)
|
||||
clip_model.to("cuda")
|
||||
|
||||
|
||||
# Tokenizer
|
||||
print("Loading tokenizer")
|
||||
tokenizer = AutoTokenizer.from_pretrained(CHECKPOINT_PATH / "text_model", use_fast=True)
|
||||
assert isinstance(tokenizer, PreTrainedTokenizer) or isinstance(tokenizer, PreTrainedTokenizerFast), f"Tokenizer is of type {type(tokenizer)}"
|
||||
|
||||
# LLM
|
||||
print("Loading LLM")
|
||||
print("Loading VLM's custom text model")
|
||||
text_model = AutoModelForCausalLM.from_pretrained(CHECKPOINT_PATH / "text_model", device_map=0, torch_dtype=torch.bfloat16)
|
||||
text_model.eval()
|
||||
|
||||
# Image Adapter
|
||||
print("Loading image adapter")
|
||||
image_adapter = ImageAdapter(clip_model.config.hidden_size, text_model.config.hidden_size, False, False, 38, False)
|
||||
image_adapter.load_state_dict(torch.load(CHECKPOINT_PATH / "image_adapter.pt", map_location="cpu"))
|
||||
image_adapter.eval()
|
||||
image_adapter.to("cuda")
|
||||
|
||||
|
||||
@spaces.GPU()
|
||||
@torch.no_grad()
|
||||
def stream_chat(input_image: Image.Image, caption_type: str, caption_length: str | int, extra_options: list[str], name_input: str, custom_prompt: str) -> tuple[str, str]:
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
# 'any' means no length specified
|
||||
length = None if caption_length == "any" else caption_length
|
||||
|
||||
if isinstance(length, str):
|
||||
try:
|
||||
length = int(length)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Build prompt
|
||||
if length is None:
|
||||
map_idx = 0
|
||||
elif isinstance(length, int):
|
||||
map_idx = 1
|
||||
elif isinstance(length, str):
|
||||
map_idx = 2
|
||||
else:
|
||||
raise ValueError(f"Invalid caption length: {length}")
|
||||
|
||||
prompt_str = CAPTION_TYPE_MAP[caption_type][map_idx]
|
||||
|
||||
# Add extra options
|
||||
if len(extra_options) > 0:
|
||||
prompt_str += " " + " ".join(extra_options)
|
||||
|
||||
# Add name, length, word_count
|
||||
prompt_str = prompt_str.format(name=name_input, length=caption_length, word_count=caption_length)
|
||||
|
||||
if custom_prompt.strip() != "":
|
||||
prompt_str = custom_prompt.strip()
|
||||
|
||||
# For debugging
|
||||
print(f"Prompt: {prompt_str}")
|
||||
|
||||
# Preprocess image
|
||||
# NOTE: I found the default processor for so400M to have worse results than just using PIL directly
|
||||
#image = clip_processor(images=input_image, return_tensors='pt').pixel_values
|
||||
image = input_image.resize((384, 384), Image.LANCZOS)
|
||||
pixel_values = TVF.pil_to_tensor(image).unsqueeze(0) / 255.0
|
||||
pixel_values = TVF.normalize(pixel_values, [0.5], [0.5])
|
||||
pixel_values = pixel_values.to('cuda')
|
||||
|
||||
# Embed image
|
||||
# This results in Batch x Image Tokens x Features
|
||||
with torch.amp.autocast_mode.autocast('cuda', enabled=True):
|
||||
vision_outputs = clip_model(pixel_values=pixel_values, output_hidden_states=True)
|
||||
embedded_images = image_adapter(vision_outputs.hidden_states)
|
||||
embedded_images = embedded_images.to('cuda')
|
||||
|
||||
# Build the conversation
|
||||
convo = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a helpful image captioner.",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": prompt_str,
|
||||
},
|
||||
]
|
||||
|
||||
# Format the conversation
|
||||
convo_string = tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = True)
|
||||
assert isinstance(convo_string, str)
|
||||
|
||||
# Tokenize the conversation
|
||||
# prompt_str is tokenized separately so we can do the calculations below
|
||||
convo_tokens = tokenizer.encode(convo_string, return_tensors="pt", add_special_tokens=False, truncation=False)
|
||||
prompt_tokens = tokenizer.encode(prompt_str, return_tensors="pt", add_special_tokens=False, truncation=False)
|
||||
assert isinstance(convo_tokens, torch.Tensor) and isinstance(prompt_tokens, torch.Tensor)
|
||||
convo_tokens = convo_tokens.squeeze(0) # Squeeze just to make the following easier
|
||||
prompt_tokens = prompt_tokens.squeeze(0)
|
||||
|
||||
# Calculate where to inject the image
|
||||
eot_id_indices = (convo_tokens == tokenizer.convert_tokens_to_ids("<|eot_id|>")).nonzero(as_tuple=True)[0].tolist()
|
||||
assert len(eot_id_indices) == 2, f"Expected 2 <|eot_id|> tokens, got {len(eot_id_indices)}"
|
||||
|
||||
preamble_len = eot_id_indices[1] - prompt_tokens.shape[0] # Number of tokens before the prompt
|
||||
|
||||
# Embed the tokens
|
||||
convo_embeds = text_model.model.embed_tokens(convo_tokens.unsqueeze(0).to('cuda'))
|
||||
|
||||
# Construct the input
|
||||
input_embeds = torch.cat([
|
||||
convo_embeds[:, :preamble_len], # Part before the prompt
|
||||
embedded_images.to(dtype=convo_embeds.dtype), # Image
|
||||
convo_embeds[:, preamble_len:], # The prompt and anything after it
|
||||
], dim=1).to('cuda')
|
||||
|
||||
input_ids = torch.cat([
|
||||
convo_tokens[:preamble_len].unsqueeze(0),
|
||||
torch.zeros((1, embedded_images.shape[1]), dtype=torch.long), # Dummy tokens for the image (TODO: Should probably use a special token here so as not to confuse any generation algorithms that might be inspecting the input)
|
||||
convo_tokens[preamble_len:].unsqueeze(0),
|
||||
], dim=1).to('cuda')
|
||||
attention_mask = torch.ones_like(input_ids)
|
||||
|
||||
# Debugging
|
||||
print(f"Input to model: {repr(tokenizer.decode(input_ids[0]))}")
|
||||
|
||||
#generate_ids = text_model.generate(input_ids, inputs_embeds=inputs_embeds, attention_mask=attention_mask, max_new_tokens=300, do_sample=False, suppress_tokens=None)
|
||||
#generate_ids = text_model.generate(input_ids, inputs_embeds=inputs_embeds, attention_mask=attention_mask, max_new_tokens=300, do_sample=True, top_k=10, temperature=0.5, suppress_tokens=None)
|
||||
generate_ids = text_model.generate(input_ids, inputs_embeds=input_embeds, attention_mask=attention_mask, max_new_tokens=300, do_sample=True, suppress_tokens=None) # Uses the default which is temp=0.6, top_p=0.9
|
||||
|
||||
# Trim off the prompt
|
||||
generate_ids = generate_ids[:, input_ids.shape[1]:]
|
||||
if generate_ids[0][-1] == tokenizer.eos_token_id or generate_ids[0][-1] == tokenizer.convert_tokens_to_ids("<|eot_id|>"):
|
||||
generate_ids = generate_ids[:, :-1]
|
||||
|
||||
caption = tokenizer.batch_decode(generate_ids, skip_special_tokens=False, clean_up_tokenization_spaces=False)[0]
|
||||
|
||||
return prompt_str, caption.strip()
|
||||
|
||||
|
||||
with gr.Blocks() as demo:
|
||||
gr.HTML(TITLE)
|
||||
|
||||
with gr.Row():
|
||||
with gr.Column():
|
||||
input_image = gr.Image(type="pil", label="Input Image")
|
||||
|
||||
caption_type = gr.Dropdown(
|
||||
choices=["Descriptive", "Descriptive (Informal)", "Training Prompt", "MidJourney", "Booru tag list", "Booru-like tag list", "Art Critic", "Product Listing", "Social Media Post"],
|
||||
label="Caption Type",
|
||||
value="Descriptive",
|
||||
)
|
||||
|
||||
caption_length = gr.Dropdown(
|
||||
choices=["any", "very short", "short", "medium-length", "long", "very long"] +
|
||||
[str(i) for i in range(20, 261, 10)],
|
||||
label="Caption Length",
|
||||
value="long",
|
||||
)
|
||||
|
||||
extra_options = gr.CheckboxGroup(
|
||||
choices=[
|
||||
"If there is a person/character in the image you must refer to them as {name}.",
|
||||
"Do NOT include information about people/characters that cannot be changed (like ethnicity, gender, etc), but do still include changeable attributes (like hair style).",
|
||||
"Include information about lighting.",
|
||||
"Include information about camera angle.",
|
||||
"Include information about whether there is a watermark or not.",
|
||||
"Include information about whether there are JPEG artifacts or not.",
|
||||
"If it is a photo you MUST include information about what camera was likely used and details such as aperture, shutter speed, ISO, etc.",
|
||||
"Do NOT include anything sexual; keep it PG.",
|
||||
"Do NOT mention the image's resolution.",
|
||||
"You MUST include information about the subjective aesthetic quality of the image from low to very high.",
|
||||
"Include information on the image's composition style, such as leading lines, rule of thirds, or symmetry.",
|
||||
"Do NOT mention any text that is in the image.",
|
||||
"Specify the depth of field and whether the background is in focus or blurred.",
|
||||
"If applicable, mention the likely use of artificial or natural lighting sources.",
|
||||
"Do NOT use any ambiguous language.",
|
||||
"Include whether the image is sfw, suggestive, or nsfw.",
|
||||
"ONLY describe the most important elements of the image."
|
||||
],
|
||||
label="Extra Options"
|
||||
)
|
||||
|
||||
name_input = gr.Textbox(label="Person/Character Name (if applicable)")
|
||||
gr.Markdown("**Note:** Name input is only used if an Extra Option is selected that requires it.")
|
||||
|
||||
custom_prompt = gr.Textbox(label="Custom Prompt (optional, will override all other settings)")
|
||||
gr.Markdown("**Note:** Alpha Two is not a general instruction follower and will not follow prompts outside its training data well. Use this feature with caution.")
|
||||
|
||||
run_button = gr.Button("Caption")
|
||||
|
||||
with gr.Column():
|
||||
output_prompt = gr.Textbox(label="Prompt that was used")
|
||||
output_caption = gr.Textbox(label="Caption")
|
||||
|
||||
run_button.click(fn=stream_chat, inputs=[input_image, caption_type, caption_length, extra_options, name_input, custom_prompt], outputs=[output_prompt, output_caption])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo.launch()
|
||||
423
resources/scripts/joyCaption/batch-caption.py
Normal file
423
resources/scripts/joyCaption/batch-caption.py
Normal file
@ -0,0 +1,423 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Use JoyCaption to caption images.
|
||||
"""
|
||||
import argparse
|
||||
import dataclasses
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
import PIL.Image
|
||||
import torch
|
||||
import torch.amp
|
||||
import torchvision.transforms.functional as TVF
|
||||
from PIL import Image
|
||||
from torch.utils.data import DataLoader, Dataset
|
||||
from tqdm import tqdm
|
||||
from transformers import (
|
||||
AutoTokenizer,
|
||||
LlavaForConditionalGeneration,
|
||||
PreTrainedTokenizer,
|
||||
PreTrainedTokenizerFast,
|
||||
)
|
||||
|
||||
|
||||
def none_or_type(value, desired_type):
|
||||
if value == "None":
|
||||
return None
|
||||
return desired_type(value)
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--glob", type=str, help="Glob pattern to find images")
|
||||
parser.add_argument("--filelist", type=str, help="File containing list of images")
|
||||
parser.add_argument("--prompt", type=str, help="Prompt to use")
|
||||
parser.add_argument(
|
||||
"--prompt-file", type=str, help="JSON file containing prompts to use"
|
||||
)
|
||||
parser.add_argument("--batch-size", type=int, default=1, help="Batch size")
|
||||
parser.add_argument(
|
||||
"--greedy", action="store_true", help="Use greedy decoding instead of sampling"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--temperature", type=float, default=0.6, help="Sampling temperature"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--top-p", type=lambda x: none_or_type(x, float), default=0.9, help="Top-p sampling"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--top-k", type=lambda x: none_or_type(x, int), default=None, help="Top-k sampling"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max-new-tokens",
|
||||
type=int,
|
||||
default=256,
|
||||
help="Maximum length of the generated caption (in tokens)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--num-workers",
|
||||
type=int,
|
||||
default=4,
|
||||
help="Number of workers loading images in parallel",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--model",
|
||||
type=str,
|
||||
default="fancyfeast/llama-joycaption-alpha-two-hf-llava",
|
||||
help="Model to use",
|
||||
)
|
||||
|
||||
|
||||
PIL.Image.MAX_IMAGE_PIXELS = 933120000 # Quiets Pillow from giving warnings on really large images (WARNING: Exposes a risk of DoS from malicious images)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Prompt:
|
||||
prompt: str
|
||||
weight: float
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def main():
|
||||
# Logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s"
|
||||
)
|
||||
|
||||
# Parse arguments
|
||||
args = parser.parse_args()
|
||||
logging.info(f"Arguments: {args}")
|
||||
|
||||
args.prompt = "Please describe the image."
|
||||
# Make sure we have a prompt or a prompt file
|
||||
prompts = parse_prompts(args.prompt, args.prompt_file)
|
||||
|
||||
args.filelist = (
|
||||
"C:\\Users\\27698\\Desktop\\node\\12\\12.txt"
|
||||
)
|
||||
# Find the images
|
||||
image_paths = find_images(args.glob, args.filelist)
|
||||
if len(image_paths) == 0:
|
||||
logging.warning("No images found")
|
||||
return
|
||||
logging.info(f"Found {len(image_paths)} images")
|
||||
|
||||
# Ignore all images that already have captions
|
||||
image_paths = [
|
||||
path for path in image_paths if not Path(path).with_suffix(".txt").exists()
|
||||
]
|
||||
|
||||
# Load JoyCaption
|
||||
tokenizer = AutoTokenizer.from_pretrained(args.model, use_fast=True)
|
||||
assert isinstance(tokenizer, PreTrainedTokenizer) or isinstance(
|
||||
tokenizer, PreTrainedTokenizerFast
|
||||
), f"Tokenizer is of type {type(tokenizer)}"
|
||||
llava_model = LlavaForConditionalGeneration.from_pretrained(
|
||||
args.model, torch_dtype="bfloat16"
|
||||
)
|
||||
assert isinstance(llava_model, LlavaForConditionalGeneration)
|
||||
|
||||
dataset = ImageDataset(
|
||||
prompts,
|
||||
image_paths,
|
||||
tokenizer,
|
||||
llava_model.config.image_token_index,
|
||||
llava_model.config.image_seq_length,
|
||||
)
|
||||
dataloader = DataLoader(
|
||||
dataset,
|
||||
collate_fn=dataset.collate_fn,
|
||||
num_workers=args.num_workers,
|
||||
shuffle=False,
|
||||
drop_last=False,
|
||||
batch_size=args.batch_size,
|
||||
)
|
||||
end_of_header_id = tokenizer.convert_tokens_to_ids("<|end_header_id|>")
|
||||
end_of_turn_id = tokenizer.convert_tokens_to_ids("<|eot_id|>")
|
||||
assert isinstance(end_of_header_id, int) and isinstance(end_of_turn_id, int)
|
||||
|
||||
pbar = tqdm(total=len(image_paths), desc="Captioning images...", dynamic_ncols=True)
|
||||
for batch in dataloader:
|
||||
vision_dtype = (
|
||||
llava_model.vision_tower.vision_model.embeddings.patch_embedding.weight.dtype
|
||||
)
|
||||
vision_device = (
|
||||
llava_model.vision_tower.vision_model.embeddings.patch_embedding.weight.device
|
||||
)
|
||||
language_device = (
|
||||
llava_model.language_model.get_input_embeddings().weight.device
|
||||
)
|
||||
|
||||
# Move to GPU
|
||||
pixel_values = batch["pixel_values"].to(vision_device, non_blocking=True)
|
||||
input_ids = batch["input_ids"].to(language_device, non_blocking=True)
|
||||
attention_mask = batch["attention_mask"].to(language_device, non_blocking=True)
|
||||
|
||||
# Normalize the image
|
||||
pixel_values = pixel_values / 255.0
|
||||
pixel_values = TVF.normalize(pixel_values, [0.5], [0.5])
|
||||
pixel_values = pixel_values.to(vision_dtype)
|
||||
|
||||
# Generate the captions
|
||||
generate_ids = llava_model.generate(
|
||||
input_ids=input_ids,
|
||||
pixel_values=pixel_values,
|
||||
attention_mask=attention_mask,
|
||||
max_new_tokens=args.max_new_tokens,
|
||||
do_sample=not args.greedy,
|
||||
suppress_tokens=None,
|
||||
use_cache=True,
|
||||
temperature=args.temperature,
|
||||
top_k=args.top_k,
|
||||
top_p=args.top_p,
|
||||
)
|
||||
|
||||
# Trim off the prompts
|
||||
assert isinstance(generate_ids, torch.Tensor)
|
||||
generate_ids = generate_ids.tolist()
|
||||
generate_ids = [
|
||||
trim_off_prompt(ids, end_of_header_id, end_of_turn_id)
|
||||
for ids in generate_ids
|
||||
]
|
||||
|
||||
# Decode the captions
|
||||
captions = tokenizer.batch_decode(
|
||||
generate_ids, skip_special_tokens=False, clean_up_tokenization_spaces=False
|
||||
)
|
||||
captions = [c.strip() for c in captions]
|
||||
|
||||
for path, caption in zip(batch["paths"], captions):
|
||||
write_caption(Path(path), caption)
|
||||
|
||||
pbar.update(len(captions))
|
||||
|
||||
|
||||
def trim_off_prompt(input_ids: list[int], eoh_id: int, eot_id: int) -> list[int]:
|
||||
# Trim off the prompt
|
||||
while True:
|
||||
try:
|
||||
i = input_ids.index(eoh_id)
|
||||
except ValueError:
|
||||
break
|
||||
|
||||
input_ids = input_ids[i + 1 :]
|
||||
|
||||
# Trim off the end
|
||||
try:
|
||||
i = input_ids.index(eot_id)
|
||||
except ValueError:
|
||||
return input_ids
|
||||
|
||||
return input_ids[:i]
|
||||
|
||||
|
||||
def write_caption(image_path: Path, caption: str):
|
||||
caption_path = image_path.with_suffix(".txt")
|
||||
|
||||
try:
|
||||
f = os.open(
|
||||
caption_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
||||
) # Write-only, create if not exist, fail if exists
|
||||
except FileExistsError:
|
||||
logging.warning(f"Caption file '{caption_path}' already exists")
|
||||
return
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to open caption file '{caption_path}': {e}")
|
||||
return
|
||||
|
||||
try:
|
||||
os.write(f, caption.encode("utf-8"))
|
||||
os.close(f)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to write caption to '{caption_path}': {e}")
|
||||
return
|
||||
|
||||
|
||||
class ImageDataset(Dataset):
|
||||
def __init__(
|
||||
self,
|
||||
prompts: list[Prompt],
|
||||
paths: list[Path],
|
||||
tokenizer: PreTrainedTokenizer | PreTrainedTokenizerFast,
|
||||
image_token_id: int,
|
||||
image_seq_length: int,
|
||||
):
|
||||
self.prompts = prompts
|
||||
self.paths = paths
|
||||
self.tokenizer = tokenizer
|
||||
self.image_token_id = image_token_id
|
||||
self.image_seq_length = image_seq_length
|
||||
self.pad_token_id = tokenizer.pad_token_id
|
||||
|
||||
def __len__(self):
|
||||
return len(self.paths)
|
||||
|
||||
def __getitem__(self, idx: int) -> dict:
|
||||
path = self.paths[idx]
|
||||
|
||||
# Pick a prompt
|
||||
prompt_str = random.choices(
|
||||
self.prompts, weights=[p.weight for p in self.prompts]
|
||||
)[0].prompt
|
||||
|
||||
# Preprocess image
|
||||
# NOTE: I don't use the Processor here and instead do it manually.
|
||||
# This is because in my testing a simple resize in Pillow yields higher quality results than the Processor,
|
||||
# and the Processor had some buggy behavior on some images.
|
||||
# And yes, with the so400m model, the model expects the image to be squished into a square, not padded.
|
||||
try:
|
||||
image = Image.open(path)
|
||||
if image.size != (384, 384):
|
||||
image = image.resize((384, 384), Image.LANCZOS)
|
||||
image = image.convert("RGB")
|
||||
pixel_values = TVF.pil_to_tensor(image)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to load image '{path}': {e}")
|
||||
pixel_values = None # Will be filtered out later
|
||||
|
||||
# Build the conversation
|
||||
convo = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a helpful image captioner.",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": prompt_str,
|
||||
},
|
||||
]
|
||||
|
||||
# Format the conversation
|
||||
convo_string = self.tokenizer.apply_chat_template(
|
||||
convo, tokenize=False, add_generation_prompt=True
|
||||
)
|
||||
assert isinstance(convo_string, str)
|
||||
|
||||
# Tokenize the conversation
|
||||
convo_tokens = self.tokenizer.encode(
|
||||
convo_string, add_special_tokens=False, truncation=False
|
||||
)
|
||||
|
||||
# Repeat the image tokens
|
||||
input_tokens = []
|
||||
for token in convo_tokens:
|
||||
if token == self.image_token_id:
|
||||
input_tokens.extend([self.image_token_id] * self.image_seq_length)
|
||||
else:
|
||||
input_tokens.append(token)
|
||||
|
||||
input_ids = torch.tensor(input_tokens, dtype=torch.long)
|
||||
attention_mask = torch.ones_like(input_ids)
|
||||
|
||||
return {
|
||||
"path": path,
|
||||
"pixel_values": pixel_values,
|
||||
"input_ids": input_ids,
|
||||
"attention_mask": attention_mask,
|
||||
}
|
||||
|
||||
def collate_fn(self, batch: list[dict]) -> dict:
|
||||
# Filter out images that failed to load
|
||||
batch = [item for item in batch if item["pixel_values"] is not None]
|
||||
|
||||
# Pad input_ids and attention_mask
|
||||
# Have to use left padding because HF's generate can't handle right padding it seems
|
||||
max_length = max(item["input_ids"].shape[0] for item in batch)
|
||||
n_pad = [max_length - item["input_ids"].shape[0] for item in batch]
|
||||
input_ids = torch.stack(
|
||||
[
|
||||
torch.nn.functional.pad(
|
||||
item["input_ids"], (n, 0), value=self.pad_token_id
|
||||
)
|
||||
for item, n in zip(batch, n_pad)
|
||||
]
|
||||
)
|
||||
attention_mask = torch.stack(
|
||||
[
|
||||
torch.nn.functional.pad(item["attention_mask"], (n, 0), value=0)
|
||||
for item, n in zip(batch, n_pad)
|
||||
]
|
||||
)
|
||||
|
||||
# Stack pixel values
|
||||
pixel_values = torch.stack([item["pixel_values"] for item in batch])
|
||||
|
||||
# Paths
|
||||
paths = [item["path"] for item in batch]
|
||||
|
||||
return {
|
||||
"paths": paths,
|
||||
"pixel_values": pixel_values,
|
||||
"input_ids": input_ids,
|
||||
"attention_mask": attention_mask,
|
||||
}
|
||||
|
||||
|
||||
def parse_prompts(prompt_str: str | None, prompt_file: str | None) -> list[Prompt]:
|
||||
if prompt_str is not None and prompt_file is not None:
|
||||
raise ValueError("Cannot specify both --prompt and --prompt-file")
|
||||
|
||||
if prompt_str is not None:
|
||||
return [Prompt(prompt=prompt_str, weight=1.0)]
|
||||
|
||||
if prompt_file is None:
|
||||
raise ValueError("Must specify either --prompt or --prompt-file")
|
||||
|
||||
data = json.loads(Path(prompt_file).read_text())
|
||||
|
||||
if not isinstance(data, list):
|
||||
raise ValueError("Expected JSON file to contain a list of prompts")
|
||||
|
||||
prompts = []
|
||||
|
||||
for item in data:
|
||||
if isinstance(item, str):
|
||||
prompts.append(Prompt(prompt=item, weight=1.0))
|
||||
elif (
|
||||
isinstance(item, dict)
|
||||
and "prompt" in item
|
||||
and "weight" in item
|
||||
and isinstance(item["prompt"], str)
|
||||
and isinstance(item["weight"], (int, float))
|
||||
):
|
||||
prompts.append(Prompt(prompt=item["prompt"], weight=item["weight"]))
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid prompt in JSON file. Should be either a string or an object with 'prompt' and 'weight' fields: {item}"
|
||||
)
|
||||
|
||||
if len(prompts) == 0:
|
||||
raise ValueError("No prompts found in JSON file")
|
||||
|
||||
if sum(p.weight for p in prompts) <= 0.0:
|
||||
raise ValueError("Prompt weights must sum to a positive number")
|
||||
|
||||
return prompts
|
||||
|
||||
|
||||
def find_images(glob: str | None, filelist: str | Path | None) -> list[Path]:
|
||||
if glob is None and filelist is None:
|
||||
raise ValueError("Must specify either --glob or --filelist")
|
||||
|
||||
paths = []
|
||||
|
||||
if glob is not None:
|
||||
paths.extend(Path(".").glob(glob))
|
||||
|
||||
if filelist is not None:
|
||||
paths.extend(
|
||||
(
|
||||
Path(line.strip())
|
||||
for line in Path(filelist).read_text().strip().splitlines()
|
||||
if line.strip() != ""
|
||||
)
|
||||
)
|
||||
|
||||
return paths
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
75
resources/scripts/joyCaption/joy.py
Normal file
75
resources/scripts/joyCaption/joy.py
Normal file
@ -0,0 +1,75 @@
|
||||
import torch
|
||||
import torch.amp
|
||||
import torchvision.transforms.functional as TVF
|
||||
from PIL import Image
|
||||
from transformers import AutoTokenizer, LlavaForConditionalGeneration
|
||||
|
||||
|
||||
IMAGE_PATH = "C:/Users/27698/Desktop/node/12/00001.png"
|
||||
PROMPT = "Write a long descriptive caption for this image in a formal tone."
|
||||
MODEL_NAME = "fancyfeast/llama-joycaption-alpha-two-hf-llava"
|
||||
|
||||
|
||||
# Load JoyCaption
|
||||
# bfloat16 is the native dtype of the LLM used in JoyCaption (Llama 3.1)
|
||||
# device_map=0 loads the model into the first GPU
|
||||
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, use_fast=True)
|
||||
llava_model = LlavaForConditionalGeneration.from_pretrained(MODEL_NAME, torch_dtype="bfloat16", device_map="cuda:0")
|
||||
llava_model.eval()
|
||||
|
||||
with torch.no_grad():
|
||||
# Load and preprocess image
|
||||
# Normally you would use the Processor here, but the image module's processor
|
||||
# has some buggy behavior and a simple resize in Pillow yields higher quality results
|
||||
image = Image.open(IMAGE_PATH)
|
||||
|
||||
if image.size != (384, 384):
|
||||
image = image.resize((384, 384), Image.LANCZOS)
|
||||
|
||||
image = image.convert("RGB")
|
||||
pixel_values = TVF.pil_to_tensor(image)
|
||||
|
||||
# Normalize the image
|
||||
pixel_values = pixel_values / 255.0
|
||||
pixel_values = TVF.normalize(pixel_values, [0.5], [0.5])
|
||||
pixel_values = pixel_values.to(torch.bfloat16).unsqueeze(0)
|
||||
|
||||
# Build the conversation
|
||||
convo = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a helpful image captioner.",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": PROMPT,
|
||||
},
|
||||
]
|
||||
|
||||
# Format the conversation
|
||||
convo_string = tokenizer.apply_chat_template(convo, tokenize=False, add_generation_prompt=True)
|
||||
|
||||
# Tokenize the conversation
|
||||
convo_tokens = tokenizer.encode(convo_string, add_special_tokens=False, truncation=False)
|
||||
|
||||
# Repeat the image tokens
|
||||
input_tokens = []
|
||||
for token in convo_tokens:
|
||||
if token == llava_model.config.image_token_index:
|
||||
input_tokens.extend([llava_model.config.image_token_index] * llava_model.config.image_seq_length)
|
||||
else:
|
||||
input_tokens.append(token)
|
||||
|
||||
input_ids = torch.tensor(input_tokens, dtype=torch.long).unsqueeze(0)
|
||||
attention_mask = torch.ones_like(input_ids)
|
||||
|
||||
# Generate the caption
|
||||
generate_ids = llava_model.generate(input_ids=input_ids.to('cuda'), pixel_values=pixel_values.to('cuda'), attention_mask=attention_mask.to('cuda'), max_new_tokens=300, do_sample=True, suppress_tokens=None, use_cache=True)[0]
|
||||
|
||||
# Trim off the prompt
|
||||
generate_ids = generate_ids[input_ids.shape[1]:]
|
||||
|
||||
# Decode the caption
|
||||
caption = tokenizer.decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)
|
||||
caption = caption.strip()
|
||||
print(caption)
|
||||
@ -1,3 +1,4 @@
|
||||
import { escapeRegExp } from "lodash";
|
||||
|
||||
/**
|
||||
* 检查字符串中是不是包含中文或者标点符号
|
||||
@ -67,3 +68,24 @@ export async function ExecuteConcurrently(tasks: Array<() => Promise<any>>, conc
|
||||
}
|
||||
return Promise.all(results);
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换主字符串中的子字符串
|
||||
* @param mainString 主字符串
|
||||
* @param substringArray 子字符串数组
|
||||
* @returns 替换后的字符串
|
||||
*/
|
||||
export function ReplaceSubstrings(mainString: string, substringArray: string[], replacement: string = " "): string {
|
||||
// 按长度降序排序子字符串数组,以确保较长的子字符串先被替换
|
||||
substringArray.sort((a, b) => b.length - a.length);
|
||||
|
||||
// 对每个子字符串进行替换
|
||||
for (const substring of substringArray) {
|
||||
// 创建一个正则表达式,用于全局匹配子字符串
|
||||
const regex = new RegExp(escapeRegExp(substring), 'g');
|
||||
// 将匹配到的子字符串替换为等长的空格
|
||||
mainString = mainString.replace(regex, replacement);
|
||||
}
|
||||
|
||||
return mainString;
|
||||
}
|
||||
@ -5,7 +5,6 @@
|
||||
* @returns 可以解析返回true,否则返回false
|
||||
*/
|
||||
export function ValidateJson(str: string): boolean {
|
||||
|
||||
try {
|
||||
JSON.parse(str);
|
||||
return true
|
||||
|
||||
@ -244,7 +244,7 @@ export class BookTaskDetailService extends BaseRealmService {
|
||||
* 删除满足条件的对象吗,必传小说ID和小说任务ID
|
||||
* @param condition bookId,bookTaskId,name,id
|
||||
*/
|
||||
DeleteBookTaskDetail(condition) {
|
||||
DeleteBookTaskDetail(condition: Book.DeleteBookTaskDetailCondition) {
|
||||
try {
|
||||
if (isEmpty(condition.id) && isEmpty(condition.bookTaskId) && isEmpty(condition.bookId)) {
|
||||
throw new Error('删除小说分镜信息失败,没有必要参数')
|
||||
|
||||
164
src/define/define_string/bookDefineString.ts
Normal file
164
src/define/define_string/bookDefineString.ts
Normal file
@ -0,0 +1,164 @@
|
||||
const BOOK = {
|
||||
MAIN_DATA_RETURN: 'MAIN_DATA_RETURN', // 监听任务返回
|
||||
REPLACE_VIDEO_CURRENT_FRAME: 'REPLACE_VIDEO_CURRENT_FRAME',
|
||||
GET_BOOK_TYPE: 'GET_BOOK_TYPE',
|
||||
ADD_OR_MODIFY_BOOK: 'ADD_OR_MODIFY_BOOK',
|
||||
GET_BOOK_DATA: 'GET_BOOK_DATA',
|
||||
GET_FRAME_DATA: 'GET_FRAME_DATA',
|
||||
GET_BOOK_TASK_DATA: 'GET_BOOK_TASK_DATA',
|
||||
AUTO_ACTION: 'AUTO_ACTION',
|
||||
SAVE_BOOK_SUBTITLE_POSITION: 'SAVE_BOOK_SUBTITLE_POSITION',
|
||||
OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT: 'OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT',
|
||||
GET_CURRENT_FRAME_TEXT: 'GET_CURRENT_FRAME_TEXT',
|
||||
GET_VIDEO_FRAME_TEXT: 'GET_VIDEO_FRAME_TEXT',
|
||||
GET_BOOK_TASK_DETAIL: 'GET_BOOK_TASK_DETAIL',
|
||||
REVERSE_PROMPT_TO_GPT_PROMPT: 'REVERSE_PROMPT_TO_GPT_PROMPT',
|
||||
SINGLE_REVERSE_TO_GPT_PROMPT: 'SINGLE_REVERSE_TO_GPT_PROMPT',
|
||||
SAVE_IMAGE_STYLE: 'SAVE_IMAGE_STYLE',
|
||||
IMAGE_LOCK_OPERATION: "IMAGE_LOCK_OPERATION",
|
||||
DOWNLOAD_IMAGE_AND_SPLIT: "DOWNLOAD_IMAGE_AND_SPLIT",
|
||||
ONE_TO_FOUR_BOOK_TASK: "ONE_TO_FOUR_BOOK_TASK",
|
||||
RESET_BOOK_TASK: "RESET_BOOK_TASK",
|
||||
DELETE_BOOK_TASK: "DELETE_BOOK_TASK",
|
||||
GENERATE_IMAGE_ALL: "GENERATE_IMAGE_ALL",
|
||||
CHECK_IMAGE_FILE_SIZE: "CHECK_IMAGE_FILE_SIZE",
|
||||
HD_IMAGE: "HD_IMAGE",
|
||||
USE_BOOK_VIDEO_DATA_TO_BOOK_TASK: "USE_BOOK_VIDEO_DATA_TO_BOOK_TASK",
|
||||
ADD_JIANYING_DRAFT: "ADD_JIANYING_DRAFT",
|
||||
EXPORT_COPYWRITING: "EXPORT_COPYWRITING",
|
||||
IMPORT_COPYWRITING: 'IMPORT_COPYWRITING',
|
||||
MERGE_PROMPT: "MERGE_PROMPT",
|
||||
RESET_BOOK_DATA: "RESET_BOOK_DATA",
|
||||
DELETE_BOOK_DATA: "DELETE_BOOK_DATA",
|
||||
CLEAR_IMPORT_WORD: "CLEAR_IMPORT_WORD",
|
||||
RESET_GPT_REVERSE_DATA: "RESET_GPT_REVERSE_DATA",
|
||||
REMOVE_MERGE_PROMPT_DATA: "REMOVE_MERGE_PROMPT_DATA",
|
||||
REMOVE_GENERATE_IMAGE: 'REMOVE_GENERATE_IMAGE',
|
||||
ADD_NEW_BOOK_TASK: "ADD_NEW_BOOK_TASK",
|
||||
REPLACE_BOOK_DATA: "REPLACE_BOOK_DATA",
|
||||
SAVE_COPYWRITING: 'SAVE_COPYWRITING',
|
||||
|
||||
//#region 通用
|
||||
|
||||
/** 重置小说批次详细信息的数据 */
|
||||
RESET_BOOK_TASK_DETAIL_DATA: "RESET_BOOK_TASK_DETAIL_DATA",
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 分镜
|
||||
|
||||
/** 剪映抽帧,并判断是不是切割视频 */
|
||||
JIANYING_FRAME: "JIANYING_FRAME",
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 提示词
|
||||
/**
|
||||
* 原创推理提示词
|
||||
*/
|
||||
ORIGINAL_GPT_PROMPT: "ORIGINAL_GPT_PROMPT",
|
||||
|
||||
/** * 原创推理提示词返回 */
|
||||
ORIGINAL_GPT_PROMPT_RETURN: "ORIGINAL_GPT_PROMPT_RETURN",
|
||||
|
||||
/** * 导入提示词,通用 */
|
||||
IMPORT_GPT_PROMPT: "IMPORT_GPT_PROMPT",
|
||||
|
||||
/** 移除不想要的提示词 */
|
||||
REMOVE_BAD_PROMPT: "REMOVE_BAD_PROMPT",
|
||||
//#endregion
|
||||
|
||||
//#region 生图返回相关
|
||||
|
||||
/**
|
||||
* MJ生图返回信息
|
||||
*/
|
||||
MJ_IMAGE_GENERATE_RETURN: 'MJ_IMAGE_GENERATE_RETURN',
|
||||
|
||||
/**
|
||||
* SD生图返回信息
|
||||
*/
|
||||
SD_IMAGE_GENERATE_RETURN: 'SD_IMAGE_GENERATE_RETURN',
|
||||
|
||||
/**
|
||||
* D3 出图返回信息
|
||||
*/
|
||||
D3_IMAGE_GENERATE_RETURN: 'D3_IMAGE_GENERATE_RETURN',
|
||||
|
||||
/**
|
||||
* flux forge 生图返回信息
|
||||
*/
|
||||
FLUX_FORGE_IMAGE_GENERATE_RETURN: "FLUX_FORGE_IMAGE_GENERATE_RETURN",
|
||||
|
||||
/**
|
||||
* flux api 生图返回信息
|
||||
*/
|
||||
FLUX_API_IMAGE_GENERATE_RETURN: "FLUX_API_IMAGE_GENERATE_RETURN",
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 图片相关
|
||||
|
||||
/**
|
||||
* 获取MJ的消息ID,下载图片并分割
|
||||
*/
|
||||
GET_IMAGE_URL_AND_DOWNLOAD: "GET_IMAGE_URL_AND_DOWNLOAD",
|
||||
|
||||
//#endregion
|
||||
|
||||
COMPUTE_STORYBOARD: 'COMPUTE_STORYBOARD',
|
||||
|
||||
GET_FRAME: 'GET_FRAME',
|
||||
|
||||
FRAMING: 'FRAMING',
|
||||
|
||||
//#region 字幕相关
|
||||
|
||||
/**
|
||||
* 获取copywriting
|
||||
*/
|
||||
GET_COPYWRITING: 'GET_COPYWRITING',
|
||||
/**
|
||||
* 获取copywriting的返回信息
|
||||
*/
|
||||
GET_COPYWRITING_RETURN: 'GET_COPYWRITING_RETURN',
|
||||
|
||||
//#endregion
|
||||
|
||||
REMOVE_WATERMARK: 'REMOVE_WATERMARK',
|
||||
REMOVE_WATERMARK_RETURN: 'REMOVE_WATERMARK_RETURN',
|
||||
|
||||
SPLI_TAUDIO: 'SPLI_TAUDIO',
|
||||
SPLI_TAUDIO_RETURN: 'SPLI_TAUDIO_RETURN',
|
||||
|
||||
//#region 反推相关
|
||||
/**
|
||||
* 添加反推任务
|
||||
*/
|
||||
ADD_REVERSE_PROMPT: 'ADD_REVERSE_PROMPT',
|
||||
/**
|
||||
* 反推任务返回信息
|
||||
*/
|
||||
REVERSE_PROMPT_RETURN: 'REVERSE_PROMPT_RETURN',
|
||||
//#endregion
|
||||
|
||||
//#region 翻译相关
|
||||
|
||||
/**
|
||||
* 反推翻译返回信息
|
||||
*/
|
||||
REVERSE_PROMPT_TRANSLATE_RETURN: 'REVERSE_PROMPT_TRANSLATE_RETURN',
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region
|
||||
|
||||
/** 合成视频相关 */
|
||||
ADD_GENERATE_VIDEO_TASK: "ADD_GENERATE_VIDEO_TASK",
|
||||
/** 生成视频返回 */
|
||||
GENERATE_VIDEO_RETURN: "GENERATE_VIDEO_RETURN",
|
||||
//#endregion
|
||||
|
||||
|
||||
}
|
||||
export default BOOK;
|
||||
@ -2,11 +2,13 @@ import { SYSTEM } from "./systemDefineString"
|
||||
import TASK from "./taskDefineString"
|
||||
import TTS from "./ttsDefineString"
|
||||
import SETTING from "./settingDefineString"
|
||||
import BOOK from "./bookDefineString"
|
||||
|
||||
export const DEFINE_STRING = {
|
||||
SYSTEM: SYSTEM,
|
||||
TASK: TASK,
|
||||
TTS: TTS,
|
||||
BOOK: BOOK,
|
||||
SETTING: SETTING,
|
||||
SHOW_GLOBAL_MESSAGE: "SHOW_GLOBAL_MESSAGE",
|
||||
SHOW_GLOBAL_MAIN_NOTIFICATION: 'SHOW_GLOBAL_MAIN_NOTIFICATION',
|
||||
@ -225,146 +227,6 @@ export const DEFINE_STRING = {
|
||||
BATCH_PROCESS_IMAGE_RESULT: 'BATCH_PROCESS_IMAGE_RESULT',
|
||||
PROCESS_IMAGE_WATERMASK_CHECK: "PROCESS_IMAGE_WATERMASK_CHECK"
|
||||
},
|
||||
BOOK: {
|
||||
MAIN_DATA_RETURN: 'MAIN_DATA_RETURN', // 监听任务返回
|
||||
REPLACE_VIDEO_CURRENT_FRAME: 'REPLACE_VIDEO_CURRENT_FRAME',
|
||||
GET_BOOK_TYPE: 'GET_BOOK_TYPE',
|
||||
ADD_OR_MODIFY_BOOK: 'ADD_OR_MODIFY_BOOK',
|
||||
GET_BOOK_DATA: 'GET_BOOK_DATA',
|
||||
GET_FRAME_DATA: 'GET_FRAME_DATA',
|
||||
GET_BOOK_TASK_DATA: 'GET_BOOK_TASK_DATA',
|
||||
AUTO_ACTION: 'AUTO_ACTION',
|
||||
SAVE_BOOK_SUBTITLE_POSITION: 'SAVE_BOOK_SUBTITLE_POSITION',
|
||||
OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT: 'OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT',
|
||||
GET_CURRENT_FRAME_TEXT: 'GET_CURRENT_FRAME_TEXT',
|
||||
GET_VIDEO_FRAME_TEXT: 'GET_VIDEO_FRAME_TEXT',
|
||||
GET_BOOK_TASK_DETAIL: 'GET_BOOK_TASK_DETAIL',
|
||||
REVERSE_PROMPT_TO_GPT_PROMPT: 'REVERSE_PROMPT_TO_GPT_PROMPT',
|
||||
SINGLE_REVERSE_TO_GPT_PROMPT: 'SINGLE_REVERSE_TO_GPT_PROMPT',
|
||||
SAVE_IMAGE_STYLE: 'SAVE_IMAGE_STYLE',
|
||||
IMAGE_LOCK_OPERATION: "IMAGE_LOCK_OPERATION",
|
||||
DOWNLOAD_IMAGE_AND_SPLIT: "DOWNLOAD_IMAGE_AND_SPLIT",
|
||||
ONE_TO_FOUR_BOOK_TASK: "ONE_TO_FOUR_BOOK_TASK",
|
||||
RESET_BOOK_TASK: "RESET_BOOK_TASK",
|
||||
DELETE_BOOK_TASK: "DELETE_BOOK_TASK",
|
||||
GENERATE_IMAGE_ALL: "GENERATE_IMAGE_ALL",
|
||||
CHECK_IMAGE_FILE_SIZE: "CHECK_IMAGE_FILE_SIZE",
|
||||
HD_IMAGE: "HD_IMAGE",
|
||||
USE_BOOK_VIDEO_DATA_TO_BOOK_TASK: "USE_BOOK_VIDEO_DATA_TO_BOOK_TASK",
|
||||
ADD_JIANYING_DRAFT: "ADD_JIANYING_DRAFT",
|
||||
EXPORT_COPYWRITING: "EXPORT_COPYWRITING",
|
||||
IMPORT_COPYWRITING: 'IMPORT_COPYWRITING',
|
||||
MERGE_PROMPT: "MERGE_PROMPT",
|
||||
RESET_BOOK_DATA: "RESET_BOOK_DATA",
|
||||
DELETE_BOOK_DATA: "DELETE_BOOK_DATA",
|
||||
CLEAR_IMPORT_WORD: "CLEAR_IMPORT_WORD",
|
||||
RESET_GPT_REVERSE_DATA: "RESET_GPT_REVERSE_DATA",
|
||||
REMOVE_MERGE_PROMPT_DATA: "REMOVE_MERGE_PROMPT_DATA",
|
||||
REMOVE_GENERATE_IMAGE: 'REMOVE_GENERATE_IMAGE',
|
||||
ADD_NEW_BOOK_TASK: "ADD_NEW_BOOK_TASK",
|
||||
REPLACE_BOOK_DATA: "REPLACE_BOOK_DATA",
|
||||
SAVE_COPYWRITING: 'SAVE_COPYWRITING',
|
||||
|
||||
//#region 提示词
|
||||
/**
|
||||
* 原创推理提示词
|
||||
*/
|
||||
ORIGINAL_GPT_PROMPT: "ORIGINAL_GPT_PROMPT",
|
||||
/**
|
||||
* 原创推理提示词返回
|
||||
*/
|
||||
ORIGINAL_GPT_PROMPT_RETURN: "ORIGINAL_GPT_PROMPT_RETURN",
|
||||
|
||||
/**
|
||||
* 导入提示词,通用
|
||||
*/
|
||||
IMPORT_GPT_PROMPT: "IMPORT_GPT_PROMPT",
|
||||
//#endregion
|
||||
|
||||
//#region 生图返回相关
|
||||
|
||||
/**
|
||||
* MJ生图返回信息
|
||||
*/
|
||||
MJ_IMAGE_GENERATE_RETURN: 'MJ_IMAGE_GENERATE_RETURN',
|
||||
|
||||
/**
|
||||
* SD生图返回信息
|
||||
*/
|
||||
SD_IMAGE_GENERATE_RETURN: 'SD_IMAGE_GENERATE_RETURN',
|
||||
|
||||
/**
|
||||
* D3 出图返回信息
|
||||
*/
|
||||
D3_IMAGE_GENERATE_RETURN: 'D3_IMAGE_GENERATE_RETURN',
|
||||
|
||||
/**
|
||||
* flux forge 生图返回信息
|
||||
*/
|
||||
FLUX_FORGE_IMAGE_GENERATE_RETURN: "FLUX_FORGE_IMAGE_GENERATE_RETURN",
|
||||
|
||||
/**
|
||||
* flux api 生图返回信息
|
||||
*/
|
||||
FLUX_API_IMAGE_GENERATE_RETURN: "FLUX_API_IMAGE_GENERATE_RETURN",
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 图片相关
|
||||
|
||||
/**
|
||||
* 获取MJ的消息ID,下载图片并分割
|
||||
*/
|
||||
GET_IMAGE_URL_AND_DOWNLOAD: "GET_IMAGE_URL_AND_DOWNLOAD",
|
||||
|
||||
//#endregion
|
||||
|
||||
COMPUTE_STORYBOARD: 'COMPUTE_STORYBOARD',
|
||||
|
||||
GET_FRAME: 'GET_FRAME',
|
||||
|
||||
FRAMING: 'FRAMING',
|
||||
|
||||
//#region 字幕相关
|
||||
|
||||
/**
|
||||
* 获取copywriting
|
||||
*/
|
||||
GET_COPYWRITING: 'GET_COPYWRITING',
|
||||
/**
|
||||
* 获取copywriting的返回信息
|
||||
*/
|
||||
GET_COPYWRITING_RETURN: 'GET_COPYWRITING_RETURN',
|
||||
|
||||
//#endregion
|
||||
|
||||
REMOVE_WATERMARK: 'REMOVE_WATERMARK',
|
||||
REMOVE_WATERMARK_RETURN: 'REMOVE_WATERMARK_RETURN',
|
||||
|
||||
SPLI_TAUDIO: 'SPLI_TAUDIO',
|
||||
SPLI_TAUDIO_RETURN: 'SPLI_TAUDIO_RETURN',
|
||||
|
||||
//#region 反推相关
|
||||
/**
|
||||
* 添加反推任务
|
||||
*/
|
||||
ADD_REVERSE_PROMPT: 'ADD_REVERSE_PROMPT',
|
||||
/**
|
||||
* 反推任务返回信息
|
||||
*/
|
||||
REVERSE_PROMPT_RETURN: 'REVERSE_PROMPT_RETURN',
|
||||
//#endregion
|
||||
|
||||
//#region 翻译相关
|
||||
|
||||
/**
|
||||
* 反推翻译返回信息
|
||||
*/
|
||||
REVERSE_PROMPT_TRANSLATE_RETURN: 'REVERSE_PROMPT_TRANSLATE_RETURN',
|
||||
|
||||
//#endregion
|
||||
|
||||
},
|
||||
PROMPT: {
|
||||
GET_SORT_OPTIONS: 'GET_SORT_OPTIONS',
|
||||
SAVE_PROMPT_SORT_DATA: 'SAVE_PROMPT_SORT_DATA',
|
||||
|
||||
@ -208,7 +208,12 @@ export enum BookTaskStatus {
|
||||
COMPOSING_DONE = 'composing_done',
|
||||
|
||||
// 合成视频失败
|
||||
COMPOSING_FAIL = 'composing_fail'
|
||||
COMPOSING_FAIL = 'composing_fail',
|
||||
/** 添加草稿完成 */
|
||||
DRAFT_DONE = 'draft_done',
|
||||
/** 添加草稿失败 */
|
||||
DRAFT_FAIL = 'draft_fail',
|
||||
|
||||
}
|
||||
|
||||
export enum TagDefineType {
|
||||
|
||||
@ -60,6 +60,7 @@ export enum ResponseMessageType {
|
||||
GET_TEXT = 'getText', // 获取文案
|
||||
REMOVE_WATERMARK = "REMOVE_WATERMARK",// 删除水印
|
||||
MJ_REVERSE = 'MJ_REVERSE',// MJ反推,返回反推结果
|
||||
SD_REVERSE = 'SD_REVERSE',// MJ反推,返回反推结果
|
||||
REVERSE_PROMPT_TRANSLATE = 'REVERSE_PROMPT_TRANSLATE',// 反推提示词翻译
|
||||
GPT_PROMPT_TRANSLATE = 'GPT_PROMPT_TRANSLATE', // GPT提示词翻译
|
||||
MJ_IMAGE = 'MJ_IMAGE',// MJ 生成图片
|
||||
|
||||
@ -92,10 +92,11 @@ export function BookIpc() {
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 小说通用操作
|
||||
//#region 小说详情通用操作
|
||||
|
||||
ipcMain.handle(DEFINE_STRING.BOOK.REPLACE_BOOK_DATA, async (event, bookTaskId, replaceData) => await bookGeneral.ReplaceBookData(bookTaskId, replaceData))
|
||||
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 分镜相关
|
||||
@ -116,6 +117,9 @@ export function BookIpc() {
|
||||
await bookFrame.ReplaceVideoCurrentFrame(bookTaskDetailId, currentTime)
|
||||
)
|
||||
|
||||
// 剪映抽帧,并判断是不是切割视频
|
||||
ipcMain.handle(DEFINE_STRING.BOOK.JIANYING_FRAME, async (event, bookTaskId, draftName) => await bookFrame.JianyingFrame(bookTaskId, draftName))
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 提示词相关
|
||||
@ -154,6 +158,12 @@ export function BookIpc() {
|
||||
async (event, bookTaskId: string, txtPath: string) => await bookPrompt.ImportGPTPrompt(bookTaskId, txtPath)
|
||||
)
|
||||
|
||||
/** 移除不想要的提示词 */
|
||||
ipcMain.handle(
|
||||
DEFINE_STRING.BOOK.REMOVE_BAD_PROMPT,
|
||||
async (event, bookTaskId: string) => await bookPrompt.RemoveBadPrompt(bookTaskId)
|
||||
)
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 文案相关
|
||||
@ -290,13 +300,13 @@ export function BookIpc() {
|
||||
// 重置小说批次数据
|
||||
ipcMain.handle(
|
||||
DEFINE_STRING.BOOK.RESET_BOOK_TASK,
|
||||
async (event, bookTaskId) => await bookTask.ReSetBookTask(bookTaskId)
|
||||
async (event, bookTaskId, operateBookType) => await bookTask.ReSetBookTask(bookTaskId, operateBookType)
|
||||
)
|
||||
|
||||
// 删除小说批次数据
|
||||
ipcMain.handle(
|
||||
DEFINE_STRING.BOOK.DELETE_BOOK_TASK,
|
||||
async (event, bookTaskId) => await bookTask.DeleteBookTask(bookTaskId)
|
||||
async (event, bookTaskId, operateBookType) => await bookTask.DeleteBookTask(bookTaskId, operateBookType)
|
||||
)
|
||||
|
||||
|
||||
@ -312,5 +322,10 @@ export function BookIpc() {
|
||||
DEFINE_STRING.BOOK.ADD_JIANYING_DRAFT,
|
||||
async (event, id: string | string[], operateBookType: OperateBookType) => await bookVideo.AddJianyingDraft(id, operateBookType)
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
DEFINE_STRING.BOOK.ADD_GENERATE_VIDEO_TASK,
|
||||
async (event, id: string | string[], operateBookType: OperateBookType) => await bookVideo.AddGenerateVideoTask(id, operateBookType)
|
||||
)
|
||||
//#endregion
|
||||
}
|
||||
|
||||
@ -236,7 +236,7 @@ export class ReverseBook {
|
||||
if (operateBookType == OperateBookType.BOOKTASK) {
|
||||
bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
|
||||
bookTaskId: id
|
||||
})
|
||||
});
|
||||
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
|
||||
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id);
|
||||
bookTaskDetails = [bookTaskDetail]
|
||||
@ -263,7 +263,11 @@ export class ReverseBook {
|
||||
if (bookTaskDetailIds.length <= 0) {
|
||||
throw new Error("没有需要反推的数据,请检查")
|
||||
}
|
||||
await this.AddReversePromptTask(bookTaskDetailIds, type);
|
||||
// 判断是不是SD反推并且是反推的全部的数据
|
||||
if (type == BookType.SD_REVERSE && operateBookType == OperateBookType.BOOKTASK) {
|
||||
bookTaskDetailIds = [null];
|
||||
}
|
||||
await this.AddReversePromptTask(bookTaskDetailIds, type, id);
|
||||
return successMessage(null, "添加反推任务成功", 'ReverseBook_AddReversePrompt')
|
||||
} catch (error) {
|
||||
return errorMessage("添加反推任务失败,错误信息如下:" + error.message, "ReverseBook_AddReversePrompt")
|
||||
@ -276,8 +280,14 @@ export class ReverseBook {
|
||||
* @param type 反推的类型
|
||||
* @returns
|
||||
*/
|
||||
async AddReversePromptTask(bookTaskDetailIds: string[], type: BookType): Promise<void> {
|
||||
async AddReversePromptTask(bookTaskDetailIds: string[], type: BookType, bookTaskId: string): Promise<void> {
|
||||
try {
|
||||
if (bookTaskDetailIds.length == 1 && bookTaskDetailIds[0] == null && type == BookType.SD_REVERSE) {
|
||||
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId);
|
||||
await this.bookServiceBasic.AddBookBackTask(bookTask.bookId, BookBackTaskType.SD_REVERSE, TaskExecuteType.AUTO, bookTaskId, bookTaskDetailIds[0], DEFINE_STRING.BOOK.REVERSE_PROMPT_RETURN
|
||||
);
|
||||
return;
|
||||
}
|
||||
for (let index = 0; index < bookTaskDetailIds.length; index++) {
|
||||
const bookTaskDetailId = bookTaskDetailIds[index];
|
||||
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(bookTaskDetailId)
|
||||
@ -327,7 +337,7 @@ export class ReverseBook {
|
||||
res = await this.mjOpt.MJImage2Text(task);
|
||||
break
|
||||
case BookBackTaskType.SD_REVERSE:
|
||||
res = await this.SDReversePrompt(task);
|
||||
res = await this.sdOpt.SDReversePrompt(task);
|
||||
break
|
||||
default:
|
||||
throw new Error("未知的任务类型")
|
||||
|
||||
@ -11,6 +11,10 @@ import { LogScheduler } from '../task/logScheduler';
|
||||
import { BookBasic } from "./BooKBasic";
|
||||
import { LoggerStatus, OtherData } from "../../../define/enum/softwareEnum";
|
||||
import { BasicReverse } from "./basicReverse";
|
||||
import JianyingService from "../jianying/jianyingService";
|
||||
import { ValidateJson } from "../../../define/Tools/validate";
|
||||
import { BookTaskStatus } from "../../../define/enum/bookEnum";
|
||||
import { define } from "../../../define/define";
|
||||
|
||||
export class BookFrame {
|
||||
bookServiceBasic: BookServiceBasic
|
||||
@ -18,14 +22,86 @@ export class BookFrame {
|
||||
logScheduler: LogScheduler
|
||||
basicReverse: BasicReverse
|
||||
bookBasic: BookBasic
|
||||
jianyingService: JianyingService
|
||||
constructor() {
|
||||
this.bookServiceBasic = new BookServiceBasic();
|
||||
this.ffmpegOptions = new FfmpegOptions();
|
||||
this.logScheduler = new LogScheduler()
|
||||
this.bookBasic = new BookBasic()
|
||||
this.basicReverse = new BasicReverse()
|
||||
this.jianyingService = new JianyingService()
|
||||
}
|
||||
|
||||
/**
|
||||
* 剪映抽帧,并判断是不是切割视频
|
||||
* @param draftName 草稿名字
|
||||
* @param clipVideo 是不是切割视频
|
||||
*/
|
||||
async JianyingFrame(bookTaskId: string, draftName: string) {
|
||||
try {
|
||||
console.log(draftName);
|
||||
let draftPath = path.join(global.config.draft_path, draftName);
|
||||
if (!await CheckFileOrDirExist(draftPath)) {
|
||||
throw new Error("未找到对应的草稿文件,请检查");
|
||||
}
|
||||
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId);
|
||||
let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId);
|
||||
|
||||
// 在执行操作之前,先删除之前的文件
|
||||
await this.bookServiceBasic.DeleteBookTaskDetailData({
|
||||
bookTaskId: bookTask.id
|
||||
})
|
||||
// 在删除对应的图片和视频文件
|
||||
let input = path.resolve(book.bookFolderPath, "tmp/input");
|
||||
if (await CheckFileOrDirExist(input)) {
|
||||
await DeleteFolderAllFile(input, true);
|
||||
}
|
||||
|
||||
await this.jianyingService.GetDraftFrameAndText(draftPath, book.bookFolderPath, 'input');
|
||||
let draftTimeLinePath = path.resolve(book.bookFolderPath, "draftFrameData.json");
|
||||
if (!await CheckFileOrDirExist(draftTimeLinePath)) {
|
||||
throw new Error("没有找到对应的草稿时间轴数据,请检查");
|
||||
}
|
||||
let draftTimeLineString = await fs.promises.readFile(draftTimeLinePath, "utf-8");
|
||||
if (!ValidateJson(draftTimeLineString)) {
|
||||
throw new Error("草稿时间轴数据格式错误,请检查");
|
||||
}
|
||||
let draftTimeLine = JSON.parse(draftTimeLineString);
|
||||
|
||||
// 开始修改数据
|
||||
// 获取SD设置
|
||||
let sdConifg = JSON.parse(await fs.promises.readFile(define.sd_setting, 'utf-8'));
|
||||
let adetailer = false;
|
||||
if (sdConifg && sdConifg?.webui?.adetailer) {
|
||||
adetailer = true;
|
||||
}
|
||||
// 写入数据
|
||||
for (let i = 0; i < draftTimeLine.length; i++) {
|
||||
const element = draftTimeLine[i];
|
||||
await this.bookServiceBasic.AddBookTaskDetail({
|
||||
bookTaskId: bookTaskId,
|
||||
bookId: bookTask.bookId,
|
||||
startTime: Math.ceil(element.startTime / 1000),
|
||||
endTime: Math.ceil(element.endTime / 1000),
|
||||
status: BookTaskStatus.WAIT,
|
||||
word: element.word,
|
||||
videoPath: element.videoPath,
|
||||
oldImage: path.relative(define.project_path, element.framePath),
|
||||
afterGpt: element.text,
|
||||
subValue: undefined,
|
||||
timeLimit: `${element.startTime} -- ${element.endTime}`,
|
||||
// 新增修脸跟随
|
||||
adetailer: adetailer
|
||||
})
|
||||
}
|
||||
return successMessage(null, '剪映抽帧成功', 'BookFrame_JianyingFrame')
|
||||
} catch (error) {
|
||||
return errorMessage('剪映抽帧失败,失败信息如下:' + error.toString(), 'BookFrame_JianyingFrame')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//#region 替换指定分镜的视频当前帧
|
||||
/**
|
||||
* 替换指定分镜的视频当前帧
|
||||
* @param bookTaskDetailId 指定的小说分镜ID
|
||||
@ -65,6 +141,8 @@ export class BookFrame {
|
||||
return errorMessage('替换指定分镜的视频当前帧失败,失败信息如下:' + error.toString(), 'BookFrame_ReplaceVideoCurrentFrame');
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region 进行分镜截取的一些操作
|
||||
|
||||
|
||||
@ -73,14 +73,6 @@ export class BookImage {
|
||||
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||
const element = bookTaskDetails[i];
|
||||
await this.bookServiceBasic.DeleteBoookTaskDetailGenerateImage(element.id);
|
||||
// if (bookTask.imageCategory == BookImageCategory.MJ) {
|
||||
// } else if (bookTask.imageCategory == BookImageCategory.D3) {
|
||||
// throw new Error('暂时不支持D3生成的图片删除')
|
||||
// } else if (bookTask.imageCategory == BookImageCategory.SD) {
|
||||
// await this.bookServiceBasic.DeleteBoookTaskDetailGenerateImage(element.id);
|
||||
// } else {
|
||||
// throw new Error('未知的小说类型,请检查')
|
||||
// }
|
||||
// 上面的信息重置完毕之后,开始删除文件信息
|
||||
let outImage = element.outImagePath;
|
||||
if (await CheckFileOrDirExist(outImage)) {
|
||||
|
||||
@ -5,12 +5,14 @@ import { GeneralResponse } from "../../../model/generalResponse";
|
||||
import { errorMessage, SendReturnMessage, successMessage } from "../../Public/generalTools";
|
||||
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
|
||||
import { GptService } from "../GPT/gpt";
|
||||
import { ExecuteConcurrently } from "../../../define/Tools/common";
|
||||
import { ExecuteConcurrently, ReplaceSubstrings } from "../../../define/Tools/common";
|
||||
import { DEFINE_STRING } from "../../../define/define_string";
|
||||
import { CheckFileOrDirExist } from "../../../define/Tools/file";
|
||||
import fs from 'fs';
|
||||
import path from 'path'
|
||||
import readline from 'readline';
|
||||
import { define } from "../../../define/define";
|
||||
import { ValidateJson } from "../../../define/Tools/validate";
|
||||
|
||||
|
||||
export class BookPrompt {
|
||||
@ -21,6 +23,51 @@ export class BookPrompt {
|
||||
this.gptService = new GptService()
|
||||
}
|
||||
|
||||
|
||||
//#region 移除不想要的提示词
|
||||
/**
|
||||
* 移除不想要的提示词
|
||||
* @param bookTaskId 小说任务的ID
|
||||
*/
|
||||
async RemoveBadPrompt(bookTaskId: string) {
|
||||
try {
|
||||
let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
|
||||
bookTaskId: bookTaskId
|
||||
})
|
||||
|
||||
let sdConfigString = await fs.promises.readFile(define.sd_setting, 'utf-8')
|
||||
if (isEmpty(sdConfigString)) {
|
||||
throw new Error('读取SD配置文件失败')
|
||||
}
|
||||
if (!ValidateJson(sdConfigString)) {
|
||||
throw new Error('SD配置文件格式错误')
|
||||
}
|
||||
let sdConfig = JSON.parse(sdConfigString)
|
||||
let badPrompt = sdConfig.tag.badPrompt;
|
||||
let badPromptList = badPrompt.split(',')
|
||||
let result = []
|
||||
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||
const element = bookTaskDetails[i];
|
||||
if (isEmpty(element.gptPrompt)) {
|
||||
continue;
|
||||
}
|
||||
let newText = ReplaceSubstrings(element.gptPrompt, badPromptList, "");
|
||||
newText.replaceAll(",,", ",");
|
||||
await this.bookServiceBasic.UpdateBookTaskDetail(element.id, {
|
||||
gptPrompt: newText
|
||||
})
|
||||
result.push({
|
||||
id: element.id,
|
||||
gptPrompt: newText
|
||||
})
|
||||
}
|
||||
return successMessage(result, "移除不想要的提示词成功", "Book_RemoveBadPrompt")
|
||||
} catch (error) {
|
||||
return errorMessage("移除不想要的提示词失败,错误信息如下:" + error.message, "Book_RemoveBadPrompt")
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 提示词通用
|
||||
|
||||
/**
|
||||
@ -191,7 +238,13 @@ export class BookPrompt {
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if (type == BookType.SD_REVERSE) { }
|
||||
} else if (type == BookType.SD_REVERSE) {
|
||||
// SD反推
|
||||
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||
const element = bookTaskDetails[i];
|
||||
await this.bookServiceBasic.UpdateBookTaskDetail(element.id, { gptPrompt: undefined });
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error("SD反推删除还不支持")
|
||||
}
|
||||
|
||||
@ -233,17 +233,36 @@ export class BookTask {
|
||||
* 重置小说任务数据
|
||||
* @param bookTaskId 小说任务ID
|
||||
*/
|
||||
async ReSetBookTask(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
||||
async ReSetBookTask(bookTaskId: string | string[], operateBookType = OperateBookType.BOOKTASK as OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
||||
try {
|
||||
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId);
|
||||
await this.bookServiceBasic.ResetBookTask(bookTaskId);
|
||||
let bookTasks = [] as Book.SelectBookTask[]
|
||||
if (operateBookType == OperateBookType.BOOKTASK) {
|
||||
let tempBookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId as string);
|
||||
bookTasks.push(tempBookTask)
|
||||
} else if (operateBookType == OperateBookType.ASSIGNBOOKTASK) {
|
||||
for (let i = 0; i < bookTaskId.length; i++) {
|
||||
const element = bookTaskId[i];
|
||||
let tempBookTask = await this.bookServiceBasic.GetBookTaskDataById(element);
|
||||
bookTasks.push(tempBookTask);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error("无效的操作类型")
|
||||
}
|
||||
if (bookTasks.length <= 0) {
|
||||
throw new Error("没有找到对应的小说任务")
|
||||
}
|
||||
for (let i = 0; i < bookTasks.length; i++) {
|
||||
const element = bookTasks[i];
|
||||
await this.bookServiceBasic.ResetBookTask(element.id);
|
||||
// 数据库删除完毕,看是删除对应的文件(主要是图片)
|
||||
let imageFolder = bookTask.imageFolder;
|
||||
let imageFolder = element.imageFolder;
|
||||
// 整个删掉在重建
|
||||
if (await CheckFileOrDirExist(imageFolder)) {
|
||||
await DeleteFolderAllFile(imageFolder)
|
||||
}
|
||||
await CheckFolderExistsOrCreate(imageFolder)
|
||||
}
|
||||
return successMessage(null, "重置小说数据成功", "BookTask_ReSetBookTask")
|
||||
} catch (error) {
|
||||
return errorMessage('重置小说批次数据失败,错误信息如下' + error.toString(), "BookTask_ReSetBookTask");
|
||||
@ -254,16 +273,33 @@ export class BookTask {
|
||||
* 删除对应的小说批次数据
|
||||
* @param bookTaskId 小说批次ID
|
||||
*/
|
||||
async DeleteBookTask(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
||||
async DeleteBookTask(bookTaskId: string | string[], operateBookType = OperateBookType.BOOKTASK as OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
||||
try {
|
||||
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId);
|
||||
let bookTasks = [] as Book.SelectBookTask[]
|
||||
if (operateBookType == OperateBookType.BOOKTASK) {
|
||||
let tempBookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId as string);
|
||||
bookTasks.push(tempBookTask)
|
||||
} else if (operateBookType == OperateBookType.ASSIGNBOOKTASK) {
|
||||
for (let i = 0; i < bookTaskId.length; i++) {
|
||||
const element = bookTaskId[i];
|
||||
let tempBookTask = await this.bookServiceBasic.GetBookTaskDataById(element);
|
||||
bookTasks.push(tempBookTask);
|
||||
}
|
||||
} else {
|
||||
throw new Error("无效的操作类型")
|
||||
}
|
||||
|
||||
for (let i = 0; i < bookTasks.length; i++) {
|
||||
const element = bookTasks[i];
|
||||
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(element.id);
|
||||
let imageFolder = bookTask.imageFolder;
|
||||
// 先删除每个批次对应的数据,然后删除批次
|
||||
await this.bookServiceBasic.DeleteBookTaskData(bookTaskId);
|
||||
await this.bookServiceBasic.DeleteBookTaskData(element.id);
|
||||
// 删除成功,直接把对应的出图文件夹删掉
|
||||
if (await CheckFileOrDirExist(imageFolder)) {
|
||||
await DeleteFolderAllFile(imageFolder, true)
|
||||
}
|
||||
}
|
||||
return successMessage(null, "删除小说批次数据成功", "BookTask_DeleteBookTask")
|
||||
} catch (error) {
|
||||
return errorMessage('删除小说批次数据失败,错误信息如下' + error.toString(), "BookTask_DeleteBookTask");
|
||||
|
||||
@ -1,26 +1,36 @@
|
||||
import { OperateBookType } from "../../../define/enum/bookEnum";
|
||||
import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, OperateBookType, TaskExecuteType } from "../../../define/enum/bookEnum";
|
||||
import { Book } from "../../../model/book";
|
||||
import { errorMessage, successMessage } from "../../Public/generalTools";
|
||||
import { GeneralResponse } from "../../../model/generalResponse";
|
||||
import { Setting } from '../../../main/setting/setting'
|
||||
import path from 'path'
|
||||
import { CheckFolderExistsOrCreate } from "../../../define/Tools/file";
|
||||
import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder, GetFilesWithExtensions } from "../../../define/Tools/file";
|
||||
import fs from 'fs'
|
||||
import { ClipDraft } from '../../Public/clipDraft'
|
||||
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
|
||||
import { isEmpty } from "lodash";
|
||||
import JianyingService from "../jianying/jianyingService";
|
||||
import { DEFINE_STRING } from "../../../define/define_string";
|
||||
import { define } from "../../../define/define";
|
||||
import { ValidateJson } from "../../../define/Tools/validate";
|
||||
import BookSetting from "@/main/setting/bookSetting";
|
||||
import util from 'util';
|
||||
import { spawn, exec } from 'child_process';
|
||||
import { SendMessageToRenderer } from "../globalService";
|
||||
const execAsync = util.promisify(exec);
|
||||
|
||||
export class BookVideo {
|
||||
setting: Setting
|
||||
bookServiceBasic: BookServiceBasic
|
||||
jianyingService: JianyingService
|
||||
bookSetting: BookSetting
|
||||
constructor() {
|
||||
this.setting = new Setting(global)
|
||||
this.bookServiceBasic = new BookServiceBasic()
|
||||
this.jianyingService = new JianyingService()
|
||||
}
|
||||
|
||||
//#region 引用主小说相关数据
|
||||
|
||||
/**
|
||||
* 将小说中的生成视频的数据,写道小说批次任务中
|
||||
@ -29,7 +39,6 @@ export class BookVideo {
|
||||
*/
|
||||
async UseBookVideoDataToBookTask(id: string, operateBookType: OperateBookType) {
|
||||
try {
|
||||
console.log(id, operateBookType)
|
||||
let book = undefined as Book.SelectBook;
|
||||
let bookTasks = undefined as Book.SelectBookTask[];
|
||||
if (operateBookType == OperateBookType.BOOK) {
|
||||
@ -61,7 +70,7 @@ export class BookVideo {
|
||||
// 将修改数据放在一个事务中
|
||||
for (let i = 0; i < bookTasks.length; i++) {
|
||||
const element = bookTasks[i];
|
||||
this.bookServiceBasic.UpdetedBookTaskData(element.id, {
|
||||
await this.bookServiceBasic.UpdetedBookTaskData(element.id, {
|
||||
backgroundMusic: book.backgroundMusic,
|
||||
friendlyReminder: book.friendlyReminder,
|
||||
draftSrtStyle: book.draftSrtStyle,
|
||||
@ -81,6 +90,10 @@ export class BookVideo {
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 生成配置文件,用于生成草稿或者是合成视频
|
||||
|
||||
/**
|
||||
* 生成配置文件,用于生成草稿或者是合成视频
|
||||
* @param book 小说数据
|
||||
@ -98,7 +111,7 @@ export class BookVideo {
|
||||
}
|
||||
// 开始生成配置文件
|
||||
|
||||
let configPath = path.join(book.bookFolderPath, "scripts/config.json");
|
||||
let configPath = path.join(book.bookFolderPath, `scripts/${bookTask.name}_config.json`);
|
||||
await CheckFolderExistsOrCreate(path.dirname(configPath));
|
||||
|
||||
// 开始配置
|
||||
@ -145,11 +158,16 @@ export class BookVideo {
|
||||
}
|
||||
// 完毕,将数据写出
|
||||
await fs.promises.writeFile(configPath, JSON.stringify(configData));
|
||||
// 复制一个到config.json中
|
||||
await CopyFileOrFolder(configPath, path.join(book.bookFolderPath, 'scripts/config.json'));
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 添加剪映草稿
|
||||
|
||||
/**
|
||||
* 添加剪映草稿
|
||||
@ -159,7 +177,6 @@ export class BookVideo {
|
||||
*/
|
||||
async AddJianyingDraft(id: string | string[], operateBookType: OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
||||
try {
|
||||
console.log(id, operateBookType)
|
||||
let book = undefined as Book.SelectBook
|
||||
let bookTasks = [] as Book.SelectBookTask[]
|
||||
if (operateBookType == OperateBookType.ASSIGNBOOKTASK) {
|
||||
@ -231,4 +248,211 @@ export class BookVideo {
|
||||
return errorMessage('添加剪映草稿失败,错误信息如下:' + error.toString(), "BookTask_AddJianyingDraft");
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 添加合成视频任务
|
||||
|
||||
async AddGenerateVideoTask(id: string | string[], operateBookType: OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
||||
try {
|
||||
let book = undefined as Book.SelectBook
|
||||
let bookTasks = [] as Book.SelectBookTask[]
|
||||
if (operateBookType == OperateBookType.ASSIGNBOOKTASK) {
|
||||
if (id.length <= 0) {
|
||||
throw new Error("没有找到小说批次任务,请检查");
|
||||
}
|
||||
for (let i = 0; i < id.length; i++) {
|
||||
const element = id[i];
|
||||
let tempBookTask = await this.bookServiceBasic.GetBookTaskDataById(element as string);
|
||||
bookTasks.push(tempBookTask)
|
||||
if (book == undefined) {
|
||||
book = await this.bookServiceBasic.GetBookDataById(tempBookTask.bookId);
|
||||
}
|
||||
// 这边要将主小说的数据写入到选中批次中
|
||||
let useRes = await this.UseBookVideoDataToBookTask(element, OperateBookType.BOOKTASK);
|
||||
if (useRes.code == 0) {
|
||||
throw new Error(useRes.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else if (operateBookType == OperateBookType.BOOKTASK) {
|
||||
// 直接获取对应的数据
|
||||
let tempBookTask = await this.bookServiceBasic.GetBookTaskDataById(id as string);
|
||||
if (tempBookTask == null) {
|
||||
throw new Error("没有找到小说批次任务,请检查");
|
||||
}
|
||||
bookTasks = [tempBookTask]
|
||||
book = await this.bookServiceBasic.GetBookDataById(tempBookTask.bookId);
|
||||
if (book == null) {
|
||||
throw new Error
|
||||
}
|
||||
} else {
|
||||
throw new Error("未知的操作类型");
|
||||
}
|
||||
|
||||
if (bookTasks.length <= 0) {
|
||||
throw new Error("没有找到小说批次任务,请检查")
|
||||
}
|
||||
|
||||
let result = []
|
||||
for (let i = 0; i < bookTasks.length; i++) {
|
||||
const element = bookTasks[i];
|
||||
await this.GenerateConfigFile(book, element);
|
||||
// 数据处理完毕,开始添加任务
|
||||
await this.bookServiceBasic.AddBookBackTask(book.id, BookBackTaskType.COMPOSING, TaskExecuteType.AUTO, element.id, null, DEFINE_STRING.BOOK.GENERATE_VIDEO_RETURN);
|
||||
// 修改状态
|
||||
await this.bookServiceBasic.UpdetedBookTaskData(element.id, {
|
||||
status: BookTaskStatus.COMPOSING,
|
||||
});
|
||||
result.push(element.name);
|
||||
}
|
||||
|
||||
// 添加完毕
|
||||
return successMessage(result, `${result.join('\n')} ${'\n'} 合成视频任务添加成功`, "BookTask_AddGenerateVideoTask")
|
||||
} catch (error) {
|
||||
return errorMessage('添加剪映草稿失败,错误信息如下:' + error.toString(), "BookTask_AddJianyingDraft");
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 合成视频任务执行
|
||||
/**
|
||||
* 合成视频任务执行
|
||||
* @param task
|
||||
*/
|
||||
async GenerateVideo(task: TaskModal.Task): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
||||
try {
|
||||
console.log(task);
|
||||
this.bookSetting = new BookSetting();
|
||||
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(task.bookTaskId);
|
||||
let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId);
|
||||
if (isEmpty(bookTask.srtPath) || !await CheckFileOrDirExist(bookTask.srtPath)) {
|
||||
throw new Error("没有找到对应的SRT文件,请检查");
|
||||
}
|
||||
if (isEmpty(bookTask.audioPath) || !await CheckFileOrDirExist(bookTask.audioPath)) {
|
||||
throw new Error("没有找到对应的音频文件,请检查");
|
||||
}
|
||||
if (isEmpty(bookTask.backgroundMusic)) {
|
||||
throw new Error("没有找到对应的背景音乐文件夹,请检查");
|
||||
}
|
||||
let videoConfigString = await fs.promises.readFile(define.video_config, 'utf-8');
|
||||
if (!ValidateJson(videoConfigString)) {
|
||||
throw new Error("视频配置文件格式错误,请检查");
|
||||
}
|
||||
|
||||
// 检查图片是不是出完了
|
||||
let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
|
||||
bookTaskId: bookTask.id
|
||||
});
|
||||
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||
const element = bookTaskDetails[i];
|
||||
if (isEmpty(element.outImagePath) || !await CheckFileOrDirExist(element.outImagePath)) {
|
||||
throw new Error("存在没有出完的图片或者图片不存在,请检查");
|
||||
}
|
||||
}
|
||||
// 检查分镜信息和出入的图片数量是不是一致
|
||||
let outImages = await GetFilesWithExtensions(bookTask.imageFolder, ['.png']);
|
||||
if (outImages.length != bookTaskDetails.length) {
|
||||
throw new Error("分镜信息和出图文件夹中图片数量不一致,请检查");
|
||||
}
|
||||
|
||||
let videoConfig = JSON.parse(videoConfigString);
|
||||
let assRandomIndex = Math.floor(Math.random() * videoConfig.assConfig.length)
|
||||
|
||||
let outMp4 = path.join(
|
||||
book.bookFolderPath,
|
||||
bookTask.name + '.mp4'
|
||||
)
|
||||
// 将所有的数据写入到配置文件中
|
||||
let obj = {
|
||||
srt_path: bookTask.srtPath,
|
||||
// 字幕样式(需要随机)
|
||||
srt_style: isEmpty(bookTask.draftSrtStyle) ? videoConfig.assConfig[assRandomIndex] : videoConfig.assConfig[assRandomIndex],
|
||||
audio_path: bookTask.audioPath,
|
||||
background_music_folder: await this.bookSetting.GetBackgroundMusicFolder(bookTask.backgroundMusic),
|
||||
// 水印设置(需要随机)
|
||||
friendly_reminder: await this.bookSetting.GetWatermarkSetting(),
|
||||
video_resolution_x: videoConfig.video_resolution_x,
|
||||
video_resolution_y: videoConfig.video_resolution_y,
|
||||
outpue_file: outMp4,
|
||||
image_folder: bookTask.imageFolder,
|
||||
srt_config: path.join(book.bookFolderPath, `scripts/${bookTask.name}_config.json`),
|
||||
mp4_file_txt: path.join(book.bookFolderPath, `scripts/${bookTask.name}.txt`),
|
||||
status: 'no',
|
||||
audio_sound_size: videoConfig.audioSoundSize,
|
||||
background_music_sound_size: videoConfig.backgroundMusicSoundSize,
|
||||
keyFrame: videoConfig.keyframe,
|
||||
frameRate: videoConfig.frameRate,
|
||||
bitRate: videoConfig.bitRate,
|
||||
stdout: '',
|
||||
stderr: ""
|
||||
}
|
||||
|
||||
// 写出配置文件
|
||||
let configPath = path.join(book.bookFolderPath, `scripts/${bookTask.name}_video_config.json`);
|
||||
await fs.promises.writeFile(configPath, JSON.stringify(obj), 'utf-8');
|
||||
|
||||
let scriptPath = path.join(define.scripts_path, 'Lai.exe')
|
||||
let gpu = global.gpu.type
|
||||
if (videoConfig.libx264) {
|
||||
gpu = 'OTHER'
|
||||
}
|
||||
// 执行生成图片的脚本
|
||||
let script = `cd "${define.scripts_path}" && "${scriptPath}" -c "${configPath.replaceAll("\\", '/')}" "${gpu}"`
|
||||
const output = await execAsync(script, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' })
|
||||
|
||||
if (output.stderr != '') {
|
||||
obj.status = 'video_error'
|
||||
obj.stdout = output.stdout
|
||||
obj.stderr = output.stderr
|
||||
throw new Error(output.stderr)
|
||||
} else {
|
||||
obj.status = 'video_ok'
|
||||
obj.stdout = output.stdout
|
||||
obj.stderr = output.stderr
|
||||
}
|
||||
|
||||
// 检查输出文件是不是存在
|
||||
if (!CheckFileOrDirExist(outMp4)) {
|
||||
throw new Error("生成的视频文件不存在,请检查")
|
||||
}
|
||||
|
||||
// 修改数据
|
||||
await this.bookServiceBasic.UpdetedBookTaskData(task.bookTaskId, {
|
||||
status: BookTaskStatus.COMPOSING_DONE,
|
||||
generateVideoPath: path.relative(define.project_path, outMp4)
|
||||
})
|
||||
await this.bookServiceBasic.UpdateTaskStatus({
|
||||
id: task.id,
|
||||
status: BookBackTaskStatus.DONE
|
||||
})
|
||||
SendMessageToRenderer({
|
||||
code: 1,
|
||||
id: task.bookTaskDetailId,
|
||||
data: BookTaskStatus.COMPOSING_DONE
|
||||
}, task.messageName);
|
||||
return successMessage(null, "合成视频任务执行成功", "BookTask_GenerateVideo");
|
||||
} catch (error) {
|
||||
let message = '合成视频任务执行失败,错误信息如下:' + error.toString()
|
||||
// 失败
|
||||
await this.bookServiceBasic.UpdetedBookTaskData(task.bookTaskId, {
|
||||
status: BookTaskStatus.COMPOSING_FAIL,
|
||||
errorMsg: error.message
|
||||
})
|
||||
await this.bookServiceBasic.UpdateTaskStatus({
|
||||
id: task.id,
|
||||
status: BookBackTaskStatus.FAIL,
|
||||
errorMessage: message
|
||||
})
|
||||
SendMessageToRenderer({
|
||||
code: 1,
|
||||
id: task.bookTaskDetailId,
|
||||
data: BookTaskStatus.COMPOSING_FAIL,
|
||||
message: message
|
||||
}, task.messageName);
|
||||
return errorMessage(message, "BookTask_GenerateVideo");
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
@ -588,7 +588,6 @@ export class MJOpt {
|
||||
} else {
|
||||
if (task_res.progress == 100) {
|
||||
task_res.type == MJRespoonseType.FINISHED;
|
||||
console.log(task.id, "22222")
|
||||
await this.bookServiceBasic.UpdateTaskStatus({
|
||||
id: task.id,
|
||||
status: BookBackTaskStatus.DONE
|
||||
@ -597,7 +596,6 @@ export class MJOpt {
|
||||
let imagePath = path.join(book.bookFolderPath, `data\\MJOriginalImage\\${task_res.messageId}.png`);
|
||||
await CheckFolderExistsOrCreate(path.dirname(imagePath))
|
||||
await this.tools.downloadFileUrl(task_res.imageClick, imagePath)
|
||||
debugger
|
||||
// 进行图片裁剪
|
||||
let imageRes = await ImageSplit(imagePath, bookTaskDetail.name, path.join(book.bookFolderPath, 'data\\MJOriginalImage'));
|
||||
if (imageRes && imageRes.length < 4) {
|
||||
@ -606,7 +604,7 @@ export class MJOpt {
|
||||
|
||||
// 修改数据库数据,将图片保存到对应的文件夹中
|
||||
let firstImage = imageRes[0];
|
||||
if (book.type == BookType.ORIGINAL) {
|
||||
if (book.type == BookType.ORIGINAL && bookTask.name == "output_00001") {
|
||||
await CopyFileOrFolder(firstImage, path.join(book.bookFolderPath, `tmp\\input\\${bookTaskDetail.name}.png`));
|
||||
}
|
||||
let out_file = path.join(bookTask.imageFolder, `${bookTaskDetail.name}.png`)
|
||||
|
||||
@ -4,15 +4,20 @@ import { checkStringValueAddSuffix, errorMessage, successMessage } from "../../P
|
||||
import { define } from '../../../define/define'
|
||||
import fs from "fs";
|
||||
import { ImageStyle } from "../Book/imageStyle";
|
||||
import { BookBackTaskStatus, BookType, OperateBookType } from "../../../define/enum/bookEnum";
|
||||
import { BookBackTaskStatus, BookTaskStatus, BookType, OperateBookType } from "../../../define/enum/bookEnum";
|
||||
import { isEmpty } from "lodash";
|
||||
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
|
||||
import { PresetService } from "../presetService";
|
||||
import path from "path";
|
||||
import axios from "axios";
|
||||
import { CheckFolderExistsOrCreate, CopyFileOrFolder, DeleteFileExifData } from "../../../define/Tools/file";
|
||||
import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder, DeleteFileExifData } from "../../../define/Tools/file";
|
||||
import { Base64ToFile } from "../../../define/Tools/image";
|
||||
import { MJAction, MJImageType } from "../../../define/enum/mjEnum";
|
||||
import { DEFINE_STRING } from "../../../define/define_string";
|
||||
import { OtherData, ResponseMessageType } from "../../../define/enum/softwareEnum";
|
||||
import util from 'util';
|
||||
import { exec } from 'child_process';
|
||||
const execAsync = util.promisify(exec)
|
||||
const fspromise = fs.promises
|
||||
|
||||
export class SDOpt {
|
||||
@ -35,6 +40,14 @@ export class SDOpt {
|
||||
return sdSetting
|
||||
}
|
||||
|
||||
private sendChangeMessage(data: GeneralResponse.MessageResponse, message_name: string = DEFINE_STRING.BOOK.MAIN_DATA_RETURN) {
|
||||
if (!message_name) {
|
||||
message_name = DEFINE_STRING.BOOK.MAIN_DATA_RETURN
|
||||
}
|
||||
global.newWindow[0].win.webContents.send(message_name, data)
|
||||
}
|
||||
|
||||
|
||||
//#region 合并提示词
|
||||
|
||||
/**
|
||||
@ -300,7 +313,7 @@ export class SDOpt {
|
||||
// 这边去图片信息
|
||||
await DeleteFileExifData(path.join(define.package_path, 'exittool/exiftool.exe'), infoImgPath, imgPath);
|
||||
// 写出去
|
||||
if (bookTask.name == 'output_00001') {
|
||||
if (bookTask.name == 'output_00001' && book.type == BookType.ORIGINAL) {
|
||||
// 复制一个到input
|
||||
let inputImgPath = path.join(inputFolder, `${bookTaskDetail.name}.png`)
|
||||
await CopyFileOrFolder(imgPath, inputImgPath)
|
||||
@ -359,6 +372,16 @@ export class SDOpt {
|
||||
id: task.bookTaskDetailId
|
||||
})
|
||||
|
||||
await this.bookServiceBasic.UpdateBookTaskDetail(task.bookTaskDetailId, {
|
||||
status: BookTaskStatus.IMAGE_FAIL,
|
||||
})
|
||||
|
||||
await this.bookServiceBasic.UpdateTaskStatus({
|
||||
id: task.id,
|
||||
status: BookBackTaskStatus.FAIL,
|
||||
errorMessage: errorMsg
|
||||
})
|
||||
|
||||
global.newWindow[0].win.webContents.send(task.messageName, {
|
||||
code: 0,
|
||||
message: errorMsg,
|
||||
@ -371,4 +394,126 @@ export class SDOpt {
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 反推
|
||||
|
||||
/**
|
||||
* SD 执行单个反推任务
|
||||
* @param task 对应的任务
|
||||
* @returns
|
||||
*/
|
||||
async SDReversePrompt(task: TaskModal.Task): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
||||
try {
|
||||
console.log(task)
|
||||
if (task.bookTaskDetailId == OtherData.DEFAULT) { // 反推整个任务的提示词
|
||||
let bookTask = await this.bookServiceBasic.GetBookTaskDataById(task.bookTaskId);
|
||||
let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId);
|
||||
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({
|
||||
bookTaskId: task.bookTaskId
|
||||
});
|
||||
let oldImages = []
|
||||
for (let i = 0; i < bookTaskDetail.length; i++) {
|
||||
const element = bookTaskDetail[i];
|
||||
oldImages.push({
|
||||
id: element.id,
|
||||
name: element.name,
|
||||
imagePath: element.oldImage
|
||||
})
|
||||
}
|
||||
// 判断是不是有为空的
|
||||
let imagePaths = [] as string[]
|
||||
for (let i = 0; i < oldImages.length; i++) {
|
||||
const element = oldImages[i];
|
||||
if (!isEmpty(element.imagePath)) {
|
||||
imagePaths.push(element.imagePath)
|
||||
}
|
||||
}
|
||||
|
||||
if (imagePaths.length == 0) {
|
||||
throw new Error("没有找到需要反推的图片,请先生成图片")
|
||||
}
|
||||
|
||||
let inputTxt = path.join(book.bookFolderPath, 'tmp/input/input.txt')
|
||||
await fspromise.writeFile(inputTxt, imagePaths.join('\n'), 'utf-8')
|
||||
let exe = path.join(define.scripts_path, 'Lai.exe')
|
||||
let scripts = `cd "${define.scripts_path}" && ${exe} -pt "${inputTxt}"`;
|
||||
|
||||
// 完成了反推
|
||||
// 开始写出数据
|
||||
const output = await execAsync(scripts, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' })
|
||||
|
||||
for (let i = 0; i < oldImages.length; i++) {
|
||||
const element = oldImages[i];
|
||||
let tagTxt = path.join(path.dirname(element.imagePath), `${element.name}.txt`)
|
||||
if (!await CheckFileOrDirExist(tagTxt)) {
|
||||
throw new Error("反推失败,没有找到反推出来的提示词文件,请检查")
|
||||
}
|
||||
let content = await fspromise.readFile(tagTxt, 'utf-8');
|
||||
content = content.replace(/\r?\n/g, '');
|
||||
// 更新数据库
|
||||
await this.bookServiceBasic.UpdateBookTaskDetail(element.id, {
|
||||
gptPrompt: content
|
||||
});
|
||||
// 主动返回任务状态
|
||||
this.sendChangeMessage({
|
||||
code: 1,
|
||||
type: ResponseMessageType.SD_REVERSE,
|
||||
id: element.id,
|
||||
data: content
|
||||
}, task.messageName);
|
||||
}
|
||||
} else { // 反推单个分镜的提示词
|
||||
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId);
|
||||
let imagePath = bookTaskDetail.oldImage;
|
||||
if (isEmpty(imagePath)) {
|
||||
throw new Error("当前任务没有生成图片,请先生成图片")
|
||||
}
|
||||
if (!await CheckFileOrDirExist(imagePath)) {
|
||||
throw new Error("当前任务生成图片不存在,请先生成图片")
|
||||
}
|
||||
|
||||
// 开始执行exe进行反推
|
||||
let exe = path.join(define.scripts_path, 'Lai.exe')
|
||||
let scripts = `cd "${define.scripts_path}" && ${exe} -ps "${imagePath}"`;
|
||||
|
||||
const output = await execAsync(scripts, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' })
|
||||
|
||||
// 反推成功
|
||||
let imageName = path.basename(imagePath, path.extname(imagePath));
|
||||
let txt = path.join(path.dirname(imagePath), `${imageName}.txt`)
|
||||
if (!await CheckFileOrDirExist(txt)) {
|
||||
throw new Error("反推失败,没有找到反推出来的提示词文件,请检查")
|
||||
}
|
||||
let content = await fspromise.readFile(txt, 'utf-8');
|
||||
content = content.replace(/\r?\n/g, '');
|
||||
|
||||
// 更新数据库
|
||||
await this.bookServiceBasic.UpdateBookTaskDetail(task.bookTaskDetailId, {
|
||||
gptPrompt: content
|
||||
});
|
||||
|
||||
await this.bookServiceBasic.UpdateTaskStatus({
|
||||
id: task.id,
|
||||
status: BookBackTaskStatus.DONE
|
||||
})
|
||||
// 主动返回任务状态
|
||||
this.sendChangeMessage({
|
||||
code: 1,
|
||||
type: ResponseMessageType.SD_REVERSE,
|
||||
id: task.bookTaskDetailId,
|
||||
data: content
|
||||
}, task.messageName);
|
||||
}
|
||||
} catch (error) {
|
||||
let errorMsg = "SD反推失败,错误信息如下:" + error.toString()
|
||||
await this.bookServiceBasic.UpdateTaskStatus({
|
||||
id: task.id,
|
||||
status: BookBackTaskStatus.FAIL,
|
||||
errorMessage: errorMsg
|
||||
})
|
||||
return errorMessage(errorMsg, "SDOpt_SDReversePrompt");
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
@ -32,7 +32,7 @@ class BookServiceBasic {
|
||||
}
|
||||
|
||||
//#region 事务操作
|
||||
transaction = async (realm : any) => await this.bookBasic.transaction(realm);
|
||||
transaction = async (realm: any) => await this.bookBasic.transaction(realm);
|
||||
|
||||
// (callback: (realm: any) => void) {
|
||||
// this.bookService.transaction(() => {
|
||||
@ -64,16 +64,16 @@ class BookServiceBasic {
|
||||
|
||||
//#region 分镜任务
|
||||
|
||||
|
||||
AddBookTaskDetail = async (bookTaskDetail: Book.SelectBookTaskDetail) => await this.bookTaskDetailServiceBasic.AddBookTaskDetail(bookTaskDetail);
|
||||
GetBookTaskDetailDataById = async (bookTaskDetailId: string) => await this.bookTaskDetailServiceBasic.GetBookTaskDetailDataById(bookTaskDetailId);
|
||||
GetBookTaskDetailData = async (condition: Book.QueryBookTaskDetailCondition, returnEmpty: boolean = false) => await this.bookTaskDetailServiceBasic.GetBookTaskDetailData(condition, returnEmpty);
|
||||
UpdateBookTaskDetail = async (bookTaskDetailId: string, data: Book.SelectBookTaskDetail) => await this.bookTaskDetailServiceBasic.UpdateBookTaskDetail(bookTaskDetailId, data);
|
||||
UpdateBookTaskStatus = async (bookTaskDetailId: string, status: BookTaskStatus,errorMsg? : string) => await this.bookTaskDetailServiceBasic.UpdateBookTaskStatus(bookTaskDetailId, status,errorMsg);
|
||||
DeleteBookTaskDetailReversePromptById = async (bookTaskDetailId: string, reversePromptId: string) => await this.bookTaskDetailServiceBasic.DeleteBookTaskDetailReversePromptById(bookTaskDetailId);
|
||||
UpdateBookTaskStatus = async (bookTaskDetailId: string, status: BookTaskStatus, errorMsg?: string) => await this.bookTaskDetailServiceBasic.UpdateBookTaskStatus(bookTaskDetailId, status, errorMsg);
|
||||
DeleteBookTaskDetailReversePromptById = async (bookTaskDetailId: string) => await this.bookTaskDetailServiceBasic.DeleteBookTaskDetailReversePromptById(bookTaskDetailId);
|
||||
DeleteBoookTaskDetailGenerateImage = async (bookTaskDetailId: string) => await this.bookTaskDetailServiceBasic.DeleteBoookTaskDetailGenerateImage(bookTaskDetailId);
|
||||
UpdateBookTaskDetailReversePrompt = async (bookTaskDetailId: string, reversePromptId: string, data: Book.ReversePrompt) => await this.bookTaskDetailServiceBasic.UpdateBookTaskDetailReversePrompt(bookTaskDetailId, reversePromptId, data);
|
||||
UpdateBookTaskDetailMjMessage = async (bookTaskDetailId: string, mjMessage: Book.MJMessage) => await this.bookTaskDetailServiceBasic.UpdateBookTaskDetailMjMessage(bookTaskDetailId, mjMessage);
|
||||
DeleteBookTaskDetailData = async (condition: Book.DeleteBookTaskDetailCondition) => await this.bookTaskDetailServiceBasic.DeleteBookTaskDetail(condition);
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
@ -89,6 +89,16 @@ export default class BookTaskDetailServiceBasic {
|
||||
this.bookTaskDetailService.DeleteBookTaskDetailReversePromptById(bookTaskDetailId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定的小说分镜数据
|
||||
* @param condition
|
||||
*/
|
||||
async DeleteBookTaskDetail(condition: Book.QueryBookTaskDetailCondition): Promise<void> {
|
||||
await this.InitService();
|
||||
this.bookTaskDetailService.DeleteBookTaskDetail(condition)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除指定的小说分镜生成的图片
|
||||
* @param bookTaskDetailId
|
||||
|
||||
@ -354,7 +354,6 @@ export class SubtitleService {
|
||||
bookTaskId: bookTaskId
|
||||
}, true)
|
||||
|
||||
debugger
|
||||
// 获取SD设置
|
||||
let sdConifg = JSON.parse(await fs.promises.readFile(define.sd_setting, 'utf-8'));
|
||||
let adetailer = false;
|
||||
|
||||
14
src/main/Service/globalService.ts
Normal file
14
src/main/Service/globalService.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { DEFINE_STRING } from "@/define/define_string"
|
||||
import { GeneralResponse } from "@/model/generalResponse"
|
||||
|
||||
/**
|
||||
* 主动返回前端的消息
|
||||
* @param data 返回的数据
|
||||
* @param message_name 消息名称
|
||||
*/
|
||||
export function SendMessageToRenderer(data: GeneralResponse.MessageResponse, message_name: string = DEFINE_STRING.BOOK.MAIN_DATA_RETURN) {
|
||||
if (!message_name) {
|
||||
message_name = DEFINE_STRING.BOOK.MAIN_DATA_RETURN
|
||||
}
|
||||
global.newWindow[0].win.webContents.send(message_name, data)
|
||||
}
|
||||
@ -36,9 +36,8 @@ class JianyingService {
|
||||
* 获取剪映草稿的关键帧和文本
|
||||
* @param draftDir 草稿目录
|
||||
* @param projectDir 项目目录
|
||||
* @param packagePath 包路径
|
||||
*/
|
||||
public async GetDraftFrameAndText(draftDir: string, projectDir: string, packagePath: string) {
|
||||
public async GetDraftFrameAndText(draftDir: string, projectDir: string, inputName: string = "input_crop"): Promise<void> {
|
||||
try {
|
||||
// 获取草稿文件路径
|
||||
let draftJsonPath = path.resolve(draftDir, "draft_content.json");
|
||||
@ -59,7 +58,7 @@ class JianyingService {
|
||||
await DeleteFolderAllFile(projectTmp);
|
||||
}
|
||||
// 创建输出文件夹
|
||||
let projectInput = path.resolve(projectTmp, "input_crop");
|
||||
let projectInput = path.resolve(projectTmp, inputName);
|
||||
console.log("projectInput", projectInput);
|
||||
|
||||
// 获取剪映的轨道,并且判断是否包含一个video轨道和一个text轨道
|
||||
@ -98,6 +97,8 @@ class JianyingService {
|
||||
console.log("GetDraftFrameAndText", jsonPath);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} finally {
|
||||
this.draftTimeLine = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ import { FluxOpt } from '../Flux/flux'
|
||||
import { AsyncQueue } from '../../quene'
|
||||
import { SoftWareServiceBasic } from '../ServiceBasic/softwareServiceBasic'
|
||||
import { MJSetting } from '../../../model/Setting/mjSetting'
|
||||
import { BookVideo } from '../Book/bookVideo'
|
||||
|
||||
export class TaskManager {
|
||||
isExecuting: boolean = false;
|
||||
@ -24,6 +25,7 @@ export class TaskManager {
|
||||
bookBackTaskListService!: BookBackTaskListService;
|
||||
eventListeners: Record<string | number, Function[]> = {};
|
||||
softWareServiceBasic: SoftWareServiceBasic
|
||||
bookVideo: BookVideo
|
||||
|
||||
mjSetting: MJSetting.MjSetting
|
||||
spaceTime: number = 5000;
|
||||
@ -252,6 +254,15 @@ export class TaskManager {
|
||||
}, `${batch}_${task.id}`, batch, `${batch}_${task.id}_${new Date().getTime()}`)
|
||||
}
|
||||
|
||||
/** 添加生成视频的后台任务 */
|
||||
async AddGenerateVideo(task: TaskModal.Task) {
|
||||
let batch = task.messageName;
|
||||
global.requestQuene.enqueue(async () => {
|
||||
this.bookVideo = new BookVideo();
|
||||
await this.bookVideo.GenerateVideo(task);
|
||||
}, `${batch}_${task.id}`, batch, `${batch}_${task.id}_${new Date().getTime()}`)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加 flux forge 任务到内存队列中
|
||||
@ -299,7 +310,8 @@ export class TaskManager {
|
||||
case BookBackTaskType.RECOGNIZE:
|
||||
this.AddExtractSubtitlesData(task);
|
||||
break;
|
||||
case BookBackTaskType.MJ_REVERSE || BookBackTaskType.SD_REVERSE:
|
||||
case BookBackTaskType.MJ_REVERSE:
|
||||
case BookBackTaskType.SD_REVERSE:
|
||||
this.AddSingleReversePrompt(task);
|
||||
break;
|
||||
case BookBackTaskType.FLUX_FORGE_IMAGE:
|
||||
@ -317,6 +329,10 @@ export class TaskManager {
|
||||
case BookBackTaskType.D3_IMAGE:
|
||||
this.AddD3Image(task);
|
||||
break;
|
||||
|
||||
case BookBackTaskType.COMPOSING: // 合成视频
|
||||
this.AddGenerateVideo(task);
|
||||
break;
|
||||
default:
|
||||
throw new Error('未知的任务类型');
|
||||
}
|
||||
|
||||
@ -506,7 +506,7 @@ async function getFrame(value) {
|
||||
let package_path = define.package_path.replaceAll('\\', '/')
|
||||
|
||||
let jianying = new JianyingService()
|
||||
await jianying.GetDraftFrameAndText(draft_path, out_dir, package_path)
|
||||
await jianying.GetDraftFrameAndText(draft_path, out_dir)
|
||||
|
||||
global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, {
|
||||
code: 1,
|
||||
@ -539,7 +539,7 @@ async function PushBackPrompt() {
|
||||
let script = `cd "${define.scripts_path}" && "${py_path}" -p "${sd_config_path.replaceAll(
|
||||
'\\',
|
||||
'/'
|
||||
)}" "input" "${global.config.project_path}"`
|
||||
)}" "input_crop" "${global.config.project_path}"`
|
||||
const output = await execAsync(script, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' })
|
||||
global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, {
|
||||
code: 1,
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
|
||||
export class BookSetting {
|
||||
constructor() {
|
||||
}
|
||||
}
|
||||
57
src/main/setting/bookSetting.ts
Normal file
57
src/main/setting/bookSetting.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { define } from "@/define/define";
|
||||
import { CheckFileOrDirExist } from "@/define/Tools/file";
|
||||
import { ValidateJson } from "@/define/Tools/validate";
|
||||
import fs from 'fs';
|
||||
|
||||
class BookSetting {
|
||||
constructor() { }
|
||||
|
||||
/**
|
||||
* 通过ID获取设置的背景音乐文件夹
|
||||
* @param id 背景音乐文件夹ID
|
||||
*/
|
||||
async GetBackgroundMusicFolder(id: string): Promise<string> {
|
||||
let clipSettingString = await fs.promises.readFile(define.clip_setting, 'utf-8');
|
||||
if (!ValidateJson(clipSettingString)) {
|
||||
throw new Error('Clip设置文件格式错误');
|
||||
}
|
||||
let clipSetting = JSON.parse(clipSettingString);
|
||||
let backgroundMusicSetting = clipSetting.background_music_setting;
|
||||
if (!backgroundMusicSetting) {
|
||||
throw new Error('没有找到对应的背景音乐文件夹地址设置,请检查');
|
||||
}
|
||||
let clip = backgroundMusicSetting.find((clip: any) => clip.id === id);
|
||||
if (clip == undefined) {
|
||||
throw new Error('没有找到对应的背景音乐文件夹地址,请检查');
|
||||
}
|
||||
if (!await CheckFileOrDirExist(clip.folder_path)) {
|
||||
throw new Error('背景音乐文件夹不存在,请检查');
|
||||
}
|
||||
return clip.folder_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定的水印设置,没有的话,随机返回一个
|
||||
* @param id 水印设置ID
|
||||
*/
|
||||
async GetWatermarkSetting(id?: string) {
|
||||
let videoSettingString = await fs.promises.readFile(define.video_config, 'utf-8');
|
||||
if (!ValidateJson(videoSettingString)) {
|
||||
throw new Error('Video设置文件格式错误');
|
||||
}
|
||||
let videoSetting = JSON.parse(videoSettingString);
|
||||
let watermarkConfig = videoSetting.watermarkConfig;
|
||||
if (!watermarkConfig) {
|
||||
throw new Error('没有找到对应的水印设置,请检查');
|
||||
}
|
||||
if (id == undefined) {
|
||||
return watermarkConfig[Math.floor(Math.random() * watermarkConfig.length)];
|
||||
}
|
||||
let watermark = watermarkConfig.find((watermark: any) => watermark.id === id);
|
||||
if (watermark == undefined) {
|
||||
throw new Error('没有找到对应的水印设置,请检查');
|
||||
}
|
||||
return watermark;
|
||||
}
|
||||
}
|
||||
export default BookSetting;
|
||||
7
src/model/book.d.ts
vendored
7
src/model/book.d.ts
vendored
@ -272,6 +272,13 @@ declare namespace Book {
|
||||
duration: number
|
||||
}
|
||||
|
||||
type DeleteBookTaskDetailCondition = {
|
||||
id?: string;
|
||||
bookId?: string;
|
||||
bookTaskId?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
//#region 替换小说数据
|
||||
|
||||
type BookReplaceData = {
|
||||
|
||||
@ -54,7 +54,7 @@ const book = {
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 小说通用操作
|
||||
//#region bookTaskDetail通用操作
|
||||
|
||||
// 替换文案数据
|
||||
ReplaceBookData: async (bookTaskId: string, replaceData: Book.BookReplaceData) =>
|
||||
@ -82,6 +82,9 @@ const book = {
|
||||
bookTaskDetailId,
|
||||
currentTime
|
||||
),
|
||||
|
||||
/** 剪映抽帧,并判断是不是切割视频 */
|
||||
JianyingFrame: async (bookTaskId: string, draftName: string) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.JIANYING_FRAME, bookTaskId, draftName),
|
||||
//#endregion
|
||||
|
||||
//#region 文案相关信息
|
||||
@ -160,6 +163,10 @@ const book = {
|
||||
|
||||
// 导入提示词
|
||||
ImportGPTPrompt: async (bookTaskId: string, txtPath: string) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.IMPORT_GPT_PROMPT, bookTaskId, txtPath),
|
||||
|
||||
/** 移除不想要的提示词 */
|
||||
RemoveBadPrompt: async (bookTaskId: string) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.REMOVE_BAD_PROMPT, bookTaskId),
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 图片相关
|
||||
@ -227,12 +234,12 @@ const book = {
|
||||
await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_NEW_BOOK_TASK, addBookTaskData),
|
||||
|
||||
// 重置小说批次数据
|
||||
ReSetBookTask: async (bookTaskId) =>
|
||||
await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_BOOK_TASK, bookTaskId),
|
||||
ReSetBookTask: async (bookTaskId: string | string[], operateBookType: OperateBookType) =>
|
||||
await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_BOOK_TASK, bookTaskId, operateBookType),
|
||||
|
||||
// 删除小说批次数据
|
||||
DeleteBookTask: async (bookTaskId) =>
|
||||
await ipcRenderer.invoke(DEFINE_STRING.BOOK.DELETE_BOOK_TASK, bookTaskId),
|
||||
DeleteBookTask: async (bookTaskId: string | string[], operateBookType: OperateBookType) =>
|
||||
await ipcRenderer.invoke(DEFINE_STRING.BOOK.DELETE_BOOK_TASK, bookTaskId, operateBookType),
|
||||
|
||||
// 一键生成所有图片
|
||||
GenerateImageAll: async (bookTaskId, imageCategory) =>
|
||||
@ -261,8 +268,11 @@ const book = {
|
||||
|
||||
// 添加数据到剪映草稿
|
||||
AddJianyingDraft: async (id: string | string[], operateBookType: OperateBookType) =>
|
||||
await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_JIANYING_DRAFT, id, operateBookType)
|
||||
await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_JIANYING_DRAFT, id, operateBookType),
|
||||
|
||||
/** 合成视频相关 */
|
||||
AddGenerateVideoTask: async (id: string | string[], operateBookType: OperateBookType) =>
|
||||
await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_GENERATE_VIDEO_TASK, id, operateBookType)
|
||||
//#endregion
|
||||
}
|
||||
export { book }
|
||||
|
||||
@ -1,300 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-dropdown trigger="hover" :options="reverseOptions" @select="ReverseSelect">
|
||||
<n-button size="tiny" :color="softwareStore.SoftColor.BROWN_YELLOW" @click="AddReversePrompt">
|
||||
单句反推
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
<n-dropdown trigger="hover" :options="tranlateOptions" @select="ReverseSelect">
|
||||
<n-button
|
||||
style="margin-left: 5px"
|
||||
size="tiny"
|
||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||
@click="TranslateOne('translate_chinese')"
|
||||
>
|
||||
英文 2 中文
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
<n-button
|
||||
style="margin-left: 5px"
|
||||
size="tiny"
|
||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||
@click="SelectReversePrompt"
|
||||
>
|
||||
重选反推提示词
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
|
||||
import { useMessage, useDialog, NDropdown, NButton } from 'naive-ui'
|
||||
import { useSoftwareStore } from '../../../../../stores/software'
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage'
|
||||
import { TranslateType } from '../../../../../define/enum/translate'
|
||||
import { ContainsChineseOrPunctuation } from '../../../../../define/Tools/common'
|
||||
import ReSelectReversePrompt from '../MJReverse/ReSelectReversePrompt.vue'
|
||||
import { OperateBookType } from '../../../../../define/enum/bookEnum'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { DEFINE_STRING } from '../../../../../define/define_string'
|
||||
|
||||
export default defineComponent({
|
||||
components: { NDropdown, NButton },
|
||||
props: ['initData', 'index'],
|
||||
setup(props) {
|
||||
let message = useMessage()
|
||||
let dialog = useDialog()
|
||||
let data = ref(props.initData)
|
||||
let index = ref(props.index)
|
||||
let softwareStore = useSoftwareStore()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
onMounted(async () => {})
|
||||
|
||||
/**
|
||||
* 单句反推提示词
|
||||
*/
|
||||
async function AddReversePrompt() {
|
||||
let res = await window.book.AddReversePrompt(
|
||||
data.value.id,
|
||||
OperateBookType.BOOKTASKDETAIL,
|
||||
null
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
} else {
|
||||
message.success('添加反推任务成功')
|
||||
}
|
||||
}
|
||||
|
||||
async function AddNextReversePrompt() {
|
||||
let res = await window.book.AddReversePrompt(
|
||||
data.value.id,
|
||||
OperateBookType.UNDERBOOKTASK,
|
||||
reverseManageStore.selectBook.type
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
} else {
|
||||
message.success('添加反推任务成功')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提示词菜单下拉操作
|
||||
*/
|
||||
async function ReverseSelect(key) {
|
||||
switch (key) {
|
||||
case 'remove_reverse': // 删除反推提示词
|
||||
let res = await window.book.ResetGptReverseData(
|
||||
data.value.id,
|
||||
OperateBookType.BOOKTASKDETAIL,
|
||||
reverseManageStore.selectBook.type
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
} else {
|
||||
reverseManageStore.selectBookTaskDetail[props.index].gptPrompt = undefined
|
||||
reverseManageStore.selectBookTaskDetail[props.index].reversePrompt = undefined
|
||||
message.success('删除反推数据成功')
|
||||
}
|
||||
break
|
||||
case 'translate_english': // 中文 2 英文
|
||||
await TranslateOne('translate_english')
|
||||
break
|
||||
|
||||
case 'down_translate_chinese': // 下翻译中文
|
||||
await TranslateOne('down_translate_chinese')
|
||||
break
|
||||
case 'down_translate_english': // 下翻译英文
|
||||
await TranslateOne('down_translate_english')
|
||||
break
|
||||
case 'down_reverse':
|
||||
await AddNextReversePrompt()
|
||||
break
|
||||
default:
|
||||
message.error('未知的操作类型')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 处理翻译英文的数据
|
||||
function TranslateOneParams(from, to) {
|
||||
let translateData = []
|
||||
if (!isEmpty(data.value.gptPrompt)) {
|
||||
translateData.push({
|
||||
text: data.value.gptPrompt,
|
||||
from: from,
|
||||
to: to,
|
||||
type: TranslateType.GPT_PROMPT_TRANSLATE,
|
||||
isSplit: false, // 该参数设置为true时,会将文本按照标点符号分割成多个句子进行翻译
|
||||
bookTaskDetailId: data.value.id,
|
||||
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
|
||||
})
|
||||
} else {
|
||||
let reversePrompt = data.value.reversePrompt
|
||||
if (reversePrompt) {
|
||||
reversePrompt.forEach((item) => {
|
||||
let temp_obj = undefined
|
||||
if (from == 'zh' && to == 'en') {
|
||||
// 判断是不是有中文
|
||||
if (!ContainsChineseOrPunctuation(item.promptCN ? item.promptCN : item.prompt)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
temp_obj = {
|
||||
text: item.prompt,
|
||||
from: from,
|
||||
to: to,
|
||||
type: TranslateType.REVERSE_PROMPT_TRANSLATE,
|
||||
isSplit: false, // 该参数设置为true时,会将文本按照标点符号分割成多个句子进行翻译
|
||||
bookTaskDetailId: data.value.id,
|
||||
reversePromptId: item.id,
|
||||
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
|
||||
}
|
||||
|
||||
translateData.push(temp_obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
return translateData
|
||||
}
|
||||
|
||||
// 处理批次的翻译
|
||||
function TranslateBatchParams(from, to) {
|
||||
let translateData = []
|
||||
for (let i = index.value; i < reverseManageStore.selectBookTaskDetail.length; i++) {
|
||||
const element = reverseManageStore.selectBookTaskDetail[i]
|
||||
// 这边判断GPT提示词是不是存在,存在的话翻译GPT提示词
|
||||
if (!isEmpty(element.gptPrompt)) {
|
||||
translateData.push({
|
||||
text: element.gptPrompt,
|
||||
from: from,
|
||||
to: to,
|
||||
type: TranslateType.GPT_PROMPT_TRANSLATE,
|
||||
isSplit: false, // 该参数设置为true时,会将文本按照标点符号分割成多个句子进行翻译
|
||||
bookTaskDetailId: element.id,
|
||||
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
|
||||
})
|
||||
} else {
|
||||
let reversePrompt = element.reversePrompt
|
||||
if (reversePrompt) {
|
||||
reversePrompt.forEach((item) => {
|
||||
if (from == 'zh' && to == 'en') {
|
||||
// 判断是不是有中文
|
||||
if (!ContainsChineseOrPunctuation(item.promptCN ? item.promptCN : item.prompt)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let temp_obj = {
|
||||
text: item.prompt,
|
||||
from: from,
|
||||
to: to,
|
||||
type: TranslateType.REVERSE_PROMPT_TRANSLATE,
|
||||
isSplit: false, // 该参数设置为true时,会将文本按照标点符号分割成多个句子进行翻译
|
||||
bookTaskDetailId: element.id,
|
||||
reversePromptId: item.id,
|
||||
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
|
||||
}
|
||||
translateData.push(temp_obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return translateData
|
||||
}
|
||||
|
||||
// 单句翻译
|
||||
async function TranslateOne(key) {
|
||||
let translateData = []
|
||||
switch (key) {
|
||||
case 'translate_english':
|
||||
translateData = TranslateOneParams('zh', 'en')
|
||||
break
|
||||
case 'translate_chinese':
|
||||
translateData = TranslateOneParams('en', 'zh')
|
||||
break
|
||||
case 'down_translate_english':
|
||||
translateData = TranslateBatchParams('zh', 'en')
|
||||
break
|
||||
case 'down_translate_chinese':
|
||||
translateData = TranslateBatchParams('en', 'zh')
|
||||
break
|
||||
default:
|
||||
message.error('未知的操作类型')
|
||||
break
|
||||
}
|
||||
if (translateData.length <= 0) {
|
||||
message.error('没有需要翻译的数据')
|
||||
return
|
||||
}
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = `翻译中 ... 0 / ${translateData.length}`
|
||||
// 开始翻译
|
||||
let res = await window.translate.TranslateNowReturn(translateData)
|
||||
softwareStore.spin.spinning = false
|
||||
window.api.showGlobalMessageDialog(res)
|
||||
}
|
||||
|
||||
/**
|
||||
* 重选反推提示词
|
||||
*/
|
||||
async function SelectReversePrompt() {
|
||||
// 判断当前是不是又反推提示词
|
||||
if (!data.value.reversePrompt || data.value.reversePrompt.length <= 0) {
|
||||
message.error('当前没有反推提示词')
|
||||
return
|
||||
}
|
||||
|
||||
// ImportWordAndSrt
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
title: `重选MJ反推提示词 ${data.value.name}`,
|
||||
closeOnEsc: false,
|
||||
content: () =>
|
||||
h(ReSelectReversePrompt, {
|
||||
reversePrompts: data.value.reversePrompt,
|
||||
index: index.value
|
||||
}),
|
||||
style: `width : 750px;`,
|
||||
maskClosable: false
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
index,
|
||||
data,
|
||||
softwareStore,
|
||||
ReverseSelect,
|
||||
TranslateOne,
|
||||
reverseManageStore,
|
||||
SelectReversePrompt,
|
||||
AddReversePrompt,
|
||||
reverseOptions: [
|
||||
{
|
||||
label: '下推理',
|
||||
key: 'down_reverse'
|
||||
},
|
||||
{
|
||||
label: '删除反推提示词',
|
||||
key: 'remove_reverse'
|
||||
}
|
||||
],
|
||||
tranlateOptions: [
|
||||
{
|
||||
label: '中文 2 英文',
|
||||
key: 'translate_english'
|
||||
},
|
||||
{
|
||||
label: '下翻译英文',
|
||||
key: 'down_translate_english'
|
||||
},
|
||||
{
|
||||
label: '下翻译中文',
|
||||
key: 'down_translate_chinese'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@ -1,165 +0,0 @@
|
||||
<template>
|
||||
<div style="display: flex; justify-content: space-between; white-space: nowrap">
|
||||
<div style="display: flex">
|
||||
<div>反推/GPT提示词</div>
|
||||
<n-button
|
||||
v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE"
|
||||
type="text"
|
||||
:color="softwareStore.SoftColor.ZHUYANTUO"
|
||||
size="tiny"
|
||||
style="margin-left: 10px"
|
||||
@click="SelectPromptIndex"
|
||||
>选择提示词</n-button
|
||||
><n-button
|
||||
v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE"
|
||||
type="text"
|
||||
:color="softwareStore.SoftColor.ZHUYANTUO"
|
||||
size="tiny"
|
||||
style="margin-left: 5px"
|
||||
@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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, h, watch } from 'vue'
|
||||
import { useMessage, useDialog, NButton, NPopover, NIcon } from 'naive-ui'
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage'
|
||||
import { BookRepalceDataType, BookType } from '../../../../../define/enum/bookEnum'
|
||||
import { useSoftwareStore } from '../../../../../stores/software'
|
||||
import SelectMJReversePrompt from '../MJReverse/SelectMJReversePrompt.vue'
|
||||
import SelectImageStyle from '../../Components/SelectImageStyle.vue'
|
||||
import FindReplaceRound from '../../Icon/FindReplaceRound.vue'
|
||||
import DatatableHeaderAfterGptSelectAndReplace from './DatatableHeaderAfterGptSelectAndReplace.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: { NButton, NPopover, FindReplaceRound, NIcon },
|
||||
|
||||
setup() {
|
||||
let message = useMessage()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let softwareStore = useSoftwareStore()
|
||||
let bookType = ref(BookType)
|
||||
let dialog = useDialog()
|
||||
let selectStyleRef = ref(null)
|
||||
let da = undefined
|
||||
|
||||
/**
|
||||
* 选择MJ反推提示词。用于生图
|
||||
*/
|
||||
async function SelectPromptIndex() {
|
||||
// 判断是不是MJ反推,判断是不是所有的都有提示词
|
||||
if (reverseManageStore.selectBook.type != BookType.MJ_REVERSE) {
|
||||
message.error('只有MJ反推可以使用该按钮')
|
||||
return
|
||||
}
|
||||
let hasReversePrompt = false
|
||||
// 判断是不是所有的都有提示词,只要有一个有,就可以
|
||||
for (let index = 0; index < reverseManageStore.selectBookTaskDetail.length; index++) {
|
||||
const element = reverseManageStore.selectBookTaskDetail[index]
|
||||
if (element.reversePrompt && element.reversePrompt.length > 0) {
|
||||
hasReversePrompt = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!hasReversePrompt) {
|
||||
message.error('请先进行MJ反推')
|
||||
return
|
||||
}
|
||||
dialog.create({
|
||||
title: '选择反推提示词',
|
||||
content: () => h(SelectMJReversePrompt),
|
||||
style: 'width : 500px',
|
||||
showIcon: false
|
||||
})
|
||||
}
|
||||
|
||||
// 选择风格
|
||||
async function SelectStyle() {
|
||||
// 先获取风格
|
||||
// 判断当前数据是不是存在
|
||||
// 处理数据。获取当前的所有的数据
|
||||
let dialogWidth = window.innerWidth * 0.8
|
||||
let dialogHeight = window.innerHeight * 0.9
|
||||
// ImportWordAndSrt
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
closeOnEsc: false,
|
||||
content: () =>
|
||||
h(SelectImageStyle, {
|
||||
height: dialogHeight,
|
||||
ref: selectStyleRef,
|
||||
imageStyle: reverseManageStore.selectBookTask.imageStyle,
|
||||
customizeImageStyle: reverseManageStore.selectBookTask.customizeImageStyle
|
||||
}),
|
||||
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
|
||||
maskClosable: false,
|
||||
onClose: async () => {
|
||||
|
||||
console.log(selectStyleRef)
|
||||
// 将数据保存到当前的数据中
|
||||
// reverseManageStore.selectBookTask.styleList = toRaw(selectStyleRef.value.selectStyle)
|
||||
|
||||
// 保存当前的风格到数据库
|
||||
let res = await window.book.SaveImageStyle(
|
||||
toRaw(selectStyleRef.value.selectStyle),
|
||||
reverseManageStore.selectBookTask.id
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
reverseManageStore.selectBookTask.imageStyle = res.data.imageStyle
|
||||
reverseManageStore.selectBookTask.customizeImageStyle = res.data.customizeImageStyle
|
||||
message.success('保存成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
reverseManageStore,
|
||||
bookType,
|
||||
softwareStore,
|
||||
SelectPromptIndex,
|
||||
SelectStyle,
|
||||
selectStyleRef,
|
||||
SelectReversePromptAndReplace
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@ -1,215 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-dropdown trigger="hover" :options="mergeOptions" @select="MergePrompt">
|
||||
<n-button
|
||||
size="tiny"
|
||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||
@click="MergePrompt(undefined)"
|
||||
>
|
||||
单句合并
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
|
||||
<n-button
|
||||
style="margin-left: 5px"
|
||||
size="tiny"
|
||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||
@click="SingleGenerateImage"
|
||||
>
|
||||
单句生图
|
||||
</n-button>
|
||||
|
||||
<n-button
|
||||
style="margin-left: 5px"
|
||||
size="tiny"
|
||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||
@click="UnderGenerateImage"
|
||||
>
|
||||
下生图
|
||||
</n-button>
|
||||
<n-button
|
||||
style="margin-left: 5px"
|
||||
size="tiny"
|
||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||
@click="ImportMJImageUrl"
|
||||
>
|
||||
导入图片
|
||||
</n-button>
|
||||
</div>
|
||||
<n-input
|
||||
type="textarea"
|
||||
size="tiny"
|
||||
@input="InputDebounced"
|
||||
v-model:value="data.prompt"
|
||||
:autosize="{ minRows: 6, maxRows: 6 }"
|
||||
></n-input>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
|
||||
import { useMessage, useDialog, NInput, NButton, NDropdown } from 'naive-ui'
|
||||
import { useSoftwareStore } from '../../../../../stores/software'
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage'
|
||||
import { OperateBookType } from '../../../../../define/enum/bookEnum'
|
||||
import { BookImageCategory } from '../../../../../define/enum/bookEnum'
|
||||
import { debounce } from 'lodash'
|
||||
import InputDialogContent from '../../Original/Components/InputDialogContent.vue'
|
||||
import { DEFINE_STRING } from '../../../../../define/define_string'
|
||||
|
||||
export default defineComponent({
|
||||
components: { NInput, NButton, NDropdown },
|
||||
props: ['initData', 'index'],
|
||||
setup(props) {
|
||||
let message = useMessage()
|
||||
let softwareStore = useSoftwareStore()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let data = ref(props.initData)
|
||||
let image_url_ref = ref(null)
|
||||
let dialog = useDialog()
|
||||
onMounted(async () => {})
|
||||
|
||||
// 合并提示词
|
||||
async function MergePrompt(key) {
|
||||
let type = key
|
||||
if (!key) {
|
||||
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
|
||||
type = 'mj_merge'
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
|
||||
type = 'sd_merge'
|
||||
} else {
|
||||
type = 'mj_merge'
|
||||
}
|
||||
}
|
||||
// 开始进行合并,调用不同的合并模式
|
||||
// 合并之前,要确认保存
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '合并命令中。。。'
|
||||
let res = await window.book.MergePrompt(data.value.id, type, OperateBookType.BOOKTASKDETAIL)
|
||||
softwareStore.spin.spinning = false
|
||||
// 这边对返回的数据进行处理
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
const element = res.data[i]
|
||||
let selectBookDetailIndex = reverseManageStore.selectBookTaskDetail.findIndex(
|
||||
(item) => item.id == element.id
|
||||
)
|
||||
reverseManageStore.selectBookTaskDetail[selectBookDetailIndex].prompt = element.prompt
|
||||
}
|
||||
message.success(res.message)
|
||||
}
|
||||
|
||||
// 单个出图
|
||||
async function SingleGenerateImage() {
|
||||
let res = undefined
|
||||
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
|
||||
res = await window.mj.AddMJGenerateImageTask(
|
||||
data.value.id,
|
||||
OperateBookType.BOOKTASKDETAIL,
|
||||
DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN
|
||||
)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
|
||||
// res = await window.sd.SingleGenerateImage(data.value.id, OperateBookType.BOOKTASKDETAIL)
|
||||
message.error('暂不支该出图模式')
|
||||
} else {
|
||||
message.error('未知的出图模式')
|
||||
return
|
||||
}
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
message.success(res.message)
|
||||
}
|
||||
|
||||
// 下生图
|
||||
async function UnderGenerateImage() {
|
||||
let res = undefined
|
||||
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
|
||||
res = await window.mj.AddMJGenerateImageTask(
|
||||
data.value.id,
|
||||
OperateBookType.UNDERBOOKTASK,
|
||||
DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN
|
||||
)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
|
||||
// res = await window.sd.SingleGenerateImage(data.value.id, OperateBookType.UNDERBOOKTASK)
|
||||
message.error('暂不支该出图模式')
|
||||
} else {
|
||||
message.error('未知的出图模式')
|
||||
return
|
||||
}
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
message.success(res.message)
|
||||
}
|
||||
|
||||
let InputDebounced = debounce(handleInput, 1000)
|
||||
async function handleInput() {
|
||||
let res = await window.db.UpdateBookTaskDetailData(data.value.id, {
|
||||
prompt: data.value.prompt
|
||||
})
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入图片的链接,将图片链接下载,并且分割
|
||||
*/
|
||||
async function ImportMJImageUrl() {
|
||||
// 判断当前数据是不是存在
|
||||
// 处理数据。获取当前的所有的数据
|
||||
let dialogWidth = 400
|
||||
let dialogHeight = 150
|
||||
let da = dialog.create({
|
||||
title: '添加图片链接/本地地址',
|
||||
showIcon: false,
|
||||
closeOnEsc: false,
|
||||
content: () =>
|
||||
h(InputDialogContent, {
|
||||
ref: image_url_ref,
|
||||
initData: null,
|
||||
placeholder: '请输入图片链接/本地地址'
|
||||
}),
|
||||
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
|
||||
maskClosable: false,
|
||||
onClose: async () => {
|
||||
da?.destroy()
|
||||
let row_image_url = image_url_ref.value.data
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '正在下载图片/分割图片中。。。'
|
||||
let res = await window.book.DownloadImageUrlAndSplit(data.value.id, row_image_url)
|
||||
softwareStore.spin.spinning = false
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
data.value.outImagePath = res.data.outImagePath
|
||||
data.value.subImagePath = res.data.subImagePath
|
||||
message.success(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
softwareStore,
|
||||
reverseManageStore,
|
||||
InputDebounced,
|
||||
SingleGenerateImage,
|
||||
MergePrompt,
|
||||
data,
|
||||
image_url_ref,
|
||||
ImportMJImageUrl,
|
||||
UnderGenerateImage,
|
||||
mergeOptions: [
|
||||
{ label: 'MJ模式合并', key: 'mj_merge' },
|
||||
{ label: 'SD模式合并', key: 'sd_merge' }
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@ -33,7 +33,7 @@
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, onMounted, defineComponent, h } from 'vue'
|
||||
import { useMessage, NButton, useDialog } from 'naive-ui'
|
||||
import { useReverseManageStore } from '../../../../../../stores/reverseManage.ts'
|
||||
@ -41,27 +41,24 @@ import { useSoftwareStore } from '../../../../../../stores/software.ts'
|
||||
import { BookType, OperateBookType } from '../../../../../../define/enum/bookEnum.ts'
|
||||
import ManageBookTaskGenerateInformation from './ManageBookTaskGenerateInformation.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NButton
|
||||
},
|
||||
let props = defineProps({
|
||||
bookTask: undefined
|
||||
})
|
||||
|
||||
props: ['bookTask'],
|
||||
setup(props) {
|
||||
let message = useMessage()
|
||||
let dialog = useDialog()
|
||||
let bookTask = ref(props.bookTask)
|
||||
console.log('bookTask', bookTask.value)
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let softwareStore = useSoftwareStore()
|
||||
let message = useMessage()
|
||||
let dialog = useDialog()
|
||||
let bookTask = ref(props.bookTask)
|
||||
console.log('bookTask', bookTask.value)
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let softwareStore = useSoftwareStore()
|
||||
|
||||
onMounted(async () => {})
|
||||
onMounted(async () => {})
|
||||
|
||||
/**
|
||||
/**
|
||||
* 重置小说数据
|
||||
* @param e
|
||||
*/
|
||||
async function ReSetBookTaskDetail(e) {
|
||||
async function ReSetBookTaskDetail(e) {
|
||||
e.stopPropagation()
|
||||
dialog.warning({
|
||||
title: '重置小说任务',
|
||||
@ -78,12 +75,12 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 删除小说任务
|
||||
*/
|
||||
async function DeleteBookTask(e) {
|
||||
async function DeleteBookTask(e) {
|
||||
e.stopPropagation()
|
||||
dialog.warning({
|
||||
title: '删除小说任务',
|
||||
@ -110,9 +107,9 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function hdImageFunc(id, operateBookType) {
|
||||
async function hdImageFunc(id, operateBookType) {
|
||||
let da = dialog.warning({
|
||||
title: '高清提示',
|
||||
content: `是否确认高清,当前的高清倍数为 ${softwareStore.globalSetting.hdScale} 倍,是否继续?`,
|
||||
@ -135,12 +132,12 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 高清图片
|
||||
*/
|
||||
async function HDImage(e) {
|
||||
async function HDImage(e) {
|
||||
e.stopPropagation()
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '正在进行高清检查。。。'
|
||||
@ -175,12 +172,12 @@ export default defineComponent({
|
||||
// 没有就直接高清
|
||||
await hdImageFunc(bookTask.value.id, OperateBookType.BOOKTASK)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 生成草稿
|
||||
*/
|
||||
async function ClipDraft(e) {
|
||||
async function ClipDraft(e) {
|
||||
e.stopPropagation()
|
||||
|
||||
dialog.info({
|
||||
@ -188,28 +185,29 @@ export default defineComponent({
|
||||
title: `生成草稿前检查 ${bookTask.value.name}`,
|
||||
maskClosable: false,
|
||||
content: () =>
|
||||
h(ManageBookTaskGenerateInformation, { bookTask: bookTask.value, type: 'bookTask' })
|
||||
h(ManageBookTaskGenerateInformation, {
|
||||
bookTask: bookTask.value,
|
||||
type: 'bookTask',
|
||||
optionType: 'draft'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 合成视频
|
||||
*/
|
||||
async function GenerateVideo(e) {
|
||||
async function GenerateVideo(e) {
|
||||
e.stopPropagation()
|
||||
message.error('该功能暂不可用')
|
||||
}
|
||||
|
||||
return {
|
||||
bookTask,
|
||||
reverseManageStore,
|
||||
ClipDraft,
|
||||
GenerateVideo,
|
||||
softwareStore,
|
||||
ReSetBookTaskDetail,
|
||||
DeleteBookTask,
|
||||
HDImage
|
||||
}
|
||||
}
|
||||
})
|
||||
dialog.info({
|
||||
closeOnEsc: false,
|
||||
title: `合成视频前检查 ${bookTask.value.name}`,
|
||||
maskClosable: false,
|
||||
content: () =>
|
||||
h(ManageBookTaskGenerateInformation, {
|
||||
bookTask: bookTask.value,
|
||||
type: 'bookTask',
|
||||
optionType: 'video'
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="选择剪映草稿" path="backgroundMusic">
|
||||
<n-form-item label="选择剪映草稿" path="backgroundMusic" v-if="optionType != 'video'">
|
||||
<div>
|
||||
<div style="color: red">
|
||||
注意:选择的草稿主轨道的图片数量要和当前的相同,会生成新的草稿并且只会替换图片,保留其余的数据
|
||||
@ -67,12 +67,23 @@
|
||||
>
|
||||
<n-button type="info" style="margin-left: 10px" @click="SaveVideoData">保存数据</n-button>
|
||||
</n-form-item>
|
||||
<div v-if="type == 'bookTask'" style="color: red">注意:在生成草稿前要先保存数据</div>
|
||||
<div v-if="type == 'bookTask'" style="color: red">
|
||||
注意:在 生成草稿/合成视频 前要先保存数据
|
||||
</div>
|
||||
<div v-else style="color: red">
|
||||
注意:在生成草稿前要先保存数据,当前会生成选择的草稿,全部会使用上面的参数
|
||||
注意:在 生成草稿/合成视频 前要先保存数据,当前会生成选择的 草稿/视频,全部会使用上面的参数
|
||||
</div>
|
||||
<n-form-item style="display: flex; justify-content: flex-end">
|
||||
<n-button type="info" style="margin-left: 10px" @click="AddJianyingDraft">生成草稿</n-button>
|
||||
<n-button
|
||||
v-if="optionType == 'video'"
|
||||
type="info"
|
||||
style="margin-left: 10px"
|
||||
@click="AddGenerateVideoTask"
|
||||
>添加合成视频任务</n-button
|
||||
>
|
||||
<n-button v-else type="info" style="margin-left: 10px" @click="AddJianyingDraft"
|
||||
>生成草稿</n-button
|
||||
>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</template>
|
||||
@ -81,18 +92,21 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useMessage, NForm, NFormItem, NButton, NIcon, NInput, NSelect } from 'naive-ui'
|
||||
import { FolderOpen } from '@vicons/ionicons5'
|
||||
import { OperateBookType } from '../../../../../../define/enum/bookEnum'
|
||||
import { BookTaskStatus, OperateBookType } from '../../../../../../define/enum/bookEnum'
|
||||
import { useReverseManageStore } from '../../../../../../stores/reverseManage'
|
||||
|
||||
let props = defineProps({
|
||||
bookTask: undefined,
|
||||
type: undefined,
|
||||
selectBookTask: []
|
||||
selectBookTask: [],
|
||||
optionType: undefined
|
||||
})
|
||||
|
||||
let bookTask = ref(props.bookTask)
|
||||
let type = ref(props.type)
|
||||
|
||||
let optionType = ref(props.optionType)
|
||||
|
||||
let backgroundMusicOptions = ref([])
|
||||
let message = useMessage()
|
||||
let draftSelect = ref([])
|
||||
@ -217,23 +231,78 @@ async function SaveVideoData() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加草稿
|
||||
*/
|
||||
async function AddJianyingDraft() {
|
||||
async function AddGenerateVideoTask() {
|
||||
let res = undefined
|
||||
if (props.type == 'book') {
|
||||
let res = await window.book.AddJianyingDraft(
|
||||
// 单个
|
||||
res = await window.book.AddGenerateVideoTask(
|
||||
props.selectBookTask,
|
||||
OperateBookType.ASSIGNBOOKTASK
|
||||
)
|
||||
window.api.showGlobalMessageDialog(res)
|
||||
if (res.code == 1) {
|
||||
reverseManageStore.UpdatedBookTaskData(props.selectBookTask, {
|
||||
status: BookTaskStatus.COMPOSING
|
||||
})
|
||||
} else {
|
||||
reverseManageStore.UpdatedBookTaskData(props.selectBookTask, {
|
||||
status: BookTaskStatus.COMPOSING_FAIL,
|
||||
errorMsg: res.message
|
||||
})
|
||||
}
|
||||
} else if (props.type == 'bookTask') {
|
||||
let res = await window.book.AddJianyingDraft(bookTask.value.id, OperateBookType.BOOKTASK)
|
||||
window.api.showGlobalMessageDialog(res)
|
||||
// 批次
|
||||
res = await window.book.AddGenerateVideoTask(bookTask.value.id, OperateBookType.BOOKTASK)
|
||||
if (res.code == 1) {
|
||||
reverseManageStore.UpdatedBookTaskData(bookTask.value.id, {
|
||||
status: BookTaskStatus.COMPOSING
|
||||
})
|
||||
} else {
|
||||
reverseManageStore.UpdatedBookTaskData(bookTask.value.id, {
|
||||
status: BookTaskStatus.COMPOSING_FAIL,
|
||||
errorMsg: res.message
|
||||
})
|
||||
}
|
||||
} else {
|
||||
message.error('未知的操作类型,请检查')
|
||||
return
|
||||
}
|
||||
window.api.showGlobalMessageDialog(res)
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加草稿
|
||||
*/
|
||||
async function AddJianyingDraft() {
|
||||
let res = undefined
|
||||
if (props.type == 'book') {
|
||||
res = await window.book.AddJianyingDraft(props.selectBookTask, OperateBookType.ASSIGNBOOKTASK)
|
||||
if (res.code == 1) {
|
||||
reverseManageStore.UpdatedBookTaskData(props.selectBookTask, {
|
||||
status: BookTaskStatus.DRAFT_DONE
|
||||
})
|
||||
} else {
|
||||
reverseManageStore.UpdatedBookTaskData(props.selectBookTask, {
|
||||
status: BookTaskStatus.DRAFT_FAIL,
|
||||
errorMsg: res.message
|
||||
})
|
||||
}
|
||||
} else if (props.type == 'bookTask') {
|
||||
res = await window.book.AddJianyingDraft(bookTask.value.id, OperateBookType.BOOKTASK)
|
||||
if (res.code == 1) {
|
||||
reverseManageStore.UpdatedBookTaskData(bookTask.value.id, {
|
||||
status: BookTaskStatus.DRAFT_DONE
|
||||
})
|
||||
} else {
|
||||
reverseManageStore.UpdatedBookTaskData(bookTask.value.id, {
|
||||
status: BookTaskStatus.DRAFT_FAIL,
|
||||
errorMsg: res.message
|
||||
})
|
||||
}
|
||||
} else {
|
||||
message.error('未知的操作类型,请检查')
|
||||
return
|
||||
}
|
||||
window.api.showGlobalMessageDialog(res)
|
||||
}
|
||||
let rules = ref({
|
||||
srtPath: [{ required: true, message: '请选择背景音乐', trigger: ['input', 'blur', 'change'] }],
|
||||
|
||||
@ -1,960 +0,0 @@
|
||||
<template>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<div style="display: flex; align-items: center">
|
||||
<n-button strong secondary @click="RetuenMain" size="small"> 返回 </n-button>
|
||||
<!-- <n-dropdown trigger="hover" :options="AutoOptions" @select="SelectAutoAction">
|
||||
<n-button :color="softwareStore.SoftColor.ORANGE" @click="Start" style="margin-left: 5px">
|
||||
自动开始
|
||||
</n-button>
|
||||
</n-dropdown> -->
|
||||
<n-divider vertical style="height: 30px" />
|
||||
<n-dropdown trigger="hover" :options="frameOptions" @select="handleSelect">
|
||||
<n-button
|
||||
:color="softwareStore.SoftColor.ORANGE"
|
||||
class="top-button"
|
||||
size="small"
|
||||
@click="ActionFrame"
|
||||
>
|
||||
开始分镜
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
<n-dropdown trigger="hover" :options="copywritingOptions" @select="handleSelect">
|
||||
<n-button
|
||||
:color="softwareStore.SoftColor.ORANGE"
|
||||
style="margin-left: 5px"
|
||||
size="small"
|
||||
@click="GetCopywriting('all')"
|
||||
>
|
||||
开始提取全部文案
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
|
||||
<n-dropdown trigger="hover" :options="watermarkOptions" @select="handleSelect">
|
||||
<n-button
|
||||
:color="softwareStore.SoftColor.ORANGE"
|
||||
style="margin-left: 5px"
|
||||
size="small"
|
||||
@click="RemoveWatermark"
|
||||
>
|
||||
开始去除水印
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
<n-dropdown trigger="hover" :options="importWordOptions" @select="handleSelect">
|
||||
<n-button
|
||||
:color="softwareStore.SoftColor.ORANGE"
|
||||
style="margin-left: 5px"
|
||||
size="small"
|
||||
@click="ImportWordAndSrtFunc"
|
||||
>
|
||||
导入文案/SRT
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
|
||||
<n-dropdown trigger="hover" :options="reverseOptions" @select="handleSelect">
|
||||
<n-button
|
||||
:color="softwareStore.SoftColor.ORANGE"
|
||||
style="margin-left: 5px"
|
||||
size="small"
|
||||
@click="ImageReversePrompt(undefined)"
|
||||
>
|
||||
一键反推
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
|
||||
<n-dropdown trigger="hover" :options="translateOptions" @select="handleSelect">
|
||||
<n-button
|
||||
:color="softwareStore.SoftColor.ORANGE"
|
||||
style="margin-left: 5px"
|
||||
size="small"
|
||||
@click="Translate(undefined)"
|
||||
>
|
||||
一键翻译
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
|
||||
<n-dropdown trigger="hover" :options="generateOptions" @select="handleSelect">
|
||||
<n-button
|
||||
:color="softwareStore.SoftColor.ORANGE"
|
||||
style="margin-left: 5px"
|
||||
size="small"
|
||||
@click="GenerateImageAll"
|
||||
>
|
||||
一键生图
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
|
||||
<n-dropdown trigger="hover" :options="resetOptions" @select="handleSelect">
|
||||
<n-button
|
||||
:color="softwareStore.SoftColor.ORANGE"
|
||||
style="margin-left: 5px"
|
||||
size="small"
|
||||
@click="ResetAllData"
|
||||
>
|
||||
重置
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
|
||||
<n-button
|
||||
:color="softwareStore.SoftColor.ORANGE"
|
||||
style="margin-left: 5px"
|
||||
size="small"
|
||||
@click="OpenSettingDialog"
|
||||
>
|
||||
设置
|
||||
</n-button>
|
||||
<div style="margin-left: 10px">
|
||||
<MonitorStatus />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-button">
|
||||
<n-button
|
||||
text
|
||||
strong
|
||||
:type="softwareStore.show_logger ? 'default' : 'info'"
|
||||
@click="switchLogger"
|
||||
>
|
||||
{{ softwareStore.show_logger ? '隐藏日志' : '显示日志' }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
|
||||
import { useMessage, NButton, useDialog, NCheckbox, NDropdown, NDivider, NRate } from 'naive-ui'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useSoftwareStore } from '../../../../../stores/software'
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage.ts'
|
||||
import { isEmpty } from 'lodash'
|
||||
import VideoCanvas from '../../VideoSubtitle/VideoCanvas.vue'
|
||||
import { SubtitleSavePositionType } from '../../../../../define/enum/waterMarkAndSubtitle'
|
||||
import { DEFINE_STRING } from '../../../../../define/define_string'
|
||||
import Setting from './Setting.vue'
|
||||
import { BookType, OperateBookType } from '../../../../../define/enum/bookEnum'
|
||||
import TranslateSetting from '../../Setting/TranslateSetting.vue'
|
||||
import { TranslateType } from '../../../../../define/enum/translate'
|
||||
import { ContainsChineseOrPunctuation } from '../../../../../define/Tools/common'
|
||||
import ImportWordAndSrt from '../../Original/Components/ImportWordAndSrt.vue'
|
||||
import GetWaterMaskRectangle from '../../Watermark/GetWaterMaskRectangle.vue'
|
||||
import MonitorStatus from './MonitorStatus.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NButton,
|
||||
NCheckbox,
|
||||
NDropdown,
|
||||
NDivider,
|
||||
GetWaterMaskRectangle,
|
||||
MonitorStatus
|
||||
},
|
||||
|
||||
setup() {
|
||||
let router = useRouter()
|
||||
let message = useMessage()
|
||||
let dialog = useDialog()
|
||||
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let softwareStore = useSoftwareStore()
|
||||
// 获取当前这个界面的数据
|
||||
onMounted(async () => {})
|
||||
|
||||
// 返回主页
|
||||
function RetuenMain() {
|
||||
router.push('/book_management')
|
||||
}
|
||||
|
||||
// 自动开始
|
||||
async function AutoAction() {
|
||||
// 开始自动执行。先分镜,后面在添加任务中
|
||||
let res_frame = await window.book.AutoAction(reverseManageStore.selectBook.id)
|
||||
}
|
||||
|
||||
// 选择自动开始的操作(停止或者是继续)
|
||||
async function SelectAutoAction() {}
|
||||
|
||||
// 是不是显示日志
|
||||
async function switchLogger() {
|
||||
softwareStore.show_logger = !softwareStore.show_logger
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始分镜
|
||||
*/
|
||||
async function ComputeStoryboard() {
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '正在计算分镜中'
|
||||
if (isEmpty(reverseManageStore.selectBookTask.id)) {
|
||||
window.api.showGlobalMessageDialog({
|
||||
code: 0,
|
||||
message: '没有找到要执行的任务'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let res_frame = await window.book.ComputeStoryboard(reverseManageStore.selectBook.id)
|
||||
if (res_frame.code == 0) {
|
||||
softwareStore.spin.spinning = false
|
||||
window.api.showGlobalMessageDialog(res_frame)
|
||||
return
|
||||
}
|
||||
|
||||
// 分镜完成,重新获取一下数据
|
||||
let getRes = await reverseManageStore.GetBookTaskDataFromDB({
|
||||
id: reverseManageStore.selectBookTask.id
|
||||
})
|
||||
|
||||
if (getRes.code == 0) {
|
||||
softwareStore.spin.spinning = false
|
||||
message.error(getRes.message)
|
||||
return
|
||||
}
|
||||
softwareStore.spin.spinning = false
|
||||
message.success('重新获取数据成功')
|
||||
}
|
||||
|
||||
// 执行分镜的方法
|
||||
async function Framing() {
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '正在分镜中'
|
||||
if (isEmpty(reverseManageStore.selectBookTask.id)) {
|
||||
window.api.showGlobalMessageDialog({
|
||||
code: 0,
|
||||
message: '没有找到要执行的任务'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let res_frame = await window.book.Framing(reverseManageStore.selectBook.id)
|
||||
if (res_frame.code == 0) {
|
||||
softwareStore.spin.spinning = false
|
||||
window.api.showGlobalMessageDialog(res_frame)
|
||||
return
|
||||
}
|
||||
|
||||
// 分镜完成,重新获取一下数据
|
||||
let getRes = await reverseManageStore.GetBookTaskDataFromDB({
|
||||
id: reverseManageStore.selectBookTask.id
|
||||
})
|
||||
|
||||
if (getRes.code == 0) {
|
||||
softwareStore.spin.spinning = false
|
||||
message.error(getRes.message)
|
||||
return
|
||||
}
|
||||
softwareStore.spin.spinning = false
|
||||
message.success('重新获取数据成功')
|
||||
}
|
||||
|
||||
// 获取文案信息
|
||||
async function GetCopywriting(type) {
|
||||
if (isEmpty(reverseManageStore.selectBookTask.id)) {
|
||||
window.api.showGlobalMessageDialog({
|
||||
code: 0,
|
||||
message: '没有找到要执行的任务'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let subtitleSettingRes = await window.write.GetSubtitleSetting()
|
||||
if (subtitleSettingRes.code == 0) {
|
||||
message.error(subtitleSettingRes.message)
|
||||
return
|
||||
}
|
||||
|
||||
let da = dialog.warning({
|
||||
title: '开始提取文案提示',
|
||||
content: `即将进行 ${type == 'blank' ? '空白' : '全部'} 文案提取${
|
||||
type == 'blank' ? '' : ',会重新提取并覆盖旧的文案数据'
|
||||
},当前的文案提取模式为 ${subtitleSettingRes.data.selectModel} ,是否继续?`,
|
||||
positiveText: '继续',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
da?.destroy()
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = `正在使用 ${subtitleSettingRes.data.selectModel} 提取文案,正在准备中。。。`
|
||||
let copywriting_res = await window.book.GetCopywriting(
|
||||
reverseManageStore.selectBook.id,
|
||||
reverseManageStore.selectBookTask.id,
|
||||
OperateBookType.BOOKTASK,
|
||||
type == 'blank' ? false : true
|
||||
)
|
||||
softwareStore.spin.spinning = false
|
||||
if (copywriting_res.code == 0) {
|
||||
message.error(copywriting_res.message)
|
||||
return
|
||||
}
|
||||
// dialog.success({})
|
||||
message.success('获取全部文案成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 开始去除水印
|
||||
async function RemoveWatermark() {
|
||||
if (isEmpty(reverseManageStore.selectBookTask.id)) {
|
||||
window.api.showGlobalMessageDialog({
|
||||
code: 0,
|
||||
message: '没有找到要执行的任务'
|
||||
})
|
||||
return
|
||||
}
|
||||
let da = dialog.warning({
|
||||
title: '去除水印提示',
|
||||
content: '即将去除全部水印,是否继续?',
|
||||
positiveText: '继续',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
da?.destroy()
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '正在去除水印中。。。'
|
||||
let res_frame = await window.book.RemoveWatermark(
|
||||
reverseManageStore.selectBookTask.id,
|
||||
OperateBookType.BOOKTASK
|
||||
)
|
||||
softwareStore.spin.spinning = false
|
||||
window.api.showGlobalMessageDialog(res_frame)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取文案信息设置
|
||||
async function GetCopywritingSetting() {
|
||||
let dialogHeight = window.innerHeight * 0.9
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
closeOnEsc: false,
|
||||
content: () =>
|
||||
h(VideoCanvas, {
|
||||
videoWidth: 800,
|
||||
videoSrc: reverseManageStore.selectBook.oldVideoPath,
|
||||
height: dialogHeight - 50,
|
||||
type: SubtitleSavePositionType.SETTING
|
||||
}),
|
||||
style: `width : 850px; height : ${dialogHeight}px`,
|
||||
maskClosable: false,
|
||||
onClose: () => {}
|
||||
})
|
||||
}
|
||||
|
||||
// 导出文案
|
||||
async function ExportCopywriting() {
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '正在导出文案中...'
|
||||
let res = await window.book.ExportCopywriting(reverseManageStore.selectBookTask.id)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
} else {
|
||||
dialog.success({
|
||||
title: '导出文案成功',
|
||||
content: '导出文案成功,是否打开导出的字幕文件',
|
||||
positiveText: '打开',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
window.system.OpenFile(res.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取水印位置
|
||||
*/
|
||||
async function GetWatermarkPosition() {
|
||||
// 开始获取水印位置
|
||||
// 判断当前数据是不是存在
|
||||
// 处理数据。获取当前的所有的数据
|
||||
let dialogWidth = window.innerWidth * 0.7
|
||||
let dialogHeight = window.innerHeight * 0.9
|
||||
// ImportWordAndSrt
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
closeOnEsc: false,
|
||||
content: () => h(GetWaterMaskRectangle, { width: dialogWidth, height: dialogHeight }),
|
||||
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
|
||||
maskClosable: false,
|
||||
onClose: () => {}
|
||||
})
|
||||
}
|
||||
|
||||
// 一些选择方式
|
||||
async function handleSelect(key) {
|
||||
switch (key) {
|
||||
case 'compute_frame': // 计算分镜
|
||||
await ComputeStoryboard()
|
||||
break
|
||||
case 'framing': // 开始分镜
|
||||
await Framing()
|
||||
break
|
||||
case 'recognizing_blank': // 提取空白文案
|
||||
await GetCopywriting('blank')
|
||||
break
|
||||
case 'recognizing_setting': // 文案位置设置
|
||||
await GetCopywritingSetting()
|
||||
break
|
||||
case 'export_recognizing': // 导出文案
|
||||
await ExportCopywriting()
|
||||
break
|
||||
case 'import_recognizing': // 导入文案
|
||||
await ImportCopywriting()
|
||||
break
|
||||
case 'watermark_position': // 水印位置设置
|
||||
await GetWatermarkPosition()
|
||||
break
|
||||
case 'clear_import_word': // 清除导入文案
|
||||
await ClearImportWord()
|
||||
break
|
||||
case 'mj_reverse': // MJ反推
|
||||
await ImageReversePrompt(BookType.MJ_REVERSE)
|
||||
break
|
||||
case 'sd_reverse': // SD反推
|
||||
await ImageReversePrompt(BookType.SD_REVERSE)
|
||||
break
|
||||
case 'translate_chinese': // 英文翻译为中文
|
||||
await Translate('translate_chinese')
|
||||
break
|
||||
case 'translate_english': // 中文翻译为英文
|
||||
await Translate('translate_english')
|
||||
break
|
||||
|
||||
case 'translate_setting': // 翻译设置
|
||||
await Translate('translate_setting')
|
||||
break
|
||||
case 'reset_gpt_reverse_data': // 重置反推/GPT提示词
|
||||
await ResetGptReverseData()
|
||||
break
|
||||
case 'reset_merge_prompt': // 重置合并提示词数据
|
||||
await ResetMergePromptData()
|
||||
break
|
||||
case 'reset_Generate_Image': // 重置生成的图片
|
||||
await ResetGenerateImage()
|
||||
break
|
||||
default:
|
||||
message.error('未知的选项')
|
||||
}
|
||||
}
|
||||
|
||||
// 重置出的图片
|
||||
async function ResetGenerateImage() {
|
||||
dialog.warning({
|
||||
title: '重置所有图片提示',
|
||||
content:
|
||||
'继续操作会重置所有的生成的图片,包括生成的主图和子图,但是并不会删除本地的图片文件,是否继续?',
|
||||
positiveText: '继续',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
let res = await window.book.ResetGenerateImage(
|
||||
reverseManageStore.selectBookTask.id,
|
||||
OperateBookType.BOOKTASK
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
} else {
|
||||
// 前端重置数据
|
||||
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
|
||||
reverseManageStore.selectBookTaskDetail[i].mjMessage = undefined
|
||||
reverseManageStore.selectBookTaskDetail[i].outImagePath = undefined
|
||||
reverseManageStore.selectBookTaskDetail[i].subImagePath = undefined
|
||||
}
|
||||
message.success('重置所有生成图片成功')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 重置合并提示词数据
|
||||
async function ResetMergePromptData() {
|
||||
dialog.warning({
|
||||
title: '重置合并提示词数据提示',
|
||||
content: '继续操作会重置所有的合并提示词数据,请确认是不是继续?',
|
||||
positiveText: '继续',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
let res = await window.book.ResetMergePromptData(
|
||||
reverseManageStore.selectBookTask.id,
|
||||
OperateBookType.BOOKTASK
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
} else {
|
||||
// 前端做响应
|
||||
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
|
||||
reverseManageStore.selectBookTaskDetail[i].prompt = undefined
|
||||
}
|
||||
message.success('重置所有合并提示词数据成功')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置反推/GPT提示词,更具不同的小说类型进行不同的重置
|
||||
*/
|
||||
async function ResetGptReverseData() {
|
||||
dialog.warning({
|
||||
title: '删除反推提示词警告提示',
|
||||
content: '继续操作会移除所有的反推提示词数据,注意,是所有,请确认是不是继续?',
|
||||
positiveText: '继续',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
let res = await window.book.ResetGptReverseData(
|
||||
reverseManageStore.selectBookTask.id,
|
||||
OperateBookType.BOOKTASK,
|
||||
reverseManageStore.selectBook.type
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
} else {
|
||||
// 这个做个前端删除
|
||||
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
|
||||
reverseManageStore.selectBookTaskDetail[i].reversePrompt = undefined
|
||||
reverseManageStore.selectBookTaskDetail[i].gptPrompt = undefined
|
||||
}
|
||||
message.success('删除所有的反推/GPT提示词成功')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开设置对话框
|
||||
*/
|
||||
async function OpenSettingDialog() {
|
||||
let dialogWidth = window.innerWidth * 0.8
|
||||
let dialogHeight = window.innerHeight * 0.95
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
title: '设置',
|
||||
content: () => h(Setting, { height: dialogHeight }),
|
||||
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
|
||||
maskClosable: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* MJ反推数据
|
||||
* @param type 反推类型
|
||||
*/
|
||||
async function ImageReversePrompt(type = undefined) {
|
||||
if (isEmpty(reverseManageStore.selectBookTask.id)) {
|
||||
window.api.showGlobalMessageDialog({
|
||||
code: 0,
|
||||
message: '没有找到要执行的任务'
|
||||
})
|
||||
return
|
||||
}
|
||||
dialog.warning({
|
||||
title: '反推提示',
|
||||
content: `即将进行反推操作,反推方式为 ${
|
||||
type ? type : reverseManageStore.selectBook.type
|
||||
} ,是否继续?`,
|
||||
positiveText: '继续',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
if (!type) {
|
||||
let bookType = reverseManageStore.selectBook.type
|
||||
type = bookType
|
||||
}
|
||||
|
||||
if (type != BookType.MJ_REVERSE && type != BookType.SD_REVERSE) {
|
||||
message.error(`该类型 ${bookType} 的小说不支持反推`)
|
||||
return
|
||||
}
|
||||
// 开始添加
|
||||
let res = await window.book.AddReversePrompt(
|
||||
reverseManageStore.selectBookTask.id,
|
||||
OperateBookType.BOOKTASK,
|
||||
type
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
} else {
|
||||
message.success('添加所有反推任务成功')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译提示词
|
||||
* @param key 操作的值,进行分类
|
||||
*/
|
||||
async function Translate(key) {
|
||||
switch (key) {
|
||||
case undefined:
|
||||
case null:
|
||||
message.error('请选择子菜单中的选项')
|
||||
break
|
||||
case 'translate_chinese': // 将英文翻译到中文
|
||||
await TranslateService('en', 'zh')
|
||||
break
|
||||
|
||||
case 'translate_english': // 将中文翻译到英文
|
||||
await TranslateService('zh', 'en')
|
||||
break
|
||||
|
||||
case 'translate_setting': // 翻译设置
|
||||
await OpenTranslateSettingDialog()
|
||||
break
|
||||
|
||||
default:
|
||||
message.error('不支持的操作类型')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开翻译设置弹窗
|
||||
*/
|
||||
async function OpenTranslateSettingDialog() {
|
||||
// 判断当前数据是不是存在
|
||||
// 处理数据。获取当前的所有的数据
|
||||
let dialogWidth = window.innerWidth * 0.8
|
||||
let dialogHeight = window.innerHeight * 0.9
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
title: '翻译设置',
|
||||
content: () => h(TranslateSetting),
|
||||
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
|
||||
maskClosable: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用翻译接口
|
||||
* @param from 源语言
|
||||
* @param to 目标语言
|
||||
*/
|
||||
async function TranslateService(from, to) {
|
||||
|
||||
let translateData = []
|
||||
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
|
||||
const element = reverseManageStore.selectBookTaskDetail[i]
|
||||
if (!isEmpty(element.gptPrompt)) {
|
||||
translateData.push({
|
||||
text: element.gptPrompt,
|
||||
from: from,
|
||||
to: to,
|
||||
type: TranslateType.GPT_PROMPT_TRANSLATE,
|
||||
isSplit: false, // 该参数设置为true时,会将文本按照标点符号分割成多个句子进行翻译
|
||||
bookTaskDetailId: element.id,
|
||||
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
|
||||
})
|
||||
} else {
|
||||
let reversePrompt = element.reversePrompt
|
||||
if (reversePrompt) {
|
||||
reversePrompt.forEach((item) => {
|
||||
if (from == 'zh' && to == 'en') {
|
||||
// 判断是不是有中文
|
||||
if (!ContainsChineseOrPunctuation(item.promptCN ? item.promptCN : item.prompt)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
let temp_obj = {
|
||||
text: item.promptCN ? item.promptCN : item.prompt,
|
||||
from: from,
|
||||
to: to,
|
||||
type: TranslateType.REVERSE_PROMPT_TRANSLATE,
|
||||
isSplit: false, // 该参数设置为true时,会将文本按照标点符号分割成多个句子进行翻译
|
||||
bookTaskDetailId: element.id,
|
||||
reversePromptId: item.id,
|
||||
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
|
||||
}
|
||||
translateData.push(temp_obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if (translateData.length == 0) {
|
||||
message.error('没有需要翻译的数据')
|
||||
return
|
||||
}
|
||||
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = `翻译中 ... 0 / ${translateData.length}`
|
||||
// 开始翻译
|
||||
let res = await window.translate.TranslateNowReturn(translateData)
|
||||
softwareStore.spin.spinning = false
|
||||
window.api.showGlobalMessageDialog(res)
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成所有图片
|
||||
*/
|
||||
async function GenerateImageAll() {
|
||||
let res = await window.book.GenerateImageAll(
|
||||
reverseManageStore.selectBookTask.id,
|
||||
reverseManageStore.selectBookTask.imageCategory
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
message.success(res.message)
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入文案和SRT
|
||||
*/
|
||||
async function ImportWordAndSrtFunc() {
|
||||
let dialogWidth = window.innerWidth * 0.95
|
||||
let dialogHeight = window.innerHeight * 0.95
|
||||
|
||||
// 这边要处理下数据
|
||||
if (
|
||||
reverseManageStore.selectBook.type == BookType.MJ_REVERSE ||
|
||||
reverseManageStore.selectBook.type == BookType.MJ_REVERSE
|
||||
) {
|
||||
if (reverseManageStore.selectBookTaskDetail.length <= 0) {
|
||||
message.error('反推小说,没有找到对应的分镜数据')
|
||||
}
|
||||
}
|
||||
|
||||
let tempData = []
|
||||
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
|
||||
|
||||
const element = reverseManageStore.selectBookTaskDetail[i]
|
||||
tempData.push({
|
||||
no: element.no,
|
||||
id: element.id,
|
||||
lastId: i == 0 ? '' : reverseManageStore.selectBookTaskDetail[i - 1].id,
|
||||
word: element.word,
|
||||
after_gpt: element.afterGpt,
|
||||
subValue: element.subValue ? element.subValue : [],
|
||||
name: element.name + '.png',
|
||||
prompt: element.prompt,
|
||||
timeLimit: element.timeLimit
|
||||
})
|
||||
}
|
||||
|
||||
// ImportWordAndSrt
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
closeOnEsc: false,
|
||||
content: () =>
|
||||
h(ImportWordAndSrt, {
|
||||
initData: tempData,
|
||||
height: dialogHeight,
|
||||
type: BookType.MJ_REVERSE
|
||||
}),
|
||||
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
|
||||
maskClosable: false,
|
||||
onClose: () => {}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除导入文案
|
||||
*/
|
||||
async function ClearImportWord() {
|
||||
let da = dialog.warning({
|
||||
title: '清除导入文案提示',
|
||||
content:
|
||||
'继续执行该操作会将导入的对齐文案全部清除(包括文案的时间线),并且操作不可逆,是否继续?',
|
||||
positiveText: '继续',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
da?.destroy()
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '正在清除导入数据。。。'
|
||||
let res = await window.book.ClearImportWord(reverseManageStore.selectBookTask.id)
|
||||
softwareStore.spin.spinning = false
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
} else {
|
||||
// 这边重置下数据
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
const element = res.data[i]
|
||||
let index = reverseManageStore.selectBookTaskDetail.findIndex(
|
||||
(item) => item.id == element.id
|
||||
)
|
||||
if (index != -1) {
|
||||
reverseManageStore.selectBookTaskDetail[index].subValue = undefined
|
||||
reverseManageStore.selectBookTaskDetail[index].startTime = element.startTime
|
||||
reverseManageStore.selectBookTaskDetail[index].endTime = element.endTime
|
||||
reverseManageStore.selectBookTaskDetail[index].timeLimit = element.timeLimit
|
||||
}
|
||||
}
|
||||
message.success('清除导入文案成功')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function ActionFrame() {
|
||||
message.info('请使用下面菜单中的按钮进行对应的操作')
|
||||
}
|
||||
|
||||
return {
|
||||
RetuenMain,
|
||||
AutoAction,
|
||||
ImportWordAndSrtFunc,
|
||||
ClearImportWord,
|
||||
reverseManageStore,
|
||||
softwareStore,
|
||||
ActionFrame,
|
||||
switchLogger,
|
||||
ComputeStoryboard,
|
||||
OpenSettingDialog,
|
||||
GetCopywriting,
|
||||
RemoveWatermark,
|
||||
ImageReversePrompt,
|
||||
ResetGptReverseData,
|
||||
Translate,
|
||||
TranslateService,
|
||||
GenerateImageAll,
|
||||
handleSelect,
|
||||
AutoOptions: [
|
||||
{
|
||||
label: '停止任务',
|
||||
value: 'auto_stop'
|
||||
},
|
||||
{
|
||||
label: '继续任务',
|
||||
value: 'auto_continue'
|
||||
}
|
||||
],
|
||||
frameOptions: [
|
||||
{
|
||||
label: '计算分镜',
|
||||
key: 'compute_frame'
|
||||
},
|
||||
{ label: '分镜', key: 'framing' }
|
||||
],
|
||||
copywritingOptions: [
|
||||
{
|
||||
label: '提取空白文案',
|
||||
key: 'recognizing_blank'
|
||||
},
|
||||
{
|
||||
label: '提取文案位置',
|
||||
key: 'recognizing_setting'
|
||||
},
|
||||
{
|
||||
label: '导出文案',
|
||||
key: 'export_recognizing'
|
||||
},
|
||||
{
|
||||
label: '导入文案',
|
||||
key: 'import_recognizing'
|
||||
},
|
||||
{
|
||||
label: '停止提取',
|
||||
key: 'stop_recognizing'
|
||||
}
|
||||
],
|
||||
watermarkOptions: [
|
||||
{
|
||||
label: '选择水印位置',
|
||||
key: 'watermark_position'
|
||||
},
|
||||
{
|
||||
label: '停止去除',
|
||||
key: 'stop_watermark'
|
||||
}
|
||||
],
|
||||
reverseOptions: [
|
||||
{
|
||||
label: 'MJ反推',
|
||||
key: 'mj_reverse'
|
||||
},
|
||||
{
|
||||
label: 'SD反推',
|
||||
key: 'sd_reverse',
|
||||
disabled: reverseManageStore.selectBook.type != BookType.SD_REVERSE
|
||||
},
|
||||
{
|
||||
label: '停止反推',
|
||||
key: 'stop_reverse'
|
||||
}
|
||||
],
|
||||
translateOptions: [
|
||||
{
|
||||
label: '英文 -> 中文',
|
||||
key: 'translate_chinese'
|
||||
},
|
||||
{
|
||||
label: '中文 -> 英文',
|
||||
key: 'translate_english'
|
||||
},
|
||||
{
|
||||
label: '翻译设置',
|
||||
key: 'translate_setting'
|
||||
},
|
||||
{
|
||||
label: '停止翻译',
|
||||
key: 'translate_stop'
|
||||
}
|
||||
],
|
||||
generateOptions: [
|
||||
{
|
||||
label: '停止生图',
|
||||
key: 'stop_image_generate'
|
||||
}
|
||||
],
|
||||
importWordOptions: [
|
||||
{
|
||||
label: '清除导入文案',
|
||||
key: 'clear_import_word'
|
||||
}
|
||||
],
|
||||
resetOptions: [
|
||||
{
|
||||
label: '重置反推/GPT提示词',
|
||||
key: 'reset_gpt_reverse_data'
|
||||
},
|
||||
{
|
||||
label: '重置合并提示词',
|
||||
key: 'reset_merge_prompt'
|
||||
},
|
||||
{
|
||||
label: '重置图片',
|
||||
key: 'reset_Generate_Image'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
.right-button {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.top-button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
@ -35,7 +35,6 @@ export default defineComponent({
|
||||
props: ['initData', 'index'],
|
||||
setup(props) {
|
||||
let data = ref(props.initData)
|
||||
console.log('data', props.initData, props.index)
|
||||
onMounted(async () => {})
|
||||
let InputDebounced = debounce(handleInput, 1000)
|
||||
|
||||
@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-dropdown trigger="hover" :options="reverseOptions" @select="ReverseSelect">
|
||||
<n-button size="tiny" :color="softwareStore.SoftColor.BROWN_YELLOW" @click="AddReversePrompt">
|
||||
单句反推
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
<n-dropdown trigger="hover" :options="tranlateOptions" @select="ReverseSelect">
|
||||
<n-button
|
||||
style="margin-left: 5px"
|
||||
size="tiny"
|
||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||
@click="TranslateOne('translate_chinese')"
|
||||
>
|
||||
英文 2 中文
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
<n-button
|
||||
v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE"
|
||||
style="margin-left: 5px"
|
||||
size="tiny"
|
||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||
@click="SelectReversePrompt"
|
||||
>
|
||||
重选反推提示词
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
|
||||
import { useMessage, useDialog, NDropdown, NButton } from 'naive-ui'
|
||||
import { useSoftwareStore } from '../../../../../stores/software'
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage'
|
||||
import { TranslateType } from '../../../../../define/enum/translate'
|
||||
import { ContainsChineseOrPunctuation } from '../../../../../define/Tools/common'
|
||||
import ReSelectReversePrompt from '../MJReverse/ReSelectReversePrompt.vue'
|
||||
import { BookType, OperateBookType } from '../../../../../define/enum/bookEnum'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { DEFINE_STRING } from '../../../../../define/define_string'
|
||||
|
||||
let props = defineProps({
|
||||
initData: undefined,
|
||||
index: undefined
|
||||
})
|
||||
let message = useMessage()
|
||||
let dialog = useDialog()
|
||||
let data = ref(props.initData)
|
||||
let index = ref(props.index)
|
||||
let softwareStore = useSoftwareStore()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let bookType = ref(BookType)
|
||||
onMounted(async () => {})
|
||||
|
||||
/**
|
||||
* 单句反推提示词
|
||||
*/
|
||||
async function AddReversePrompt() {
|
||||
let res = await window.book.AddReversePrompt(data.value.id, OperateBookType.BOOKTASKDETAIL, null)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
} else {
|
||||
message.success('添加反推任务成功')
|
||||
}
|
||||
}
|
||||
|
||||
async function AddNextReversePrompt() {
|
||||
let res = await window.book.AddReversePrompt(
|
||||
data.value.id,
|
||||
OperateBookType.UNDERBOOKTASK,
|
||||
reverseManageStore.selectBook.type
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
} else {
|
||||
message.success('添加反推任务成功')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提示词菜单下拉操作
|
||||
*/
|
||||
async function ReverseSelect(key) {
|
||||
switch (key) {
|
||||
case 'remove_reverse': // 删除反推提示词
|
||||
let res = await window.book.ResetGptReverseData(
|
||||
data.value.id,
|
||||
OperateBookType.BOOKTASKDETAIL,
|
||||
reverseManageStore.selectBook.type
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
} else {
|
||||
reverseManageStore.selectBookTaskDetail[props.index].gptPrompt = undefined
|
||||
reverseManageStore.selectBookTaskDetail[props.index].reversePrompt = undefined
|
||||
message.success('删除反推数据成功')
|
||||
}
|
||||
break
|
||||
case 'translate_english': // 中文 2 英文
|
||||
await TranslateOne('translate_english')
|
||||
break
|
||||
|
||||
case 'down_translate_chinese': // 下翻译中文
|
||||
await TranslateOne('down_translate_chinese')
|
||||
break
|
||||
case 'down_translate_english': // 下翻译英文
|
||||
await TranslateOne('down_translate_english')
|
||||
break
|
||||
case 'down_reverse':
|
||||
await AddNextReversePrompt()
|
||||
break
|
||||
default:
|
||||
message.error('未知的操作类型')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 处理翻译英文的数据
|
||||
function TranslateOneParams(from, to) {
|
||||
let translateData = []
|
||||
if (!isEmpty(data.value.gptPrompt)) {
|
||||
translateData.push({
|
||||
text: data.value.gptPrompt,
|
||||
from: from,
|
||||
to: to,
|
||||
type: TranslateType.GPT_PROMPT_TRANSLATE,
|
||||
isSplit: false, // 该参数设置为true时,会将文本按照标点符号分割成多个句子进行翻译
|
||||
bookTaskDetailId: data.value.id,
|
||||
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
|
||||
})
|
||||
} else {
|
||||
let reversePrompt = data.value.reversePrompt
|
||||
if (reversePrompt) {
|
||||
reversePrompt.forEach((item) => {
|
||||
let temp_obj = undefined
|
||||
if (from == 'zh' && to == 'en') {
|
||||
// 判断是不是有中文
|
||||
if (!ContainsChineseOrPunctuation(item.promptCN ? item.promptCN : item.prompt)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
temp_obj = {
|
||||
text: item.prompt,
|
||||
from: from,
|
||||
to: to,
|
||||
type: TranslateType.REVERSE_PROMPT_TRANSLATE,
|
||||
isSplit: false, // 该参数设置为true时,会将文本按照标点符号分割成多个句子进行翻译
|
||||
bookTaskDetailId: data.value.id,
|
||||
reversePromptId: item.id,
|
||||
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
|
||||
}
|
||||
|
||||
translateData.push(temp_obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
return translateData
|
||||
}
|
||||
|
||||
// 处理批次的翻译
|
||||
function TranslateBatchParams(from, to) {
|
||||
let translateData = []
|
||||
for (let i = index.value; i < reverseManageStore.selectBookTaskDetail.length; i++) {
|
||||
const element = reverseManageStore.selectBookTaskDetail[i]
|
||||
// 这边判断GPT提示词是不是存在,存在的话翻译GPT提示词
|
||||
if (!isEmpty(element.gptPrompt)) {
|
||||
translateData.push({
|
||||
text: element.gptPrompt,
|
||||
from: from,
|
||||
to: to,
|
||||
type: TranslateType.GPT_PROMPT_TRANSLATE,
|
||||
isSplit: false, // 该参数设置为true时,会将文本按照标点符号分割成多个句子进行翻译
|
||||
bookTaskDetailId: element.id,
|
||||
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
|
||||
})
|
||||
} else {
|
||||
let reversePrompt = element.reversePrompt
|
||||
if (reversePrompt) {
|
||||
reversePrompt.forEach((item) => {
|
||||
if (from == 'zh' && to == 'en') {
|
||||
// 判断是不是有中文
|
||||
if (!ContainsChineseOrPunctuation(item.promptCN ? item.promptCN : item.prompt)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let temp_obj = {
|
||||
text: item.prompt,
|
||||
from: from,
|
||||
to: to,
|
||||
type: TranslateType.REVERSE_PROMPT_TRANSLATE,
|
||||
isSplit: false, // 该参数设置为true时,会将文本按照标点符号分割成多个句子进行翻译
|
||||
bookTaskDetailId: element.id,
|
||||
reversePromptId: item.id,
|
||||
responseMessgeName: DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN
|
||||
}
|
||||
translateData.push(temp_obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return translateData
|
||||
}
|
||||
|
||||
// 单句翻译
|
||||
async function TranslateOne(key) {
|
||||
let translateData = []
|
||||
switch (key) {
|
||||
case 'translate_english':
|
||||
translateData = TranslateOneParams('zh', 'en')
|
||||
break
|
||||
case 'translate_chinese':
|
||||
translateData = TranslateOneParams('en', 'zh')
|
||||
break
|
||||
case 'down_translate_english':
|
||||
translateData = TranslateBatchParams('zh', 'en')
|
||||
break
|
||||
case 'down_translate_chinese':
|
||||
translateData = TranslateBatchParams('en', 'zh')
|
||||
break
|
||||
default:
|
||||
message.error('未知的操作类型')
|
||||
break
|
||||
}
|
||||
if (translateData.length <= 0) {
|
||||
message.error('没有需要翻译的数据')
|
||||
return
|
||||
}
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = `翻译中 ... 0 / ${translateData.length}`
|
||||
// 开始翻译
|
||||
let res = await window.translate.TranslateNowReturn(translateData)
|
||||
softwareStore.spin.spinning = false
|
||||
window.api.showGlobalMessageDialog(res)
|
||||
}
|
||||
|
||||
/**
|
||||
* 重选反推提示词
|
||||
*/
|
||||
async function SelectReversePrompt() {
|
||||
// 判断当前是不是又反推提示词
|
||||
if (!data.value.reversePrompt || data.value.reversePrompt.length <= 0) {
|
||||
message.error('当前没有反推提示词')
|
||||
return
|
||||
}
|
||||
|
||||
// ImportWordAndSrt
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
title: `重选MJ反推提示词 ${data.value.name}`,
|
||||
closeOnEsc: false,
|
||||
content: () =>
|
||||
h(ReSelectReversePrompt, {
|
||||
reversePrompts: data.value.reversePrompt,
|
||||
index: index.value
|
||||
}),
|
||||
style: `width : 750px;`,
|
||||
maskClosable: false
|
||||
})
|
||||
}
|
||||
let reverseOptions = [
|
||||
{
|
||||
label: '下推理',
|
||||
key: 'down_reverse'
|
||||
},
|
||||
{
|
||||
label: '删除反推提示词',
|
||||
key: 'remove_reverse'
|
||||
}
|
||||
]
|
||||
let tranlateOptions = [
|
||||
{
|
||||
label: '中文 2 英文',
|
||||
key: 'translate_english'
|
||||
},
|
||||
{
|
||||
label: '下翻译英文',
|
||||
key: 'down_translate_english'
|
||||
},
|
||||
{
|
||||
label: '下翻译中文',
|
||||
key: 'down_translate_chinese'
|
||||
}
|
||||
]</script>
|
||||
@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div style="display: flex; justify-content: space-between; white-space: nowrap">
|
||||
<div style="display: flex">
|
||||
<div>反推/GPT提示词</div>
|
||||
<n-button
|
||||
v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE"
|
||||
type="text"
|
||||
:color="softwareStore.SoftColor.ZHUYANTUO"
|
||||
size="tiny"
|
||||
style="margin-left: 10px"
|
||||
@click="SelectPromptIndex"
|
||||
>选择提示词</n-button
|
||||
><n-button
|
||||
type="text"
|
||||
:color="softwareStore.SoftColor.ZHUYANTUO"
|
||||
size="tiny"
|
||||
style="margin-left: 5px"
|
||||
@click="SelectStyle"
|
||||
>风格</n-button
|
||||
>
|
||||
<n-button
|
||||
type="text"
|
||||
:color="softwareStore.SoftColor.ZHUYANTUO"
|
||||
size="tiny"
|
||||
style="margin-left: 5px"
|
||||
@click="RemoveBadPrompt"
|
||||
>去除提示词</n-button
|
||||
>
|
||||
<n-button
|
||||
type="text"
|
||||
:color="softwareStore.SoftColor.ZHUYANTUO"
|
||||
size="tiny"
|
||||
style="margin-left: 5px"
|
||||
@click="ManagentBadPrompt"
|
||||
>管理</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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, toRaw, h } from 'vue'
|
||||
import { useMessage, useDialog, NButton, NPopover, NIcon } from 'naive-ui'
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage'
|
||||
import { BookRepalceDataType, BookType } from '../../../../../define/enum/bookEnum'
|
||||
import { useSoftwareStore } from '../../../../../stores/software'
|
||||
import SelectMJReversePrompt from '../MJReverse/SelectMJReversePrompt.vue'
|
||||
import SelectImageStyle from '../../Components/SelectImageStyle.vue'
|
||||
import FindReplaceRound from '../../Icon/FindReplaceRound.vue'
|
||||
import DatatableHeaderAfterGptSelectAndReplace from './DatatableHeaderAfterGptSelectAndReplace.vue'
|
||||
import ManageBadPrompt from '../../Components/ManageBadPrompt.vue'
|
||||
|
||||
let message = useMessage()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let softwareStore = useSoftwareStore()
|
||||
let bookType = ref(BookType)
|
||||
let dialog = useDialog()
|
||||
let selectStyleRef = ref(null)
|
||||
let da = undefined
|
||||
let promprRef = ref(null)
|
||||
|
||||
/**
|
||||
* 管理不想要的值
|
||||
*/
|
||||
async function ManagentBadPrompt() {
|
||||
// 弹窗,管理不想要的值
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
title: '管理不想要的值',
|
||||
content: () => h(ManageBadPrompt, { ref: promprRef }),
|
||||
style: `width : 650px;`,
|
||||
maskClosable: false,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
console.log(toRaw(promprRef.value.tags))
|
||||
// 将数据写入
|
||||
await window.api.SaveBadPrompt(toRaw(promprRef.value.tags), (value) => {
|
||||
console.log(value)
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return false
|
||||
}
|
||||
message.success('添加成功')
|
||||
})
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 移除不想要的值 */
|
||||
async function RemoveBadPrompt() {
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '正在移除不想要的值...'
|
||||
let dia = dialog.warning({
|
||||
title: '移除不想要的值提示',
|
||||
content: '是否移除反推/GPT提示词里面不想要的值,移除后不可恢复,是否继续?',
|
||||
positiveText: '继续',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
dia.destroy()
|
||||
let res = await window.book.RemoveBadPrompt(reverseManageStore.selectBookTask.id)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
console.log('RemoveBadPrompt', res)
|
||||
if (res.data && res.data.length > 0) {
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
const element = res.data[i]
|
||||
let findIndex = reverseManageStore.selectBookTaskDetail.findIndex(
|
||||
(item) => item.id == element.id
|
||||
)
|
||||
if (findIndex != -1) {
|
||||
reverseManageStore.selectBookTaskDetail[findIndex].gptPrompt = element.gptPrompt
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('没有移除任何值')
|
||||
}
|
||||
message.success(res.message)
|
||||
} catch (error) {
|
||||
message.error(error.message)
|
||||
} finally {
|
||||
softwareStore.spin.spinning = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择MJ反推提示词。用于生图
|
||||
*/
|
||||
async function SelectPromptIndex() {
|
||||
// 判断是不是MJ反推,判断是不是所有的都有提示词
|
||||
if (reverseManageStore.selectBook.type != BookType.MJ_REVERSE) {
|
||||
message.error('只有MJ反推可以使用该按钮')
|
||||
return
|
||||
}
|
||||
let hasReversePrompt = false
|
||||
// 判断是不是所有的都有提示词,只要有一个有,就可以
|
||||
for (let index = 0; index < reverseManageStore.selectBookTaskDetail.length; index++) {
|
||||
const element = reverseManageStore.selectBookTaskDetail[index]
|
||||
if (element.reversePrompt && element.reversePrompt.length > 0) {
|
||||
hasReversePrompt = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!hasReversePrompt) {
|
||||
message.error('请先进行MJ反推')
|
||||
return
|
||||
}
|
||||
dialog.create({
|
||||
title: '选择反推提示词',
|
||||
content: () => h(SelectMJReversePrompt),
|
||||
style: 'width : 500px',
|
||||
showIcon: false
|
||||
})
|
||||
}
|
||||
|
||||
// 选择风格
|
||||
async function SelectStyle() {
|
||||
// 先获取风格
|
||||
// 判断当前数据是不是存在
|
||||
// 处理数据。获取当前的所有的数据
|
||||
let dialogWidth = window.innerWidth * 0.8
|
||||
let dialogHeight = window.innerHeight * 0.9
|
||||
// ImportWordAndSrt
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
closeOnEsc: false,
|
||||
content: () =>
|
||||
h(SelectImageStyle, {
|
||||
height: dialogHeight,
|
||||
ref: selectStyleRef,
|
||||
imageStyle: reverseManageStore.selectBookTask.imageStyle,
|
||||
customizeImageStyle: reverseManageStore.selectBookTask.customizeImageStyle
|
||||
}),
|
||||
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
|
||||
maskClosable: false,
|
||||
onClose: async () => {
|
||||
console.log(selectStyleRef)
|
||||
// 将数据保存到当前的数据中
|
||||
// reverseManageStore.selectBookTask.styleList = toRaw(selectStyleRef.value.selectStyle)
|
||||
|
||||
// 保存当前的风格到数据库
|
||||
let res = await window.book.SaveImageStyle(
|
||||
toRaw(selectStyleRef.value.selectStyle),
|
||||
reverseManageStore.selectBookTask.id
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
reverseManageStore.selectBookTask.imageStyle = res.data.imageStyle
|
||||
reverseManageStore.selectBookTask.customizeImageStyle = res.data.customizeImageStyle
|
||||
message.success('保存成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function SelectReversePromptAndReplace() {
|
||||
if (da) {
|
||||
return
|
||||
}
|
||||
da = dialog.create({
|
||||
title: '批量查询替换反推提示词',
|
||||
showIcon: false,
|
||||
maskClosable: false,
|
||||
content: () =>
|
||||
h(DatatableHeaderAfterGptSelectAndReplace, { type: BookRepalceDataType.GPT_PROMPT }),
|
||||
onClose: () => {
|
||||
da = undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@ -2,7 +2,7 @@
|
||||
<div style="display: flex; align-items: center">
|
||||
<div>出图</div>
|
||||
<n-select
|
||||
style="width: 70px; margin-left: 5px"
|
||||
style="width: 100px; margin-left: 5px"
|
||||
size="tiny"
|
||||
v-model:value="select"
|
||||
@update:value="handleUpdateValue"
|
||||
@ -47,13 +47,20 @@ export default defineComponent({
|
||||
? reverseManageStore.selectBookTask.imageCategory
|
||||
: BookImageCategory.MJ
|
||||
)
|
||||
onMounted(async () => {})
|
||||
let options = ref([])
|
||||
|
||||
async function handleUpdateValue(value) {
|
||||
if (value == BookImageCategory.D3) {
|
||||
message.error('暂不支持该出图模式')
|
||||
onMounted(async () => {
|
||||
// 获取生图下拉列表的方式
|
||||
await window.api.GetImageGenerateCategory((value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
options.value = value.data
|
||||
})
|
||||
})
|
||||
|
||||
async function handleUpdateValue(value) {
|
||||
// 将值的修改保存进数据库中
|
||||
let res = await window.db.UpdateBookTaskData(reverseManageStore.selectBookTask.id, {
|
||||
imageCategory: value
|
||||
@ -124,20 +131,7 @@ export default defineComponent({
|
||||
key: 'unlock'
|
||||
}
|
||||
],
|
||||
options: [
|
||||
{
|
||||
label: 'MJ',
|
||||
value: BookImageCategory.MJ
|
||||
},
|
||||
{
|
||||
label: 'SD',
|
||||
value: BookImageCategory.SD
|
||||
},
|
||||
{
|
||||
label: 'D3',
|
||||
value: BookImageCategory.D3
|
||||
}
|
||||
]
|
||||
options
|
||||
}
|
||||
}
|
||||
})
|
||||
305
src/renderer/src/components/Book/MJReverse/DatatablePrompt.vue
Normal file
305
src/renderer/src/components/Book/MJReverse/DatatablePrompt.vue
Normal file
@ -0,0 +1,305 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-dropdown trigger="hover" :options="mergeOptions" @select="MergePrompt">
|
||||
<n-button
|
||||
size="tiny"
|
||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||
@click="MergePrompt(undefined)"
|
||||
>
|
||||
单句合并
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
|
||||
<n-button
|
||||
style="margin-left: 5px"
|
||||
size="tiny"
|
||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||
@click="SingleGenerateImage"
|
||||
>
|
||||
单句生图
|
||||
</n-button>
|
||||
|
||||
<n-button
|
||||
style="margin-left: 5px"
|
||||
size="tiny"
|
||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||
@click="UnderGenerateImage"
|
||||
>
|
||||
下生图
|
||||
</n-button>
|
||||
<n-button
|
||||
style="margin-left: 5px"
|
||||
size="tiny"
|
||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||
@click="ImportMJImageUrl"
|
||||
>
|
||||
导入图片
|
||||
</n-button>
|
||||
</div>
|
||||
<n-input
|
||||
type="textarea"
|
||||
size="tiny"
|
||||
@input="InputDebounced"
|
||||
v-model:value="data.prompt"
|
||||
:autosize="{ minRows: 6, maxRows: 6 }"
|
||||
></n-input>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
|
||||
import { useMessage, useDialog, NInput, NButton, NDropdown } from 'naive-ui'
|
||||
import { useSoftwareStore } from '../../../../../stores/software'
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage'
|
||||
import {
|
||||
BookBackTaskType,
|
||||
OperateBookType,
|
||||
TaskExecuteType
|
||||
} from '../../../../../define/enum/bookEnum'
|
||||
import { BookImageCategory } from '../../../../../define/enum/bookEnum'
|
||||
import { debounce } from 'lodash'
|
||||
import InputDialogContent from '../../Original/Components/InputDialogContent.vue'
|
||||
import { DEFINE_STRING } from '../../../../../define/define_string'
|
||||
|
||||
let props = defineProps({
|
||||
initData: undefined,
|
||||
index: undefined
|
||||
})
|
||||
|
||||
let message = useMessage()
|
||||
let softwareStore = useSoftwareStore()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let data = ref(props.initData)
|
||||
let image_url_ref = ref(null)
|
||||
let dialog = useDialog()
|
||||
onMounted(async () => {})
|
||||
|
||||
// 合并提示词
|
||||
async function MergePrompt(key) {
|
||||
let type = key
|
||||
if (!key) {
|
||||
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
|
||||
type = 'mj_merge'
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
|
||||
type = 'sd_merge'
|
||||
} else {
|
||||
type = 'mj_merge'
|
||||
}
|
||||
}
|
||||
// 开始进行合并,调用不同的合并模式
|
||||
// 合并之前,要确认保存
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '合并命令中。。。'
|
||||
let res = await window.book.MergePrompt(data.value.id, type, OperateBookType.BOOKTASKDETAIL)
|
||||
softwareStore.spin.spinning = false
|
||||
// 这边对返回的数据进行处理
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
const element = res.data[i]
|
||||
let selectBookDetailIndex = reverseManageStore.selectBookTaskDetail.findIndex(
|
||||
(item) => item.id == element.id
|
||||
)
|
||||
reverseManageStore.selectBookTaskDetail[selectBookDetailIndex].prompt = element.prompt
|
||||
}
|
||||
message.success(res.message)
|
||||
}
|
||||
|
||||
// 单个出图
|
||||
async function SingleGenerateImage() {
|
||||
let res = undefined
|
||||
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
|
||||
res = await window.mj.AddMJGenerateImageTask(
|
||||
data.value.id,
|
||||
OperateBookType.BOOKTASKDETAIL,
|
||||
DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN
|
||||
)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
|
||||
// 直接添加SD出图任务
|
||||
res = await window.task.AddBookBackTask(
|
||||
reverseManageStore.selectBook.id,
|
||||
BookBackTaskType.SD_IMAGE,
|
||||
TaskExecuteType.AUTO,
|
||||
reverseManageStore.selectBookTask.id,
|
||||
data.value.id,
|
||||
DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN
|
||||
)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.D3) {
|
||||
res = await window.task.AddBookBackTask(
|
||||
reverseManageStore.selectBook.id,
|
||||
BookBackTaskType.D3_IMAGE,
|
||||
TaskExecuteType.AUTO,
|
||||
reverseManageStore.selectBookTask.id,
|
||||
data.value.id,
|
||||
DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN
|
||||
)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_API) {
|
||||
res = await window.task.AddBookBackTask(
|
||||
reverseManageStore.selectBook.id,
|
||||
BookBackTaskType.FLUX_API_IMAGE,
|
||||
TaskExecuteType.AUTO,
|
||||
reverseManageStore.selectBookTask.id,
|
||||
data.value.id,
|
||||
DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN
|
||||
)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_FORGE) {
|
||||
res = await window.task.AddBookBackTask(
|
||||
reverseManageStore.selectBook.id,
|
||||
BookBackTaskType.FLUX_FORGE_IMAGE,
|
||||
TaskExecuteType.AUTO,
|
||||
reverseManageStore.selectBookTask.id,
|
||||
data.value.id,
|
||||
DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN
|
||||
)
|
||||
} else {
|
||||
message.error('未知的出图模式')
|
||||
return
|
||||
}
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
message.success(res.message)
|
||||
}
|
||||
|
||||
// 下生图
|
||||
async function UnderGenerateImage() {
|
||||
let res = undefined
|
||||
|
||||
let bookTaskDetails = reverseManageStore.selectBookTaskDetail.filter(
|
||||
(item, index) => index >= props.index && !item.imageLock
|
||||
)
|
||||
if (bookTaskDetails.length <= 0) {
|
||||
message.error('未找到可生成图片的分镜,或者分镜都被锁定')
|
||||
return
|
||||
}
|
||||
|
||||
if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.MJ) {
|
||||
res = await window.mj.AddMJGenerateImageTask(
|
||||
data.value.id,
|
||||
OperateBookType.UNDERBOOKTASK,
|
||||
DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN
|
||||
)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.SD) {
|
||||
// 添加SD出图任务
|
||||
let tasksList = []
|
||||
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||
const element = bookTaskDetails[i]
|
||||
tasksList.push({
|
||||
bookId: reverseManageStore.selectBook.id,
|
||||
type: BookBackTaskType.SD_IMAGE,
|
||||
executeType: TaskExecuteType.AUTO,
|
||||
bookTaskId: reverseManageStore.selectBookTask.id,
|
||||
bookTaskDetailId: element.id,
|
||||
messageName: DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN
|
||||
})
|
||||
}
|
||||
res = await window.task.AddMultiBookBackTask(tasksList)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_FORGE) {
|
||||
// 本地的forge flux
|
||||
let tasksList = []
|
||||
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||
const element = bookTaskDetails[i]
|
||||
tasksList.push({
|
||||
bookId: reverseManageStore.selectBook.id,
|
||||
type: BookBackTaskType.FLUX_FORGE_IMAGE,
|
||||
executeType: TaskExecuteType.AUTO,
|
||||
bookTaskId: reverseManageStore.selectBookTask.id,
|
||||
bookTaskDetailId: element.id,
|
||||
messageName: DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN
|
||||
})
|
||||
}
|
||||
res = await window.task.AddMultiBookBackTask(tasksList)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.FLUX_API) {
|
||||
// 本地的forge flux
|
||||
let tasksList = []
|
||||
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||
const element = bookTaskDetails[i]
|
||||
tasksList.push({
|
||||
bookId: reverseManageStore.selectBook.id,
|
||||
type: BookBackTaskType.FLUX_API_IMAGE,
|
||||
executeType: TaskExecuteType.AUTO,
|
||||
bookTaskId: reverseManageStore.selectBookTask.id,
|
||||
bookTaskDetailId: element.id,
|
||||
messageName: DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN
|
||||
})
|
||||
}
|
||||
res = await window.task.AddMultiBookBackTask(tasksList)
|
||||
} else if (reverseManageStore.selectBookTask.imageCategory == BookImageCategory.D3) {
|
||||
let tasksList = []
|
||||
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||
const element = bookTaskDetails[i]
|
||||
tasksList.push({
|
||||
bookId: reverseManageStore.selectBook.id,
|
||||
type: BookBackTaskType.D3_IMAGE,
|
||||
executeType: TaskExecuteType.AUTO,
|
||||
bookTaskId: reverseManageStore.selectBookTask.id,
|
||||
bookTaskDetailId: element.id,
|
||||
messageName: DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN
|
||||
})
|
||||
}
|
||||
res = await window.task.AddMultiBookBackTask(tasksList)
|
||||
} else {
|
||||
message.error('未知的出图模式')
|
||||
return
|
||||
}
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
message.success(res.message)
|
||||
}
|
||||
|
||||
let InputDebounced = debounce(handleInput, 1000)
|
||||
async function handleInput() {
|
||||
let res = await window.db.UpdateBookTaskDetailData(data.value.id, {
|
||||
prompt: data.value.prompt
|
||||
})
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入图片的链接,将图片链接下载,并且分割
|
||||
*/
|
||||
async function ImportMJImageUrl() {
|
||||
// 判断当前数据是不是存在
|
||||
// 处理数据。获取当前的所有的数据
|
||||
let dialogWidth = 400
|
||||
let dialogHeight = 150
|
||||
let da = dialog.create({
|
||||
title: '添加图片链接/本地地址',
|
||||
showIcon: false,
|
||||
closeOnEsc: false,
|
||||
content: () =>
|
||||
h(InputDialogContent, {
|
||||
ref: image_url_ref,
|
||||
initData: null,
|
||||
placeholder: '请输入图片链接/本地地址'
|
||||
}),
|
||||
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
|
||||
maskClosable: false,
|
||||
onClose: async () => {
|
||||
da?.destroy()
|
||||
let row_image_url = image_url_ref.value.data
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '正在下载图片/分割图片中。。。'
|
||||
let res = await window.book.DownloadImageUrlAndSplit(data.value.id, row_image_url)
|
||||
softwareStore.spin.spinning = false
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
data.value.outImagePath = res.data.outImagePath
|
||||
data.value.subImagePath = res.data.subImagePath
|
||||
message.success(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
let mergeOptions = [
|
||||
{ label: 'MJ模式合并', key: 'mj_merge' },
|
||||
{ label: 'SD模式合并', key: 'sd_merge' }
|
||||
]
|
||||
</script>
|
||||
@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<n-spin :show="show">
|
||||
<n-form :label-width="80" :model="formValue">
|
||||
<n-form-item label="选择剪映草稿" path="draftName">
|
||||
<n-select
|
||||
v-model:value="formValue.draftName"
|
||||
filterable
|
||||
placeholder="选择草稿"
|
||||
:options="draftSelect"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="writeTimeLine">
|
||||
<n-button type="primary" @click="GetJianyingFrame">开始抽帧</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #description> 正在剪映抽帧中... </template>
|
||||
</n-spin>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import {
|
||||
NForm,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NSpin,
|
||||
NButton,
|
||||
NSelect,
|
||||
NCheckbox,
|
||||
useMessage,
|
||||
useDialog
|
||||
} from 'naive-ui'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage.ts'
|
||||
|
||||
let draftSelect = ref([])
|
||||
let formValue = ref({
|
||||
draftName: undefined
|
||||
})
|
||||
let show = ref(false)
|
||||
let message = useMessage()
|
||||
let dialog = useDialog()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
|
||||
onMounted(() => {
|
||||
// 获取草稿文件列表
|
||||
window.api.getDraftFileList((value) => {
|
||||
value.forEach((element) => {
|
||||
let obj = {
|
||||
label: element,
|
||||
value: element
|
||||
}
|
||||
draftSelect.value.push(obj)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
async function GetJianyingFrame() {
|
||||
if (
|
||||
reverseManageStore.selectBookTaskDetail &&
|
||||
reverseManageStore.selectBookTaskDetail.length > 0
|
||||
) {
|
||||
let da = dialog.warning({
|
||||
title: '剪映抽帧提示',
|
||||
content:
|
||||
'检测到到当前已经存在分镜数据,继续抽帧将会首先删除当前数据,哪怕是失败也不能恢复,是否继续?',
|
||||
positiveText: '继续',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
da.destroy()
|
||||
await JianyingFrame()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await JianyingFrame()
|
||||
}
|
||||
}
|
||||
|
||||
async function JianyingFrame() {
|
||||
if (isEmpty(formValue.value.draftName)) {
|
||||
message.error('请选择草稿文件')
|
||||
return
|
||||
}
|
||||
show.value = true
|
||||
try {
|
||||
let res = await window.book.JianyingFrame(
|
||||
reverseManageStore.selectBookTask.id,
|
||||
formValue.value.draftName
|
||||
)
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
|
||||
message.success('剪映抽帧成功')
|
||||
// 重新加载数据
|
||||
// 分镜完成,重新获取一下数据
|
||||
let getRes = await reverseManageStore.GetBookTaskDetail(reverseManageStore.selectBookTask.id)
|
||||
if (getRes.code == 0) {
|
||||
message.error(getRes.message)
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(error.toString())
|
||||
} finally {
|
||||
show.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -37,7 +37,7 @@
|
||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, computed } from 'vue'
|
||||
import { useMessage, NButton, NInput, NSpin, NDropdown } from 'naive-ui'
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage'
|
||||
import DatatableGptPromptButton from '../Components/DatatableGptPromptButton.vue'
|
||||
import DatatableGptPromptButton from './DatatableGptPromptButton.vue'
|
||||
import { debounce } from 'lodash'
|
||||
|
||||
export default defineComponent({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -10,34 +10,31 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, defineComponent, watch, h } from 'vue'
|
||||
<script setup>
|
||||
import { ref, onMounted, defineComponent, computed, watch, h } from 'vue'
|
||||
import { useMessage, NDataTable, NImage } from 'naive-ui'
|
||||
import { useSoftwareStore } from '../../../../../stores/software'
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage'
|
||||
import DataTableShowGenerateImage from '../../Original/Components/DataTableShowGenerateImage.vue'
|
||||
import ManageBookOldImage from '../Components/ManageBookOldImage.vue'
|
||||
import ManageBookOldImage from './ManageBookOldImage.vue'
|
||||
import { BookType } from '../../../../../define/enum/bookEnum'
|
||||
import MJReversePrompt from './MJReversePrompt.vue'
|
||||
import DatatableHeaderGptPrompt from '../Components/DatatableHeaderGptPrompt.vue'
|
||||
import DatatableHeaderAfterGpt from '../Components/DatatableHeaderAfterGpt.vue'
|
||||
import DatatableHeaderPrompt from '../Components/DatatableHeaderPrompt.vue'
|
||||
import DatatablePrompt from '../Components/DatatablePrompt.vue'
|
||||
import DatatableHeaderImage from '../Components/DatatableHeaderImage.vue'
|
||||
import DatatableAfterGpt from '../Components/DatatableAfterGpt.vue'
|
||||
import DatatableGenerateImage from '../Components/DatatableGenerateImage.vue'
|
||||
import DatatableHeaderGptPrompt from './DatatableHeaderGptPrompt.vue'
|
||||
import DatatableHeaderAfterGpt from './DatatableHeaderAfterGpt.vue'
|
||||
import DatatableHeaderPrompt from './DatatableHeaderPrompt.vue'
|
||||
import DatatablePrompt from './DatatablePrompt.vue'
|
||||
import DatatableHeaderImage from './DatatableHeaderImage.vue'
|
||||
import DatatableAfterGpt from './DatatableAfterGpt.vue'
|
||||
import DatatableGenerateImage from './DatatableGenerateImage.vue'
|
||||
import ODataTableAction from '../Original/ODataTableAction.vue'
|
||||
import SDReversePrompt from './SDReversePrompt.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: { NDataTable, NImage },
|
||||
let softwareStore = useSoftwareStore()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let message = useMessage()
|
||||
let maxHeight = ref(0)
|
||||
let datatable = ref(null)
|
||||
|
||||
setup() {
|
||||
let softwareStore = useSoftwareStore()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let message = useMessage()
|
||||
let maxHeight = ref(0)
|
||||
let datatable = ref(null)
|
||||
|
||||
watch(
|
||||
watch(
|
||||
() => softwareStore.skip,
|
||||
(newValue) => {
|
||||
let doc = document.querySelectorAll('.n-data-table-tr')
|
||||
@ -45,12 +42,12 @@ export default defineComponent({
|
||||
datatable.value.scrollTo({ el: sk })
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
)
|
||||
|
||||
const createColumns = () => [
|
||||
const columns = [
|
||||
{
|
||||
title: 'No.',
|
||||
key: 'name',
|
||||
key: 'no',
|
||||
width: 50,
|
||||
fixed: 'left'
|
||||
},
|
||||
@ -61,6 +58,7 @@ export default defineComponent({
|
||||
key: 'afterGpt',
|
||||
width: 200,
|
||||
fixed: 'left',
|
||||
className: 'empty-margin',
|
||||
render(row, index) {
|
||||
return h(DatatableAfterGpt, { initData: row, index: index })
|
||||
}
|
||||
@ -68,7 +66,7 @@ export default defineComponent({
|
||||
{
|
||||
title: '图片/视频',
|
||||
key: 'oldImage',
|
||||
className: 'space-row',
|
||||
className: 'empty-margin',
|
||||
width: 130,
|
||||
render(row, index) {
|
||||
return h(ManageBookOldImage, {
|
||||
@ -82,7 +80,7 @@ export default defineComponent({
|
||||
return h(DatatableHeaderGptPrompt)
|
||||
},
|
||||
key: 'gptPrompt',
|
||||
className: 'space-row',
|
||||
className: 'empty-margin',
|
||||
resizable: true,
|
||||
width: 400,
|
||||
minWidth: 400,
|
||||
@ -94,7 +92,10 @@ export default defineComponent({
|
||||
index: index
|
||||
})
|
||||
} else if (reverseManageStore.selectBook.type == BookType.SD_REVERSE) {
|
||||
return h('span', ['SD 反推这边不能用', index])
|
||||
return h(SDReversePrompt, {
|
||||
initData: row,
|
||||
index: index
|
||||
})
|
||||
} else if (reverseManageStore.selectBook.type == BookType.ORIGINAL) {
|
||||
return h('span', ['原创 这边暂时不能用', index])
|
||||
}
|
||||
@ -105,7 +106,7 @@ export default defineComponent({
|
||||
title(row) {
|
||||
return h(DatatableHeaderPrompt)
|
||||
},
|
||||
className: 'space-row',
|
||||
className: 'empty-margin',
|
||||
key: 'row1',
|
||||
render(row, index) {
|
||||
return h(DatatablePrompt, {
|
||||
@ -114,12 +115,21 @@ export default defineComponent({
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
// 参数
|
||||
title: '参数',
|
||||
key: 'parameter',
|
||||
width: '110',
|
||||
render(row, index) {
|
||||
return h(ODataTableAction, { initData: row, index: index })
|
||||
}
|
||||
},
|
||||
{
|
||||
title() {
|
||||
return h(DatatableHeaderImage)
|
||||
},
|
||||
width: 300,
|
||||
className: 'space-row',
|
||||
className: 'empty-margin',
|
||||
render(row, index) {
|
||||
return h(DatatableGenerateImage, {
|
||||
image_generate_category: 'mj',
|
||||
@ -130,17 +140,16 @@ export default defineComponent({
|
||||
},
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
calcHeight()
|
||||
]
|
||||
|
||||
onMounted(async () => {
|
||||
setTimeout(() => {
|
||||
let div = document.getElementsByClassName('space-row')
|
||||
let div = document.getElementsByClassName('empty-margin')
|
||||
for (let i = 0; i < div.length; i++) {
|
||||
div[i].style.padding = '0px 5px'
|
||||
}
|
||||
calcHeight()
|
||||
// getMaxHeight()
|
||||
}, 100)
|
||||
|
||||
// 数据加载完毕
|
||||
@ -152,22 +161,11 @@ export default defineComponent({
|
||||
}
|
||||
softwareStore.spin.spinning = false
|
||||
reverseManageStore.selectBookTaskDetail = detailRes.data
|
||||
})
|
||||
|
||||
function calcHeight() {
|
||||
})
|
||||
|
||||
function calcHeight() {
|
||||
// 视口的高度
|
||||
let height = window.innerHeight
|
||||
maxHeight.value = height - 120
|
||||
}
|
||||
|
||||
return {
|
||||
softwareStore,
|
||||
reverseManageStore,
|
||||
maxHeight,
|
||||
columns: createColumns(),
|
||||
datatable
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<DatatableGptPromptButton :initData="data" :index="index" />
|
||||
</div>
|
||||
<n-input
|
||||
type="textarea"
|
||||
size="tiny"
|
||||
v-model:value="data.gptPrompt"
|
||||
:autosize="{
|
||||
minRows: 6,
|
||||
maxRows: 6
|
||||
}"
|
||||
@input="InputDebounced"
|
||||
>
|
||||
</n-input>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useMessage, NButton, NInput, NSpin, NDropdown } from 'naive-ui'
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage'
|
||||
import DatatableGptPromptButton from './DatatableGptPromptButton.vue'
|
||||
import { debounce } from 'lodash'
|
||||
|
||||
let props = defineProps({
|
||||
initData: undefined,
|
||||
index: undefined
|
||||
})
|
||||
let data = ref(props.initData)
|
||||
let index = ref(props.index)
|
||||
let message = useMessage()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
onMounted(async () => {})
|
||||
|
||||
let InputDebounced = debounce(handleInput, 1000)
|
||||
async function handleInput(value) {
|
||||
let res = await window.db.UpdateBookTaskDetailData(data.value.id, {
|
||||
gptPrompt: value
|
||||
})
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<div style="height: 100%; display: flex; width: 100%; overflow: auto; padding: 0; margin: 0">
|
||||
<div>
|
||||
<div v-if="reverseManageStore.selectBook.type == bookType.MJ_REVERSE">
|
||||
<div
|
||||
v-if="
|
||||
reverseManageStore.selectBook.type == bookType.MJ_REVERSE ||
|
||||
reverseManageStore.selectBook.type == bookType.SD_REVERSE
|
||||
"
|
||||
>
|
||||
<div v-if="true" class="left_container" style="flex: 10; min-width: 500px">
|
||||
<ManageBookDetailButton />
|
||||
<ManageBookReverseTable style="margin-top: 5px" />
|
||||
@ -20,10 +25,10 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
|
||||
import { useMessage, NDivider } from 'naive-ui'
|
||||
import ManageBookDetailButton from './Components/ManageBookDetailButton.vue'
|
||||
import ManageBookDetailButton from './MJReverse/ManageBookDetailButton.vue'
|
||||
import ManageBookReverseTable from './MJReverse/ManageBookReverseTable.vue'
|
||||
import ManageBookShowLogger from './Components/ManageBook/ManageBookShowLogger.vue'
|
||||
import { useSoftwareStore } from '../../../../stores/software'
|
||||
@ -33,22 +38,51 @@ import { ResponseMessageType } from '../../../../define/enum/softwareEnum'
|
||||
import { useReverseManageStore } from '../../../../stores/reverseManage'
|
||||
import OriginalMainPage from './Original/OriginalMainPage.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
ManageBookDetailButton,
|
||||
ManageBookReverseTable,
|
||||
ManageBookShowLogger,
|
||||
NDivider,
|
||||
OriginalMainPage
|
||||
},
|
||||
let message = useMessage()
|
||||
let softwareStore = useSoftwareStore()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let bookType = ref(BookType)
|
||||
|
||||
setup() {
|
||||
// 原创SD生图返回的数据进行修改
|
||||
function OriginalSDImageResponseReturn(value) {
|
||||
console.log('OriginalSDImageResponseReturn', value)
|
||||
// 开始修改
|
||||
if (value.data) {
|
||||
let dataIndex = reverseManageStore.selectBookTaskDetail.findIndex(
|
||||
(item) => item.id == value.data.id
|
||||
)
|
||||
if (dataIndex >= 0) {
|
||||
reverseManageStore.selectBookTaskDetail[dataIndex].mjMessage = value.data
|
||||
reverseManageStore.selectBookTaskDetail[dataIndex].outImagePath = value.data.outImagePath
|
||||
reverseManageStore.selectBookTaskDetail[dataIndex].subImagePath = value.data.subImagePath
|
||||
}
|
||||
if (value.data.status == 'error') {
|
||||
message.error(value.data.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// 监听SD出图返回的数据
|
||||
window.api.setEventListen([DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN], (value) =>
|
||||
OriginalSDImageResponseReturn(value)
|
||||
)
|
||||
|
||||
// 监听D3出图返回的数据
|
||||
window.api.setEventListen([DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN], (value) => {
|
||||
OriginalSDImageResponseReturn(value)
|
||||
})
|
||||
|
||||
// 监听FLUX API 出图返回的数据
|
||||
window.api.setEventListen([DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN], (value) =>
|
||||
OriginalSDImageResponseReturn(value)
|
||||
)
|
||||
|
||||
// 监听FLUX FORGE 出图返回的数据
|
||||
window.api.setEventListen([DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN], (value) =>
|
||||
OriginalSDImageResponseReturn(value)
|
||||
)
|
||||
|
||||
let message = useMessage()
|
||||
let softwareStore = useSoftwareStore()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let bookType = ref(BookType)
|
||||
onMounted(async () => {
|
||||
// 监听提取文案的返回数据
|
||||
window.api.setEventListen([DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN], (value) => {
|
||||
if (value.code == 0) {
|
||||
@ -82,8 +116,15 @@ export default defineComponent({
|
||||
window.api.setEventListen([DEFINE_STRING.BOOK.REVERSE_PROMPT_RETURN], (value) => {
|
||||
if (value.code == 0) {
|
||||
errorResponse(value)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
if (value.type == ResponseMessageType.MJ_REVERSE) {
|
||||
MJReverseResponse(value)
|
||||
} else if (value.type == ResponseMessageType.SD_REVERSE) {
|
||||
SDReverseResponse(value)
|
||||
} else {
|
||||
console.error('错误的返回数据类型')
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
@ -110,9 +151,9 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function errorResponse(value) {
|
||||
function errorResponse(value) {
|
||||
switch (value.dialogType) {
|
||||
case DialogType.DIALOG:
|
||||
window.api.showGlobalMessageDialog(value)
|
||||
@ -127,17 +168,21 @@ export default defineComponent({
|
||||
window.api.showGlobalNotificationDialog(value)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
onUnmounted(() => {
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.REMOVE_WATERMARK_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.REVERSE_PROMPT_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.REVERSE_PROMPT_TRANSLATE_RETURN)
|
||||
})
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.SD_IMAGE_GENERATE_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.D3_IMAGE_GENERATE_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.FLUX_API_IMAGE_GENERATE_RETURN)
|
||||
window.api.removeEventListen(DEFINE_STRING.BOOK.FLUX_FORGE_IMAGE_GENERATE_RETURN)
|
||||
})
|
||||
|
||||
function MJImageResponse(value) {
|
||||
function MJImageResponse(value) {
|
||||
//开始修改
|
||||
if (value.data) {
|
||||
let dataIndex = reverseManageStore.selectBookTaskDetail.findIndex(
|
||||
@ -149,9 +194,9 @@ export default defineComponent({
|
||||
reverseManageStore.selectBookTaskDetail[dataIndex].subImagePath = value.data.subImagePath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 处理翻译返回的数据,主要是将翻译的数据放到对应的位置
|
||||
* @param value 返回的数据,数据结构如下
|
||||
* bookTaskDetailId: string // 分镜ID
|
||||
@ -161,7 +206,7 @@ export default defineComponent({
|
||||
* prompt: string // 英文数据
|
||||
* promptCN: string // 中文翻译数据(但是也可以存储英文)
|
||||
*/
|
||||
function ReversePromptTranslateResponse(value) {
|
||||
function ReversePromptTranslateResponse(value) {
|
||||
if (!value.data) {
|
||||
return
|
||||
}
|
||||
@ -191,9 +236,9 @@ export default defineComponent({
|
||||
} else {
|
||||
message.error('未知的翻译目标语言')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function GptPromptTranslateResponse(value) {
|
||||
function GptPromptTranslateResponse(value) {
|
||||
console.log('GptPromptTranslateResponse', value)
|
||||
if (!value.data) {
|
||||
return
|
||||
@ -207,12 +252,12 @@ export default defineComponent({
|
||||
reverseManageStore.selectBookTaskDetail[index].gptPrompt = value.data.prompt
|
||||
// 这边也要修改进度
|
||||
softwareStore.spin.tip = `翻译中 ... ${value.data.progress} / ${value.data.total}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* MJ反推返回数据
|
||||
*/
|
||||
function MJReverseResponse(value) {
|
||||
function MJReverseResponse(value) {
|
||||
if (value.data && value.data.progress == 100) {
|
||||
let dataIndex = reverseManageStore.selectBookTaskDetail.findIndex(
|
||||
(item) => item.id == value.data.id
|
||||
@ -224,11 +269,19 @@ export default defineComponent({
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { softwareStore, reverseManageStore, bookType }
|
||||
/**
|
||||
* SD反推返回数据
|
||||
*/
|
||||
function SDReverseResponse(value) {
|
||||
if (value.data) {
|
||||
let dataIndex = reverseManageStore.selectBookTaskDetail.findIndex((item) => item.id == value.id)
|
||||
if (dataIndex >= 0) {
|
||||
reverseManageStore.selectBookTaskDetail[dataIndex].gptPrompt = value.data
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
|
||||
@ -30,11 +30,29 @@
|
||||
@update:checked-row-keys="handleCheck"
|
||||
/>
|
||||
</div>
|
||||
<n-dropdown
|
||||
placement="bottom-start"
|
||||
trigger="manual"
|
||||
:x="x"
|
||||
:y="y"
|
||||
:options="options"
|
||||
:show="showDropdown"
|
||||
:on-clickoutside="onClickoutside"
|
||||
@select="handleSelect"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
|
||||
import { useMessage, useDialog, NButton, NDataTable, NIcon, NWatermark } from 'naive-ui'
|
||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, nextTick, h } from 'vue'
|
||||
import {
|
||||
useMessage,
|
||||
useDialog,
|
||||
NButton,
|
||||
NDataTable,
|
||||
NIcon,
|
||||
NDropdown,
|
||||
NPerformantEllipsis
|
||||
} from 'naive-ui'
|
||||
import { useReverseManageStore } from '../../../../stores/reverseManage'
|
||||
import { useSoftwareStore } from '../../../../stores/software'
|
||||
import { AddSharp } from '@vicons/ionicons5'
|
||||
@ -53,6 +71,12 @@ const router = useRouter()
|
||||
let message = useMessage()
|
||||
let columns = createColumns()
|
||||
const checkedRowKeysRef = ref([])
|
||||
let showDropdown = ref(false)
|
||||
let x = ref(0)
|
||||
let y = ref(0)
|
||||
let selectRow = ref(null)
|
||||
let options = []
|
||||
|
||||
onMounted(async () => {
|
||||
window.api.setEventListen([DEFINE_STRING.BOOK.MAIN_DATA_RETURN], (value) => {
|
||||
if (value.code == 0) {
|
||||
@ -107,39 +131,238 @@ function createColumns() {
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 215,
|
||||
fixed: 'right',
|
||||
title: '失败原因',
|
||||
key: 'errorMsg',
|
||||
width: 210,
|
||||
render(row, index) {
|
||||
return h(BookTaskListAction, {
|
||||
bookTask: row
|
||||
})
|
||||
return h(
|
||||
NPerformantEllipsis,
|
||||
{
|
||||
style: 'max-width: 200px'
|
||||
},
|
||||
row.errorMsg
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 高清数据
|
||||
*/
|
||||
function createOptions(row) {
|
||||
return [
|
||||
{
|
||||
label: `高清 ${row.name}`,
|
||||
key: 'hd'
|
||||
},
|
||||
{
|
||||
label: `生成草稿 ${row.name}`,
|
||||
key: 'draft'
|
||||
},
|
||||
{
|
||||
label: `合成视频 ${row.name}`,
|
||||
key: 'generateVideo'
|
||||
},
|
||||
{
|
||||
label: () => h('span', { style: { color: '#f4a534' } }, `重置 ${row.name}`),
|
||||
key: 'reset'
|
||||
},
|
||||
{
|
||||
label: () => h('span', { style: { color: 'red' } }, `删除 ${row.name}`),
|
||||
key: 'delete'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async function HDImage(value) {
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '正在进行高清检查。。。'
|
||||
let checkImage = await window.book.CheckImageFileSize(value.id, 10240, OperateBookType.BOOKTASK)
|
||||
softwareStore.spin.spinning = false
|
||||
if (checkImage.code == 0) {
|
||||
message.error(checkImage.message)
|
||||
return
|
||||
}
|
||||
// 高清检查完成
|
||||
if (checkImage.data && checkImage.data.length > 0) {
|
||||
// 出现了图片大的,用户判断是不是需要高清
|
||||
let msg = checkImage.data.map((item, index) => {
|
||||
return item.outImagePath + '\n'
|
||||
})
|
||||
dialog.warning({
|
||||
title: '高清警告',
|
||||
content: '下面的图片大于10MB,是否继续高清?' + '\n' + msg,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
maskClosable: false,
|
||||
onPositiveClick: async () => {
|
||||
alert('确认高清')
|
||||
await hdImageFunc(value.id, OperateBookType.BOOKTASK)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 没有就直接高清
|
||||
await hdImageFunc(value.id, OperateBookType.BOOKTASK)
|
||||
}
|
||||
}
|
||||
|
||||
async function hdImageFunc(id, operateBookType) {
|
||||
let da = dialog.warning({
|
||||
title: '高清提示',
|
||||
content: `是否确认高清,当前的高清倍数为 ${softwareStore.globalSetting.hdScale} 倍,是否继续?`,
|
||||
positiveText: '继续',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
da.destroy()
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '正在高清中。。。'
|
||||
let res = await window.book.HDImage(id, 4, operateBookType)
|
||||
let res = await window.book.HDImage(
|
||||
id,
|
||||
softwareStore.globalSetting.hdScale ?? 2,
|
||||
operateBookType
|
||||
)
|
||||
softwareStore.spin.spinning = false
|
||||
if (res.code == 1) {
|
||||
message.success('高清成功')
|
||||
} else {
|
||||
message.error(res.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成草稿
|
||||
*/
|
||||
async function ClipDraft(value) {
|
||||
dialog.info({
|
||||
closeOnEsc: false,
|
||||
title: `生成草稿前检查 ${value.name}`,
|
||||
maskClosable: false,
|
||||
content: () =>
|
||||
h(ManageBookTaskGenerateInformation, {
|
||||
bookTask: value,
|
||||
type: 'bookTask',
|
||||
optionType: 'draft'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 合成视频
|
||||
*/
|
||||
async function GenerateVideo(value) {
|
||||
dialog.info({
|
||||
closeOnEsc: false,
|
||||
title: `合成视频前检查 ${value.name}`,
|
||||
maskClosable: false,
|
||||
content: () =>
|
||||
h(ManageBookTaskGenerateInformation, {
|
||||
bookTask: value,
|
||||
type: 'bookTask',
|
||||
optionType: 'video'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置小说数据
|
||||
* @param e
|
||||
*/
|
||||
async function ReSetBookTaskDetail(value) {
|
||||
dialog.warning({
|
||||
title: '重置小说任务',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
content:
|
||||
'确定要重置小说任务吗?该操作不可逆,会将对应的批次任务的数据清空,分镜数据全部删除,请谨慎操作!',
|
||||
onPositiveClick: async () => {
|
||||
let res = await window.book.ReSetBookTask(value.id, OperateBookType.BOOKTASK)
|
||||
if (res.code == 1) {
|
||||
message.success('重置小说任务成功')
|
||||
} else {
|
||||
message.error(res.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除小说任务
|
||||
*/
|
||||
async function DeleteBookTask(value) {
|
||||
dialog.warning({
|
||||
title: '删除小说任务',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
content:
|
||||
'确定要删除小说任务吗?该操作不可逆,会将对应的批次任务数据和分镜数据全部删除,请谨慎操作!',
|
||||
onPositiveClick: async () => {
|
||||
let res = await window.book.DeleteBookTask(value.id, OperateBookType.BOOKTASK)
|
||||
if (res.code == 1) {
|
||||
// 加载任务列表
|
||||
let bookTaskRes = await reverseManageStore.GetBookTaskDataFromDB({
|
||||
bookId: reverseManageStore.selectBook.id
|
||||
})
|
||||
softwareStore.spin.spinning = false
|
||||
if (bookTaskRes.code == 0) {
|
||||
message.error(bookTaskRes.message)
|
||||
return
|
||||
}
|
||||
// 删除成功
|
||||
message.success('删除小说任务并刷新数据成功')
|
||||
} else {
|
||||
message.error(res.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function handleSelect(key) {
|
||||
showDropdown.value = false
|
||||
switch (key) {
|
||||
case 'hd':
|
||||
await HDImage(selectRow.value)
|
||||
break
|
||||
case 'draft':
|
||||
await ClipDraft(selectRow.value)
|
||||
break
|
||||
case 'generateVideo':
|
||||
await GenerateVideo(selectRow.value)
|
||||
break
|
||||
case 'reset':
|
||||
await ReSetBookTaskDetail(selectRow.value)
|
||||
break
|
||||
case 'delete':
|
||||
await DeleteBookTask(selectRow.value)
|
||||
break
|
||||
default:
|
||||
message.error('未知的下拉值')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function onClickoutside() {
|
||||
showDropdown.value = false
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 高清数据
|
||||
// */
|
||||
// async function hdImageFunc2(id, operateBookType) {
|
||||
// softwareStore.spin.spinning = true
|
||||
// softwareStore.spin.tip = '正在高清中。。。'
|
||||
// let res = await window.book.HDImage(id, 4, operateBookType)
|
||||
// softwareStore.spin.spinning = false
|
||||
// if (res.code == 1) {
|
||||
// message.success('高清成功')
|
||||
// } else {
|
||||
// message.error(res.message)
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 一键高清全部
|
||||
*/
|
||||
async function HDImageAll() {
|
||||
console.log('一键高清', checkedRowKeysRef.value)
|
||||
if (checkedRowKeysRef.value.length == 0) {
|
||||
message.error('请选择要高清的数据')
|
||||
return
|
||||
@ -214,21 +437,99 @@ async function DraftAll() {
|
||||
* 一键生成视频
|
||||
*/
|
||||
async function VideoAll() {
|
||||
alert('视频全部')
|
||||
if (checkedRowKeysRef.value.length == 0) {
|
||||
message.error('请选择要生成草稿的任务')
|
||||
return
|
||||
}
|
||||
// 草稿弹窗
|
||||
dialog.info({
|
||||
closeOnEsc: false,
|
||||
title: `生成草稿前检查 ${reverseManageStore.selectBook.name}`,
|
||||
maskClosable: false,
|
||||
content: () =>
|
||||
h(ManageBookTaskGenerateInformation, {
|
||||
bookTask: reverseManageStore.selectBook,
|
||||
type: 'book',
|
||||
selectBookTask: [...checkedRowKeysRef.value],
|
||||
optionType: 'video'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 一键重置
|
||||
*/
|
||||
async function ResetAll() {
|
||||
alert('重置全部')
|
||||
if (checkedRowKeysRef.value.length == 0) {
|
||||
message.error('请选择要高清的数据')
|
||||
return
|
||||
}
|
||||
dialog.warning({
|
||||
title: '重置选中小说任务',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
content:
|
||||
'确定要重置选中的小说任务吗?该操作不可逆,会将对应的批次任务的数据清空,分镜数据全部删除,请谨慎操作!',
|
||||
onPositiveClick: async () => {
|
||||
let res = await window.book.ReSetBookTask(
|
||||
[...checkedRowKeysRef.value],
|
||||
OperateBookType.ASSIGNBOOKTASK
|
||||
)
|
||||
if (res.code == 1) {
|
||||
// 加载任务列表
|
||||
let bookTaskRes = await reverseManageStore.GetBookTaskDataFromDB({
|
||||
bookId: reverseManageStore.selectBook.id
|
||||
})
|
||||
softwareStore.spin.spinning = false
|
||||
if (bookTaskRes.code == 0) {
|
||||
message.error(bookTaskRes.message)
|
||||
return
|
||||
}
|
||||
message.success('重置小说任务成功')
|
||||
} else {
|
||||
message.error(res.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 一键删除
|
||||
*/
|
||||
async function DeleteAll() {
|
||||
alert('删除全部')
|
||||
if (checkedRowKeysRef.value.length == 0) {
|
||||
message.error('请选择要高清的数据')
|
||||
return
|
||||
}
|
||||
|
||||
dialog.warning({
|
||||
title: '删除所选小说任务',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
content:
|
||||
'确定要删除选中的小说任务吗?该操作不可逆,会将对应的批次任务数据和分镜数据全部删除,请谨慎操作!',
|
||||
onPositiveClick: async () => {
|
||||
let res = await window.book.DeleteBookTask(
|
||||
[...checkedRowKeysRef.value],
|
||||
OperateBookType.ASSIGNBOOKTASK
|
||||
)
|
||||
if (res.code == 1) {
|
||||
// 加载任务列表
|
||||
let bookTaskRes = await reverseManageStore.GetBookTaskDataFromDB({
|
||||
bookId: reverseManageStore.selectBook.id
|
||||
})
|
||||
softwareStore.spin.spinning = false
|
||||
if (bookTaskRes.code == 0) {
|
||||
message.error(bookTaskRes.message)
|
||||
return
|
||||
}
|
||||
// 删除成功
|
||||
message.success('删除小说任务并刷新数据成功')
|
||||
} else {
|
||||
message.error(res.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const renderIcon = () => {
|
||||
return h(NIcon, null, {
|
||||
@ -245,6 +546,17 @@ const rowProps = (row) => {
|
||||
softwareStore.spin.tip = '正在加载数据'
|
||||
// 跳转
|
||||
router.push({ name: 'manage_book', params: { id: row.id } })
|
||||
},
|
||||
onContextmenu: (e) => {
|
||||
e.preventDefault()
|
||||
selectRow.value = row
|
||||
showDropdown.value = false
|
||||
options = createOptions(row)
|
||||
nextTick().then(() => {
|
||||
showDropdown.value = true
|
||||
x.value = e.clientX
|
||||
y.value = e.clientY
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +87,7 @@ import { isEmpty } from 'lodash'
|
||||
import { TranslateType } from '../../../../../define/enum/translate'
|
||||
import { DEFINE_STRING } from '../../../../../define/define_string'
|
||||
import FindReplaceRound from '../../Icon/FindReplaceRound.vue'
|
||||
import DatatableHeaderAfterGptSelectAndReplace from '../Components/DatatableHeaderAfterGptSelectAndReplace.vue'
|
||||
import DatatableHeaderAfterGptSelectAndReplace from '../MJReverse/DatatableHeaderAfterGptSelectAndReplace.vue'
|
||||
import { BookRepalceDataType } from '../../../../../define/enum/bookEnum'
|
||||
|
||||
let softwareStore = useSoftwareStore()
|
||||
|
||||
@ -60,7 +60,7 @@ import { useSoftwareStore } from '../../../../../stores/software'
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage'
|
||||
import { BookRepalceDataType, OperateBookType } from '../../../../../define/enum/bookEnum'
|
||||
import FindReplaceRound from '../../Icon/FindReplaceRound.vue'
|
||||
import DatatableHeaderAfterGptSelectAndReplace from '../Components/DatatableHeaderAfterGptSelectAndReplace.vue'
|
||||
import DatatableHeaderAfterGptSelectAndReplace from '../MJReverse/DatatableHeaderAfterGptSelectAndReplace.vue'
|
||||
let message = useMessage()
|
||||
let dialog = useDialog()
|
||||
let softwareStore = useSoftwareStore()
|
||||
|
||||
@ -73,7 +73,7 @@ import { useReverseManageStore } from '../../../../../stores/reverseManage'
|
||||
import ImportWordAndSrt from '../Components/ImportWord/ImportWordAndSrt.vue'
|
||||
import CharacterAnalyze from '../Components/PresetLibrary/CharacterAnalyze.vue'
|
||||
import PromptSetting from '../../Original/Components/PromptSetting.vue'
|
||||
import MonitorStatus from '../Components/MonitorStatus.vue'
|
||||
import MonitorStatus from '../MJReverse/MonitorStatus.vue'
|
||||
import {
|
||||
BookBackTaskType,
|
||||
BookImageCategory,
|
||||
|
||||
@ -20,8 +20,8 @@
|
||||
<script setup>
|
||||
import { ref, h, onMounted, watch } from 'vue'
|
||||
import { NDataTable, useMessage } from 'naive-ui'
|
||||
import DatatableHeaderAfterGpt from '../Components/DatatableHeaderAfterGpt.vue'
|
||||
import DatatableAfterGpt from '../Components/DatatableAfterGpt.vue'
|
||||
import DatatableHeaderAfterGpt from '../MJReverse/DatatableHeaderAfterGpt.vue'
|
||||
import DatatableAfterGpt from '../MJReverse/DatatableAfterGpt.vue'
|
||||
import ODatatableHeaderCharacterAndScene from './ODataTableHeaderCharacterAndScene.vue'
|
||||
import ODatatableCharacterAndScene from './ODataTableCharacterAndScene.vue'
|
||||
import ODataTableGptPrompt from './ODataTableGptPrompt.vue'
|
||||
@ -29,8 +29,8 @@ import ODataTableHeaderGptPrompt from './ODataTableHeaderGptPrompt.vue'
|
||||
import ODataTableHeaderPrompt from './ODatatableHeaderPrompt.vue'
|
||||
import ODatatablePrompt from './ODatatablePrompt.vue'
|
||||
import ODataTableAction from './ODataTableAction.vue'
|
||||
import DatatableHeaderGenerateImage from '../Components/DatatableHeaderGenerateImage.vue'
|
||||
import DatatableGenerateImage from '../Components/DatatableGenerateImage.vue'
|
||||
import DatatableHeaderGenerateImage from '../MJReverse/DatatableHeaderGenerateImage.vue'
|
||||
import DatatableGenerateImage from '../MJReverse/DatatableGenerateImage.vue'
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage'
|
||||
import { useSoftwareStore } from '../../../../../stores/software'
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
|
||||
@ -171,7 +171,6 @@ async function ManageAccount(remote = null) {
|
||||
settingStore.actionRemoteMJ = remote
|
||||
}
|
||||
|
||||
// 弹窗,管理不想要的值
|
||||
let dW = 800
|
||||
let dH = 600
|
||||
dialog.create({
|
||||
|
||||
@ -509,7 +509,6 @@ async function openGptBuyUrl() {
|
||||
* 添加多个账号,这样可以同时跑多个账号
|
||||
*/
|
||||
async function AddMultiMjAccount() {
|
||||
// 弹窗,管理不想要的值
|
||||
let dW = window.innerWidth * 0.9
|
||||
let dH = window.innerHeight * 0.9
|
||||
dialog.create({
|
||||
|
||||
@ -468,7 +468,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
// 开始处理
|
||||
// 弹窗,管理不想要的值
|
||||
dp = dialog.create({
|
||||
name: 'progressDialog',
|
||||
showIcon: false,
|
||||
|
||||
@ -59,6 +59,31 @@ export const useReverseManageStore = defineStore('reverseManage', {
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
||||
//#region 更新小说批次任务数据
|
||||
/** 更新小说批次任务数据 */
|
||||
UpdatedBookTaskData(bookTaskId: string | string[], data: Book.SelectBookTask) {
|
||||
if (Array.isArray(bookTaskId)) {
|
||||
bookTaskId.forEach(item => {
|
||||
let findIndex = this.bookTaskData.findIndex((t: Book.SelectBookTask) => t.id === item)
|
||||
if (findIndex != -1) {
|
||||
for (let key in data) {
|
||||
this.bookTaskData[findIndex][key] = data[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
let findIndex = this.bookTaskData.findIndex((t: Book.SelectBookTask) => t.id === bookTaskId)
|
||||
if (findIndex != -1) {
|
||||
for (let key in data) {
|
||||
this.bookTaskData[findIndex][key] = data[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
//#endregion
|
||||
|
||||
|
||||
/**
|
||||
* 获取小说的数据
|
||||
* @param {*} condition
|
||||
@ -238,7 +263,8 @@ export const useReverseManageStore = defineStore('reverseManage', {
|
||||
return {
|
||||
...item,
|
||||
outImagePath: item.outImagePath ? item.outImagePath + '?t=' + new Date().getTime() : undefined,
|
||||
subImagePath: item.subImagePath ? item.subImagePath.map(item => item + '?t=' + new Date().getTime()) : []
|
||||
subImagePath: item.subImagePath ? item.subImagePath.map(item => item + '?t=' + new Date().getTime()) : [],
|
||||
oldImage: item.oldImage ? item.oldImage + '?t=' + new Date().getTime() : undefined
|
||||
}
|
||||
})
|
||||
// 这边开始修改数据,
|
||||
|
||||
@ -6,7 +6,11 @@
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"target": "es2021", // 你可以设置目标版本
|
||||
"target": "es2021", // 你可以设置目标版本,
|
||||
"baseUrl": ".", // 设置模块解析的根目录为项目根目录
|
||||
"paths": {
|
||||
"@/*": ["src/*"] // 使用 @ 作为 src 目录的别名
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user