v 3.4.5 迁移图转视频 视频多语系 英文和中文
This commit is contained in:
parent
2182c1a36e
commit
8bc60256ba
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
|
.idea
|
||||||
out
|
out
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.eslintcache
|
.eslintcache
|
||||||
@ -8,3 +9,4 @@ resources/logger
|
|||||||
resources/project
|
resources/project
|
||||||
Database
|
Database
|
||||||
build
|
build
|
||||||
|
src/renderer/src/components/Original/MainHome/OriginalTaskCard.vue
|
||||||
|
|||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||||
},
|
},
|
||||||
"[json]": {
|
"[json]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "laitool-pro",
|
"name": "laitool-pro",
|
||||||
"productName": "来推 Pro",
|
"productName": "来推 Pro",
|
||||||
"version": "v3.4.3",
|
"version": "v3.4.5",
|
||||||
"description": "A desktop application for AI image generation and processing, built with Electron and Vue 3.",
|
"description": "来推 Pro - 一款集音频处理、文案生成、图片生成、视频生成等功能于一体的多合一AI工具软件。",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "xiangbei",
|
"author": "xiangbei",
|
||||||
"homepage": "https://electron-vite.org",
|
"homepage": "https://electron-vite.org",
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { t } from '@/i18n'
|
||||||
import { escapeRegExp, isEmpty } from 'lodash'
|
import { escapeRegExp, isEmpty } from 'lodash'
|
||||||
//#region 检查字符串中是不是包含中文或者标点符号
|
//#region 检查字符串中是不是包含中文或者标点符号
|
||||||
/**
|
/**
|
||||||
@ -35,15 +36,21 @@ export async function RetryWithBackoff<T>(
|
|||||||
// 这边记下日志吧
|
// 这边记下日志吧
|
||||||
global.logger.error(
|
global.logger.error(
|
||||||
fn.name + '_RetryWithBackoff',
|
fn.name + '_RetryWithBackoff',
|
||||||
`第 ${attempts} 请求失败,开始下一次重试,失败信息如下:` + error.toString()
|
t("第 {attempts} 请求失败,开始下一次重试,失败信息如下,{error}", {
|
||||||
|
attempts,
|
||||||
|
error: error.message
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (attempts >= retries) {
|
if (attempts >= retries) {
|
||||||
throw new Error(`失败次数超过 ${retries} 错误信息如下: ${error.message}`)
|
throw new Error(t("失败次数超过 {retries} 错误信息如下,{error}", {
|
||||||
|
retries,
|
||||||
|
error: error.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
await new Promise((resolve) => setTimeout(resolve, delay))
|
await new Promise((resolve) => setTimeout(resolve, delay))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error('所有重试失败') // 理论上不会到达这里
|
throw new Error(t('所有重试失败')) // 理论上不会到达这里
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
@ -122,11 +129,13 @@ export function ReplaceSubstrings(
|
|||||||
*/
|
*/
|
||||||
export function GetBaseUrl(url: string): string {
|
export function GetBaseUrl(url: string): string {
|
||||||
if (isEmpty(url)) {
|
if (isEmpty(url)) {
|
||||||
throw new Error('url不能为空')
|
throw new Error(t("{data} 不能为空", {
|
||||||
|
data: 'url'
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
// 判断是不是一个合法的url
|
// 判断是不是一个合法的url
|
||||||
if (!url.startsWith('http')) {
|
if (!url.startsWith('http')) {
|
||||||
throw new Error('一个合法的url请求地址')
|
throw new Error(t('不是一个合法的url地址'))
|
||||||
}
|
}
|
||||||
const parsedUrl = new URL(url)
|
const parsedUrl = new URL(url)
|
||||||
return `${parsedUrl.protocol}//${parsedUrl.host}`
|
return `${parsedUrl.protocol}//${parsedUrl.host}`
|
||||||
@ -147,7 +156,7 @@ export async function DownloadFile(url: string, localPath?: string): Promise<voi
|
|||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
|
|
||||||
if (!response.body) {
|
if (!response.body) {
|
||||||
throw new Error('浏览器不支持流式下载')
|
throw new Error(t("浏览器不支持流式下载"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const reader = response.body.getReader()
|
const reader = response.body.getReader()
|
||||||
@ -192,7 +201,9 @@ export async function DownloadFile(url: string, localPath?: string): Promise<voi
|
|||||||
if (res.statusCode === 200) {
|
if (res.statusCode === 200) {
|
||||||
resolve(res)
|
resolve(res)
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(`请求失败,状态码:${res.statusCode}`))
|
reject(new Error(t("请求失败,状态码:{statusCode}", {
|
||||||
|
statusCode: res.statusCode
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on('error', reject)
|
.on('error', reject)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { isEmpty } from 'lodash'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import util from 'util'
|
import util from 'util'
|
||||||
import { exec } from 'child_process'
|
import { exec } from 'child_process'
|
||||||
|
import { t } from '@/i18n'
|
||||||
const execAsync = util.promisify(exec)
|
const execAsync = util.promisify(exec)
|
||||||
const fspromises = fs.promises
|
const fspromises = fs.promises
|
||||||
|
|
||||||
@ -59,7 +60,9 @@ export async function DeleteFolderAllFile(
|
|||||||
try {
|
try {
|
||||||
let folderIsExist = await CheckFileOrDirExist(folderPath)
|
let folderIsExist = await CheckFileOrDirExist(folderPath)
|
||||||
if (!folderIsExist) {
|
if (!folderIsExist) {
|
||||||
throw new Error('目的文件夹不存在,' + folderPath)
|
throw new Error(t("目的文件/文件夹不存在,{data}", {
|
||||||
|
data: folderPath
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
// 开始删除
|
// 开始删除
|
||||||
let files = await fspromises.readdir(folderPath)
|
let files = await fspromises.readdir(folderPath)
|
||||||
@ -94,14 +97,18 @@ export async function CopyFileOrFolder(source, target, checkParent = false) {
|
|||||||
try {
|
try {
|
||||||
// 判断源文件或文件夹是不是存在
|
// 判断源文件或文件夹是不是存在
|
||||||
if (!(await CheckFileOrDirExist(source))) {
|
if (!(await CheckFileOrDirExist(source))) {
|
||||||
throw new Error(`源文件或文件夹不存在: ${source}`)
|
throw new Error(t("源文件或文件夹不存在,{data}", {
|
||||||
|
data: source
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
// 判断父文件夹是否存在,不存在创建
|
// 判断父文件夹是否存在,不存在创建
|
||||||
const parent_path = path.dirname(target)
|
const parent_path = path.dirname(target)
|
||||||
let parentIsExist = await CheckFileOrDirExist(parent_path)
|
let parentIsExist = await CheckFileOrDirExist(parent_path)
|
||||||
if (!parentIsExist) {
|
if (!parentIsExist) {
|
||||||
if (checkParent) {
|
if (checkParent) {
|
||||||
throw new Error(`目的文件或文件夹的父文件夹不存在: ${parent_path}`)
|
throw new Error(t("目的文件或文件夹的父文件夹不存在,{data}", {
|
||||||
|
data: parent_path
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
await fspromises.mkdir(parent_path, { recursive: true })
|
await fspromises.mkdir(parent_path, { recursive: true })
|
||||||
}
|
}
|
||||||
@ -147,7 +154,7 @@ export async function IsDirectory(path) {
|
|||||||
const stat = await fspromises.stat(path)
|
const stat = await fspromises.stat(path)
|
||||||
return stat.isDirectory()
|
return stat.isDirectory()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`获取文件夹信息失败: ${path}`)
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -191,7 +198,7 @@ export async function GetFilesWithExtensions(
|
|||||||
try {
|
try {
|
||||||
// 判断当前是不是文件夹
|
// 判断当前是不是文件夹
|
||||||
if (!(await IsDirectory(folderPath))) {
|
if (!(await IsDirectory(folderPath))) {
|
||||||
throw new Error('输入的不是有效的文件夹地址')
|
throw new Error(t('输入的不是有效的文件夹地址'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let entries = await fspromises.readdir(folderPath, { withFileTypes: true })
|
let entries = await fspromises.readdir(folderPath, { withFileTypes: true })
|
||||||
@ -238,7 +245,9 @@ export async function GetFilesWithExtensions(
|
|||||||
export async function GetFileSize(filePath: string): Promise<number> {
|
export async function GetFileSize(filePath: string): Promise<number> {
|
||||||
try {
|
try {
|
||||||
if (!(await CheckFileOrDirExist(filePath))) {
|
if (!(await CheckFileOrDirExist(filePath))) {
|
||||||
throw new Error('获取文件大小,指定的文件不存在')
|
throw new Error(t("目的文件/文件夹不存在,{data}", {
|
||||||
|
data: filePath
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
const stats = await fspromises.stat(filePath)
|
const stats = await fspromises.stat(filePath)
|
||||||
return stats.size / 1024
|
return stats.size / 1024
|
||||||
@ -364,7 +373,7 @@ export async function DownloadImageFromUrl(
|
|||||||
|
|
||||||
// 验证下载的数据是否有效
|
// 验证下载的数据是否有效
|
||||||
if (buffer.length === 0) {
|
if (buffer.length === 0) {
|
||||||
throw new Error('下载的文件为空')
|
throw new Error(t('下载的文件为空'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将图片数据写入本地文件
|
// 将图片数据写入本地文件
|
||||||
@ -373,7 +382,7 @@ export async function DownloadImageFromUrl(
|
|||||||
console.log(`图片下载成功: ${localPath} (大小: ${(buffer.length / 1024).toFixed(2)} KB)`)
|
console.log(`图片下载成功: ${localPath} (大小: ${(buffer.length / 1024).toFixed(2)} KB)`)
|
||||||
return localPath
|
return localPath
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
lastError = error instanceof Error ? error : new Error('未知错误')
|
lastError = error instanceof Error ? error : new Error(t('未知错误'))
|
||||||
|
|
||||||
console.error(`第${attempt}次下载失败:`, lastError.message)
|
console.error(`第${attempt}次下载失败:`, lastError.message)
|
||||||
|
|
||||||
@ -389,15 +398,28 @@ export async function DownloadImageFromUrl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 所有重试都失败了,抛出最后一个错误
|
// 所有重试都失败了,抛出最后一个错误
|
||||||
const errorMessage = lastError?.message || '未知错误'
|
const errorMessage = lastError?.message || t('未知错误')
|
||||||
|
|
||||||
if (errorMessage.includes('timeout') || errorMessage.includes('Timeout')) {
|
if (errorMessage.includes('timeout') || errorMessage.includes('Timeout')) {
|
||||||
throw new Error(`下载图片超时 (${timeout / 1000}秒),已重试${maxRetries}次: ${errorMessage}`)
|
throw new Error(t("下载图片超时 ({timeout}秒),已重试{maxRetries}次,失败信息:{errorMessage}", {
|
||||||
|
timeout: timeout / 1000,
|
||||||
|
maxRetries,
|
||||||
|
errorMessage
|
||||||
|
}))
|
||||||
} else if (errorMessage.includes('ENOTFOUND') || errorMessage.includes('ECONNREFUSED')) {
|
} else if (errorMessage.includes('ENOTFOUND') || errorMessage.includes('ECONNREFUSED')) {
|
||||||
throw new Error(`网络连接失败,无法访问图片地址,已重试${maxRetries}次: ${errorMessage}`)
|
throw new Error(t("网络连接失败,无法访问图片地址,已重试 {maxRetries}次,失败信息:{errorMessage}", {
|
||||||
|
maxRetries,
|
||||||
|
errorMessage
|
||||||
|
}))
|
||||||
} else if (errorMessage.includes('Connect Timeout Error')) {
|
} else if (errorMessage.includes('Connect Timeout Error')) {
|
||||||
throw new Error(`连接超时,服务器响应缓慢,已重试${maxRetries}次: ${errorMessage}`)
|
throw new Error(t("连接超时,服务器响应缓慢,已重试{maxRetries}次,失败信息:{errorMessage}", {
|
||||||
|
maxRetries,
|
||||||
|
errorMessage
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`下载图片失败,已重试${maxRetries}次: ${errorMessage}`)
|
throw new Error(t("下载图片失败,已重试{maxRetries}次,失败信息: ${errorMessage}", {
|
||||||
|
maxRetries,
|
||||||
|
errorMessage
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import sharp from 'sharp'
|
|||||||
import { CheckFileOrDirExist, CheckFolderExistsOrCreate } from './file'
|
import { CheckFileOrDirExist, CheckFolderExistsOrCreate } from './file'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import https from 'https'
|
import https from 'https'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将指定的图片的尺寸修改,返回修改后的图片数据(base64或buffer)
|
* 将指定的图片的尺寸修改,返回修改后的图片数据(base64或buffer)
|
||||||
@ -21,17 +22,17 @@ export async function ResizeImage(
|
|||||||
try {
|
try {
|
||||||
// 检查 type 参数
|
// 检查 type 参数
|
||||||
if (type !== 'base64' && type !== 'buffer') {
|
if (type !== 'base64' && type !== 'buffer') {
|
||||||
throw new Error('type 参数必须是 "base64" 或 "buffer"')
|
throw new Error(t('未知类型'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是不是图片文件
|
// 判断是不是图片文件
|
||||||
if (!image_path.match(/\.(jpg|jpeg|png)$/)) {
|
if (!image_path.match(/\.(jpg|jpeg|png)$/)) {
|
||||||
throw new Error('输入的文件地址不是图片文件地址')
|
throw new Error(t("输入的文件地址不是图片文件地址,支持jpg、jpeg、png"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断文件是否存在
|
// 判断文件是否存在
|
||||||
if (!(await CheckFileOrDirExist(image_path))) {
|
if (!(await CheckFileOrDirExist(image_path))) {
|
||||||
throw new Error('文件不存在')
|
throw new Error(t('文件不存在'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改图片尺寸
|
// 修改图片尺寸
|
||||||
@ -58,12 +59,12 @@ export async function GetImageSize(image_path: string) {
|
|||||||
try {
|
try {
|
||||||
// 判断文件是否存在
|
// 判断文件是否存在
|
||||||
if (!(await CheckFileOrDirExist(image_path))) {
|
if (!(await CheckFileOrDirExist(image_path))) {
|
||||||
throw new Error('文件不存在')
|
throw new Error(t('文件不存在'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是不是图片文件
|
// 判断是不是图片文件
|
||||||
if (!image_path.match(/\.(jpg|jpeg|png)$/)) {
|
if (!image_path.match(/\.(jpg|jpeg|png)$/)) {
|
||||||
throw new Error('输入的文件地址不是图片文件地址')
|
throw new Error(t("输入的文件地址不是图片文件地址,支持jpg、jpeg、png"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取图片的宽高
|
// 获取图片的宽高
|
||||||
@ -146,7 +147,7 @@ export function GetImageTypeFromBase64(base64String: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('解析base64图片类型时出错:', error)
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
return extension
|
return extension
|
||||||
@ -159,7 +160,9 @@ export function GetImageTypeFromBase64(base64String: string): string {
|
|||||||
*/
|
*/
|
||||||
export function GetImageBase64(url: string): Promise<string> {
|
export function GetImageBase64(url: string): Promise<string> {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return Promise.reject('URL不能为空')
|
return Promise.reject(t("{data} 不能为空", {
|
||||||
|
data: 'URL'
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -242,7 +245,7 @@ export async function ProcessImage(
|
|||||||
// 获取图片的元数据
|
// 获取图片的元数据
|
||||||
const { width, height } = await image.metadata()
|
const { width, height } = await image.metadata()
|
||||||
if (!width || !height) {
|
if (!width || !height) {
|
||||||
throw new Error('获取图片的宽高失败')
|
throw new Error(t('获取图片的宽高失败'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const whiteBackground = await sharp()
|
const whiteBackground = await sharp()
|
||||||
@ -316,7 +319,9 @@ export async function Base64ToFile(base64: string, outFilePath: string): Promise
|
|||||||
await fs.promises.writeFile(outFilePath, dataBuffer)
|
await fs.promises.writeFile(outFilePath, dataBuffer)
|
||||||
// await this.tools.writeArrayToFile(dataBuffer, out_file);
|
// await this.tools.writeArrayToFile(dataBuffer, out_file);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw new Error('将base64转换为文件失败,失败信息如下:' + error.toString())
|
throw new Error(t("将base64转换为文件失败,{error}", {
|
||||||
|
error: (error as Error).toString()
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,7 +345,7 @@ export async function ImageSplit(
|
|||||||
// 获取图片元数据
|
// 获取图片元数据
|
||||||
const metadata = await sharp(inputPath).metadata()
|
const metadata = await sharp(inputPath).metadata()
|
||||||
if (!metadata.height || !metadata.width) {
|
if (!metadata.height || !metadata.width) {
|
||||||
throw new Error('获取图片的宽高失败')
|
throw new Error(t('获取图片的宽高失败'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算每个分块的宽高,使用Math.floor确保不超出边界
|
// 计算每个分块的宽高,使用Math.floor确保不超出边界
|
||||||
|
|||||||
@ -29,7 +29,8 @@ export class Logger {
|
|||||||
datePattern: 'YYYY-MM-DD',
|
datePattern: 'YYYY-MM-DD',
|
||||||
zippedArchive: true,
|
zippedArchive: true,
|
||||||
maxSize: '10m',
|
maxSize: '10m',
|
||||||
maxFiles: '14d'
|
maxFiles: '14d',
|
||||||
|
options: { flags: 'a', encoding: 'utf8' }
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { t } from "@/i18n"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验是不是可以进行JSON解析
|
* 校验是不是可以进行JSON解析
|
||||||
* @param str 要解析的字符串
|
* @param str 要解析的字符串
|
||||||
@ -21,12 +23,12 @@ export function ValidateJson(str: string): boolean {
|
|||||||
export function ValidateJsonAndParse<T>(str: string): T {
|
export function ValidateJsonAndParse<T>(str: string): T {
|
||||||
try {
|
try {
|
||||||
if (str == null) {
|
if (str == null) {
|
||||||
throw new Error('数据不能为空')
|
throw new Error(t("数据不能为空"))
|
||||||
}
|
}
|
||||||
let res = JSON.parse(str) as T
|
let res = JSON.parse(str) as T
|
||||||
return res
|
return res
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error('数据解析失败,请检查数据格式')
|
throw new Error(t('数据解析失败,请检查数据格式'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,9 +50,11 @@ interface ValidationErrors {
|
|||||||
export function ValidateErrorString(errors: ValidationErrors): string {
|
export function ValidateErrorString(errors: ValidationErrors): string {
|
||||||
const errorMessages = Object.values(errors)
|
const errorMessages = Object.values(errors)
|
||||||
.map((err) => {
|
.map((err) => {
|
||||||
return err[0]?.message || '验证错误'
|
return err[0]?.message || t('验证错误')
|
||||||
})
|
})
|
||||||
.join(', ')
|
.join(', ')
|
||||||
let res = '请修正以下错误: ' + (errorMessages || errors.message)
|
let res = t("请修正以下错误,{error}", {
|
||||||
|
error: errorMessages || errors.message
|
||||||
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { t } from "@/i18n"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按字符数对word数组进行重新分组
|
* 按字符数对word数组进行重新分组
|
||||||
*
|
*
|
||||||
@ -135,6 +137,8 @@ export function splitTextByCustomDelimiters(oldText: string, formatSpecialChars:
|
|||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw new Error('格式化文本失败,失败信息如下:' + error.message)
|
throw new Error(t("格式化文本失败,{error}", {
|
||||||
|
error: (error as Error).message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { t } from '@/i18n'
|
||||||
import { AIStoryboardMasterAIEnhance } from './aiPrompt/bookStoryboardPrompt/aiStoryboardMasterAIEnhance'
|
import { AIStoryboardMasterAIEnhance } from './aiPrompt/bookStoryboardPrompt/aiStoryboardMasterAIEnhance'
|
||||||
import { AIStoryboardMasterGeneral } from './aiPrompt/bookStoryboardPrompt/aiStoryboardMasterGeneral'
|
import { AIStoryboardMasterGeneral } from './aiPrompt/bookStoryboardPrompt/aiStoryboardMasterGeneral'
|
||||||
import { AIStoryboardMasterMJAncientStyle } from './aiPrompt/bookStoryboardPrompt/aiStoryboardMasterMJAncientStyle'
|
import { AIStoryboardMasterMJAncientStyle } from './aiPrompt/bookStoryboardPrompt/aiStoryboardMasterMJAncientStyle'
|
||||||
@ -24,7 +25,7 @@ export type AiInferenceModelModel = {
|
|||||||
export const aiOptionsData: AiInferenceModelModel[] = [
|
export const aiOptionsData: AiInferenceModelModel[] = [
|
||||||
{
|
{
|
||||||
value: 'AIStoryboardMasterScenePrompt',
|
value: 'AIStoryboardMasterScenePrompt',
|
||||||
label: '【LaiTool】场景提示大师(上下文-提示词不包含人物)',
|
label: t('【LaiTool】场景提示大师(上下文-提示词不包含人物)'),
|
||||||
hasExample: false,
|
hasExample: false,
|
||||||
mustCharacter: false,
|
mustCharacter: false,
|
||||||
requestBody: AIStoryboardMasterScenePrompt,
|
requestBody: AIStoryboardMasterScenePrompt,
|
||||||
@ -32,7 +33,7 @@ export const aiOptionsData: AiInferenceModelModel[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'AIStoryboardMasterSpecialEffects',
|
value: 'AIStoryboardMasterSpecialEffects',
|
||||||
label: '【LaiTool】分镜大师-特效增强版(上下文-人物场景固定)',
|
label: t('【LaiTool】分镜大师-特效增强版(上下文-人物场景固定)'),
|
||||||
hasExample: false,
|
hasExample: false,
|
||||||
mustCharacter: true,
|
mustCharacter: true,
|
||||||
requestBody: AIStoryboardMasterSpecialEffects,
|
requestBody: AIStoryboardMasterSpecialEffects,
|
||||||
@ -40,7 +41,7 @@ export const aiOptionsData: AiInferenceModelModel[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'AIStoryboardMasterGeneral',
|
value: 'AIStoryboardMasterGeneral',
|
||||||
label: '【LaiTool】分镜大师-通用版(上下文-人物场景固定-类型推理)',
|
label: t('【LaiTool】分镜大师-通用版(上下文-人物场景固定-类型推理)'),
|
||||||
hasExample: false,
|
hasExample: false,
|
||||||
mustCharacter: true,
|
mustCharacter: true,
|
||||||
requestBody: AIStoryboardMasterGeneral,
|
requestBody: AIStoryboardMasterGeneral,
|
||||||
@ -48,7 +49,7 @@ export const aiOptionsData: AiInferenceModelModel[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'AIStoryboardMasterAIEnhance',
|
value: 'AIStoryboardMasterAIEnhance',
|
||||||
label: '【LaiTool】分镜大师-全面版-AI增强(上下文-人物场景固定-单帧)',
|
label: t('【LaiTool】分镜大师-全面版-AI增强(上下文-人物场景固定-单帧)'),
|
||||||
hasExample: false,
|
hasExample: false,
|
||||||
mustCharacter: true,
|
mustCharacter: true,
|
||||||
requestBody: AIStoryboardMasterAIEnhance,
|
requestBody: AIStoryboardMasterAIEnhance,
|
||||||
@ -56,7 +57,7 @@ export const aiOptionsData: AiInferenceModelModel[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'AIStoryboardMasterOptimize',
|
value: 'AIStoryboardMasterOptimize',
|
||||||
label: '【LaiTool】分镜大师-全能优化版(上下文-人物固定)',
|
label: t('【LaiTool】分镜大师-全能优化版(上下文-人物固定)'),
|
||||||
hasExample: false,
|
hasExample: false,
|
||||||
mustCharacter: true,
|
mustCharacter: true,
|
||||||
requestBody: AIStoryboardMasterOptimize,
|
requestBody: AIStoryboardMasterOptimize,
|
||||||
@ -64,7 +65,7 @@ export const aiOptionsData: AiInferenceModelModel[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'AIStoryboardMasterMJAncientStyle',
|
value: 'AIStoryboardMasterMJAncientStyle',
|
||||||
label: '【LaiTool】分镜大师-MJ古风版(上下文-人物场景固定-MJ古风提示词)',
|
label: t('【LaiTool】分镜大师-MJ古风版(上下文-人物场景固定-MJ古风提示词)'),
|
||||||
hasExample: false,
|
hasExample: false,
|
||||||
mustCharacter: true,
|
mustCharacter: true,
|
||||||
requestBody: AIStoryboardMasterMJAncientStyle,
|
requestBody: AIStoryboardMasterMJAncientStyle,
|
||||||
@ -72,7 +73,7 @@ export const aiOptionsData: AiInferenceModelModel[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'AIStoryboardMasterSDEnglish',
|
value: 'AIStoryboardMasterSDEnglish',
|
||||||
label: '【LaiTool】分镜大师-SD英文版(上下文-人物场景固定-SD-英文提示词)',
|
label: t('【LaiTool】分镜大师-SD英文版(上下文-人物场景固定-SD-英文提示词)'),
|
||||||
hasExample: false,
|
hasExample: false,
|
||||||
mustCharacter: true,
|
mustCharacter: true,
|
||||||
requestBody: AIStoryboardMasterSDEnglish,
|
requestBody: AIStoryboardMasterSDEnglish,
|
||||||
@ -80,7 +81,7 @@ export const aiOptionsData: AiInferenceModelModel[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'AIStoryboardMasterSingleFrame',
|
value: 'AIStoryboardMasterSingleFrame',
|
||||||
label: '【LaiTool】分镜大师-单帧分镜提示词(上下文-单帧-人物自动推理)',
|
label: t('【LaiTool】分镜大师-单帧分镜提示词(上下文-单帧-人物自动推理)'),
|
||||||
hasExample: false,
|
hasExample: false,
|
||||||
mustCharacter: false,
|
mustCharacter: false,
|
||||||
requestBody: AIStoryboardMasterSingleFrame,
|
requestBody: AIStoryboardMasterSingleFrame,
|
||||||
@ -88,7 +89,7 @@ export const aiOptionsData: AiInferenceModelModel[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'AIStoryboardMasterSingleFrameWithCharacter',
|
value: 'AIStoryboardMasterSingleFrameWithCharacter',
|
||||||
label: '【LaiTool】分镜大师-单帧分镜提示词(上下文-单帧-人物场景固定)',
|
label: t('【LaiTool】分镜大师-单帧分镜提示词(上下文-单帧-人物场景固定)'),
|
||||||
hasExample: false,
|
hasExample: false,
|
||||||
mustCharacter: true,
|
mustCharacter: true,
|
||||||
requestBody: AIStoryboardMasterSingleFrameWithCharacter,
|
requestBody: AIStoryboardMasterSingleFrameWithCharacter,
|
||||||
@ -106,7 +107,7 @@ export const aiOptionsData: AiInferenceModelModel[] = [
|
|||||||
export function GetAIPromptOptionByValue(value: string) {
|
export function GetAIPromptOptionByValue(value: string) {
|
||||||
let aiOptionIndex = aiOptionsData.findIndex((item) => item.value == value)
|
let aiOptionIndex = aiOptionsData.findIndex((item) => item.value == value)
|
||||||
if (aiOptionIndex == -1) {
|
if (aiOptionIndex == -1) {
|
||||||
throw new Error('没有找到对应的AI选项,请先检查配置')
|
throw new Error(t('没有找到对应的AI选项,请先检查配置'))
|
||||||
}
|
}
|
||||||
return aiOptionsData[aiOptionIndex]
|
return aiOptionsData[aiOptionIndex]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
|
import { t } from "@/i18n"
|
||||||
|
|
||||||
export const apiDefineData = [
|
export const apiDefineData = [
|
||||||
{
|
{
|
||||||
label: 'LAI API - 香港',
|
label: t('LAI API - 香港'),
|
||||||
value: 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65',
|
value: 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65',
|
||||||
id: 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65',
|
id: 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65',
|
||||||
gpt_url: 'https://api.laitool.cc/v1/chat/completions',
|
gpt_url: 'https://api.laitool.cc/v1/chat/completions',
|
||||||
mj_url: {
|
mj_url: {
|
||||||
imagine: 'https://api.laitool.cc/mj/submit/imagine',
|
imagine: 'https://api.laitool.cc/mj/submit/imagine',
|
||||||
describe: 'https://api.laitool.cc/mj/submit/describe',
|
describe: 'https://api.laitool.cc/mj/submit/describe',
|
||||||
|
video: 'https://api.laitool.cc/mj/submit/video',
|
||||||
update_file: 'https://api.laitool.cc/mj/submit/upload-discord-images',
|
update_file: 'https://api.laitool.cc/mj/submit/upload-discord-images',
|
||||||
once_get_task: 'https://api.laitool.cc/mj/task/${id}/fetch'
|
once_get_task: 'https://api.laitool.cc/mj/task/${id}/fetch'
|
||||||
},
|
},
|
||||||
@ -16,13 +19,14 @@ export const apiDefineData = [
|
|||||||
buy_url: 'https://api.laitool.cc/register?aff=RCSW'
|
buy_url: 'https://api.laitool.cc/register?aff=RCSW'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'LAI API - 美国',
|
label: t('LAI API - 美国'),
|
||||||
value: '2b443f53-ba12-42b3-a57c-e4df92685c73',
|
value: '2b443f53-ba12-42b3-a57c-e4df92685c73',
|
||||||
id: '2b443f53-ba12-42b3-a57c-e4df92685c73',
|
id: '2b443f53-ba12-42b3-a57c-e4df92685c73',
|
||||||
gpt_url: 'https://laitool.net/v1/chat/completions',
|
gpt_url: 'https://laitool.net/v1/chat/completions',
|
||||||
mj_url: {
|
mj_url: {
|
||||||
imagine: 'https://laitool.net/mj/submit/imagine',
|
imagine: 'https://laitool.net/mj/submit/imagine',
|
||||||
describe: 'https://laitool.net/mj/submit/describe',
|
describe: 'https://laitool.net/mj/submit/describe',
|
||||||
|
video: 'https://laitool.net/mj/submit/video',
|
||||||
update_file: 'https://laitool.net/mj/submit/upload-discord-images',
|
update_file: 'https://laitool.net/mj/submit/upload-discord-images',
|
||||||
once_get_task: 'https://laitool.net/mj/task/${id}/fetch'
|
once_get_task: 'https://laitool.net/mj/task/${id}/fetch'
|
||||||
},
|
},
|
||||||
@ -32,7 +36,7 @@ export const apiDefineData = [
|
|||||||
buy_url: 'https://laitool.net/register?aff=RCSW'
|
buy_url: 'https://laitool.net/register?aff=RCSW'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'LaiTool生图包',
|
label: t('LaiTool生图包'),
|
||||||
value: '9c9023bd-871d-4b63-8004-facb3b66c5b3',
|
value: '9c9023bd-871d-4b63-8004-facb3b66c5b3',
|
||||||
isPackage: true,
|
isPackage: true,
|
||||||
mj_url: {
|
mj_url: {
|
||||||
@ -55,7 +59,7 @@ export const apiDefineData = [
|
|||||||
export function GetApiDefineDataById(id: string) {
|
export function GetApiDefineDataById(id: string) {
|
||||||
let mj_api_url_index = apiDefineData.findIndex((item) => item.value == id)
|
let mj_api_url_index = apiDefineData.findIndex((item) => item.value == id)
|
||||||
if (mj_api_url_index == -1) {
|
if (mj_api_url_index == -1) {
|
||||||
throw new Error('没有找到对应的API的配置,请先检查配置')
|
throw new Error(t('没有找到对应的API的配置,请先检查配置'))
|
||||||
}
|
}
|
||||||
return apiDefineData[mj_api_url_index]
|
return apiDefineData[mj_api_url_index]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -98,30 +98,3 @@ export function getImageCategoryLabel(value: string): TaskModal.TaskStatus {
|
|||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 图转视频方式
|
|
||||||
|
|
||||||
export enum ImageToVideoCategory {
|
|
||||||
/** runway 生成视频 */
|
|
||||||
RUNWAY = 'runway',
|
|
||||||
/** luma 生成视频 */
|
|
||||||
LUMA = 'luma',
|
|
||||||
/** 可灵生成视频 */
|
|
||||||
KLING = 'kling',
|
|
||||||
/** Pika 生成视频 */
|
|
||||||
PIKA = 'pika'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有出图方式的选项列表
|
|
||||||
* @returns 包含label和value的选项数组
|
|
||||||
*/
|
|
||||||
export function getImageToVideoCategoryOptions(): Array<{ label: string; value: string }> {
|
|
||||||
return [
|
|
||||||
{ label: 'Runway', value: ImageToVideoCategory.RUNWAY },
|
|
||||||
{ label: 'Luma', value: ImageToVideoCategory.LUMA },
|
|
||||||
{ label: '可灵', value: ImageToVideoCategory.KLING },
|
|
||||||
{ label: 'Pika', value: ImageToVideoCategory.PIKA }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
//#region MJ 出图方式
|
//#region MJ 出图方式
|
||||||
|
|
||||||
|
import { t } from "@/i18n";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MJ 图像生成方式,目前仅支持 API 模式
|
* MJ 图像生成方式,目前仅支持 API 模式
|
||||||
*/
|
*/
|
||||||
@ -40,10 +42,10 @@ export enum ImageGenerateMode {
|
|||||||
*/
|
*/
|
||||||
export function getImageGenerateModeOptions(): Array<{ label: string; value: string }> {
|
export function getImageGenerateModeOptions(): Array<{ label: string; value: string }> {
|
||||||
return [
|
return [
|
||||||
{ label: 'API模式', value: ImageGenerateMode.MJ_API },
|
{ label: t('API模式'), value: ImageGenerateMode.MJ_API },
|
||||||
{ label: 'LaiTool生图包', value: ImageGenerateMode.MJ_PACKAGE },
|
{ label: t('LaiTool生图包'), value: ImageGenerateMode.MJ_PACKAGE },
|
||||||
{ label: '代理模式', value: ImageGenerateMode.REMOTE_MJ },
|
{ label: t('代理模式'), value: ImageGenerateMode.REMOTE_MJ },
|
||||||
{ label: '本地代理模式(自有账号推荐)', value: ImageGenerateMode.LOCAL_MJ }
|
{ label: t('本地代理模式(自有账号推荐)'), value: ImageGenerateMode.LOCAL_MJ }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,11 +210,11 @@ export enum MJSpeed {
|
|||||||
export function getMJSpeedOptions() {
|
export function getMJSpeedOptions() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: '快速',
|
label: t('快速'),
|
||||||
value: MJSpeed.FAST
|
value: MJSpeed.FAST
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '慢速',
|
label: t('慢速'),
|
||||||
value: MJSpeed.RELAX
|
value: MJSpeed.RELAX
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { t } from "@/i18n";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 预设数据类型
|
* 预设数据类型
|
||||||
*/
|
*/
|
||||||
@ -18,9 +20,9 @@ export enum PresetCategory {
|
|||||||
*/
|
*/
|
||||||
export function getPresetCategoryOptions(): Array<{ label: string; value: string }> {
|
export function getPresetCategoryOptions(): Array<{ label: string; value: string }> {
|
||||||
return [
|
return [
|
||||||
{ label: '风格预设', value: PresetCategory.Style },
|
{ label: t('按钮,风格预设'), value: PresetCategory.Style },
|
||||||
{ label: '人物预设', value: PresetCategory.Character },
|
{ label: t('按钮,角色预设'), value: PresetCategory.Character },
|
||||||
{ label: '场景预设', value: PresetCategory.Scene }
|
{ label: t('按钮,场景预设'), value: PresetCategory.Scene }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,7 +33,7 @@ export function getPresetCategoryOptions(): Array<{ label: string; value: string
|
|||||||
*/
|
*/
|
||||||
export function getPresetCategoryLabel(value: string): string {
|
export function getPresetCategoryLabel(value: string): string {
|
||||||
const option = getPresetCategoryOptions().find((item) => item.value === value)
|
const option = getPresetCategoryOptions().find((item) => item.value === value)
|
||||||
return option ? option.label : '未知类型'
|
return option ? option.label : t('未知类型')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,9 +47,9 @@ export function getPromptSortLabel(value: string): string {
|
|||||||
return option.label
|
return option.label
|
||||||
} else {
|
} else {
|
||||||
if (value == 'prompt') {
|
if (value == 'prompt') {
|
||||||
return '提示词'
|
return t('提示词')
|
||||||
} else {
|
} else {
|
||||||
return '未知类型'
|
return t('未知类型')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,8 @@ interface ISoftwareData {
|
|||||||
version: string
|
version: string
|
||||||
/** 发布日期 */
|
/** 发布日期 */
|
||||||
date: string
|
date: string
|
||||||
/** 更新说明列表 */
|
|
||||||
notes: string[]
|
|
||||||
/** 系统信息 */
|
/** 系统信息 */
|
||||||
systemInfo: {
|
systemInfo: {
|
||||||
/** 快速开始 */
|
|
||||||
quickStart: string
|
|
||||||
/** 使用文档 */
|
/** 使用文档 */
|
||||||
documentationUrl: string
|
documentationUrl: string
|
||||||
/** 更新文档 */
|
/** 更新文档 */
|
||||||
@ -34,28 +30,7 @@ interface ISoftwareData {
|
|||||||
export const SoftwareData: ISoftwareData = {
|
export const SoftwareData: ISoftwareData = {
|
||||||
version: 'V3.4.2',
|
version: 'V3.4.2',
|
||||||
date: '2025-08-08',
|
date: '2025-08-08',
|
||||||
notes: [
|
|
||||||
'1. 新增图/文转视频菜单界面,专注实现图/文转视频(目前只集成了 MJ VIDEO)',
|
|
||||||
' • 全新的界面排列,小说列表和批次任务更加分明',
|
|
||||||
' • 添加转视频进度,在主界面即可看到转视频的比例',
|
|
||||||
' • 单独的界面去处理图转视频,避免表格数据过多繁琐',
|
|
||||||
' • 新增分页显示,界面加载更快,也可切换不分页',
|
|
||||||
' • 单独操作面板,参数修改处理更加清晰,支持多种模式显示',
|
|
||||||
' • 批量设置转视频配置,可以批量修改分类',
|
|
||||||
' • 友好的选择视频界面',
|
|
||||||
'2. 重写软件导出剪映,修复若干草稿导出问题',
|
|
||||||
' • 修复导出剪映文案和图片对齐问题,解决时长越长越明显的对不上问题',
|
|
||||||
' • 修复导出草稿关键帧部分问题',
|
|
||||||
' • 导出的文案通过分镜自动导入,不再需要手动选择SRT',
|
|
||||||
'3. 美化生成草稿界面弹窗,优化部分逻辑',
|
|
||||||
' • 删除选择SRT文件,SRT根据聚合推文中导入的SRT自动生成草稿',
|
|
||||||
' • 只需选择配音文件即可,配音文件和导入的SRT请自行对应',
|
|
||||||
' • 背景音乐不在内部设置,自行选择文件夹或者是MP3、WAV文件',
|
|
||||||
' • 背景音乐选择文件夹则读取文件夹,随机获取一个',
|
|
||||||
' • 背景音乐选择指定的音乐文件则使用选择的'
|
|
||||||
],
|
|
||||||
systemInfo: {
|
systemInfo: {
|
||||||
quickStart: '快速开始',
|
|
||||||
documentationUrl: 'https://rvgyir5wk1c.feishu.cn/wiki/WdaWwAfDdiLOnjkywIgcaQoKnog',
|
documentationUrl: 'https://rvgyir5wk1c.feishu.cn/wiki/WdaWwAfDdiLOnjkywIgcaQoKnog',
|
||||||
updateUrl: 'https://pvwu1oahp5m.feishu.cn/docx/CAjGdTDlboJ3nVx0cQccOuNHnvd',
|
updateUrl: 'https://pvwu1oahp5m.feishu.cn/docx/CAjGdTDlboJ3nVx0cQccOuNHnvd',
|
||||||
softwareUrl: 'https://pvwu1oahp5m.feishu.cn/docx/FONZdfnrOoLlMrxXHV0czJ3jnkd',
|
softwareUrl: 'https://pvwu1oahp5m.feishu.cn/docx/FONZdfnrOoLlMrxXHV0czJ3jnkd',
|
||||||
|
|||||||
@ -63,6 +63,7 @@ export class VideoMessage extends Realm.Object<VideoMessage> {
|
|||||||
runwayOptions!: string | null // 生成视频的一些设置
|
runwayOptions!: string | null // 生成视频的一些设置
|
||||||
lumaOptions!: string | null // 生成视频的一些设置
|
lumaOptions!: string | null // 生成视频的一些设置
|
||||||
klingOptions!: string | null // 生成视频的一些设置
|
klingOptions!: string | null // 生成视频的一些设置
|
||||||
|
mjVideoOptions!: string | null // MJ生成视频的一些设置
|
||||||
messageData!: string | null
|
messageData!: string | null
|
||||||
static schema: ObjectSchema = {
|
static schema: ObjectSchema = {
|
||||||
name: 'VideoMessage',
|
name: 'VideoMessage',
|
||||||
@ -81,6 +82,7 @@ export class VideoMessage extends Realm.Object<VideoMessage> {
|
|||||||
runwayOptions: 'string?',
|
runwayOptions: 'string?',
|
||||||
lumaOptions: 'string?',
|
lumaOptions: 'string?',
|
||||||
klingOptions: 'string?',
|
klingOptions: 'string?',
|
||||||
|
mjVideoOptions: 'string?',
|
||||||
messageData: 'string?'
|
messageData: 'string?'
|
||||||
},
|
},
|
||||||
primaryKey: 'id'
|
primaryKey: 'id'
|
||||||
@ -166,6 +168,7 @@ export class BookTaskDetailModel extends Realm.Object<BookTaskDetailModel> {
|
|||||||
bookTaskId!: string
|
bookTaskId!: string
|
||||||
videoPath!: string | null // 视频地址
|
videoPath!: string | null // 视频地址
|
||||||
generateVideoPath!: string | null // 生成视频地址
|
generateVideoPath!: string | null // 生成视频地址
|
||||||
|
subVideoPath!: string[] | null // 生成的批次视频的地址
|
||||||
audioPath!: string | null // 音频地址
|
audioPath!: string | null // 音频地址
|
||||||
word!: string | null // 文案
|
word!: string | null // 文案
|
||||||
oldImage!: string | null // 旧图片(用于SD的图生图)
|
oldImage!: string | null // 旧图片(用于SD的图生图)
|
||||||
@ -203,6 +206,7 @@ export class BookTaskDetailModel extends Realm.Object<BookTaskDetailModel> {
|
|||||||
bookTaskId: { type: 'string', indexed: true },
|
bookTaskId: { type: 'string', indexed: true },
|
||||||
videoPath: 'string?',
|
videoPath: 'string?',
|
||||||
generateVideoPath: 'string?', // 生成视频地址
|
generateVideoPath: 'string?', // 生成视频地址
|
||||||
|
subVideoPath: 'string[]', // 生成的批次视频的地址
|
||||||
audioPath: 'string?',
|
audioPath: 'string?',
|
||||||
word: 'string?',
|
word: 'string?',
|
||||||
oldImage: 'string?',
|
oldImage: 'string?',
|
||||||
|
|||||||
@ -16,6 +16,8 @@ export class TaskListModel extends Realm.Object<TaskListModel> {
|
|||||||
startTime!: number
|
startTime!: number
|
||||||
endTime!: number
|
endTime!: number
|
||||||
messageName?: string
|
messageName?: string
|
||||||
|
taskId?: string // 任务ID,可能是视频生成任务的ID
|
||||||
|
taskMessage?: string // 任务消息,可能是视频生成任务的消息
|
||||||
|
|
||||||
static schema: ObjectSchema = {
|
static schema: ObjectSchema = {
|
||||||
name: 'TaskList',
|
name: 'TaskList',
|
||||||
@ -33,7 +35,9 @@ export class TaskListModel extends Realm.Object<TaskListModel> {
|
|||||||
updateTime: 'date',
|
updateTime: 'date',
|
||||||
startTime: 'int',
|
startTime: 'int',
|
||||||
endTime: 'int',
|
endTime: 'int',
|
||||||
messageName: 'string?'
|
messageName: 'string?',
|
||||||
|
taskId: 'string?', // 任务ID,可能是视频生成任务的ID
|
||||||
|
taskMessage: 'string?' // 任务消息,可能是视频生成任务的消息
|
||||||
},
|
},
|
||||||
primaryKey: 'id'
|
primaryKey: 'id'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,15 +13,15 @@ import {
|
|||||||
} from '../../model/bookTaskDetail'
|
} from '../../model/bookTaskDetail'
|
||||||
import { TaskListModel } from '../../model/taskList'
|
import { TaskListModel } from '../../model/taskList'
|
||||||
import { OptionModel } from '../../model/options'
|
import { OptionModel } from '../../model/options'
|
||||||
import { app } from 'electron'
|
|
||||||
import { BookTaskModel } from '../../model/bookTask'
|
import { BookTaskModel } from '../../model/bookTask'
|
||||||
import { PresetModel } from '../../model/preset'
|
import { PresetModel } from '../../model/preset'
|
||||||
|
|
||||||
|
const { app } = require('electron')
|
||||||
// Determine database path based on environment
|
// Determine database path based on environment
|
||||||
const isDev = !app.isPackaged
|
const isDev = !app.isPackaged
|
||||||
let dbPath = isDev
|
let dbPath = isDev
|
||||||
? path.resolve(process.cwd(), 'Database/option.realm') // Development path
|
? path.resolve(process.cwd(), 'Database/option.realm') // Development path
|
||||||
: path.resolve(app.getPath('userData'), 'Database/option.realm') // Production path
|
: path.resolve(app.getPath('userData'), '../laitool-pro/Database/option.realm') // Production path
|
||||||
|
|
||||||
// 版本迁移
|
// 版本迁移
|
||||||
const migration = (_oldRealm: Realm, _newRealm: Realm) => {}
|
const migration = (_oldRealm: Realm, _newRealm: Realm) => {}
|
||||||
@ -68,7 +68,7 @@ export class RealmBaseService extends BaseService {
|
|||||||
PresetModel
|
PresetModel
|
||||||
],
|
],
|
||||||
path: this.dbpath,
|
path: this.dbpath,
|
||||||
schemaVersion: 19, // 数据库版本号,修改时需要增加
|
schemaVersion: 21, // 数据库版本号,修改时需要增加
|
||||||
migration: migration
|
migration: migration
|
||||||
}
|
}
|
||||||
this.realm = await Realm.open(config)
|
this.realm = await Realm.open(config)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { getGeneralSetting, getProjectPath } from '@/main/service/option/optionC
|
|||||||
import { SrtHandle } from '@/main/service/common/srtHandle'
|
import { SrtHandle } from '@/main/service/common/srtHandle'
|
||||||
import { BookTaskDetailService } from './bookTaskDetailService'
|
import { BookTaskDetailService } from './bookTaskDetailService'
|
||||||
import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class BookService extends RealmBaseService {
|
export class BookService extends RealmBaseService {
|
||||||
static instance: BookService | null = null
|
static instance: BookService | null = null
|
||||||
@ -37,15 +38,25 @@ export class BookService extends RealmBaseService {
|
|||||||
* 获取小说信息,没有找到返回null
|
* 获取小说信息,没有找到返回null
|
||||||
* @param bookId
|
* @param bookId
|
||||||
*/
|
*/
|
||||||
async GetBookDataById(bookId: string): Promise<Book.SelectBook | null> {
|
async GetBookDataById(bookId: string, notEmpty: true): Promise<Book.SelectBook>
|
||||||
|
async GetBookDataById(bookId: string, notEmpty?: false): Promise<Book.SelectBook | null>
|
||||||
|
async GetBookDataById(
|
||||||
|
bookId: string,
|
||||||
|
notEmpty: boolean = false
|
||||||
|
): Promise<Book.SelectBook | null> {
|
||||||
try {
|
try {
|
||||||
if (isEmpty(bookId)) {
|
if (isEmpty(bookId)) {
|
||||||
throw new Error('获取小说信息失败,缺少小说ID')
|
throw new Error(t("{data} 不能为空", {
|
||||||
|
data: "ID"
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
let projectPath: string = await getProjectPath()
|
let projectPath: string = await getProjectPath()
|
||||||
|
|
||||||
let books = this.realm.objects<BookModel>('Book').filtered('id = $0', bookId)
|
let books = this.realm.objects<BookModel>('Book').filtered('id = $0', bookId)
|
||||||
if (books.length <= 0) {
|
if (books.length <= 0) {
|
||||||
|
if (notEmpty) {
|
||||||
|
throw new Error(t("未找到指定ID的小说数据"))
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
// 对返回的数据进行处理
|
// 对返回的数据进行处理
|
||||||
@ -148,14 +159,14 @@ export class BookService extends RealmBaseService {
|
|||||||
async AddOrModifyBook(book: Book.SelectBook): Promise<Book.SelectBook> {
|
async AddOrModifyBook(book: Book.SelectBook): Promise<Book.SelectBook> {
|
||||||
try {
|
try {
|
||||||
if (book == null) {
|
if (book == null) {
|
||||||
throw new Error('小说数据为空,无法修改')
|
throw new Error(t('小说数据为空,无法修改'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当小说的类型是反推的时候,必须传入视频
|
// 当小说的类型是反推的时候,必须传入视频
|
||||||
if (book.type == BookType.MJ_REVERSE || book.type == BookType.SD_REVERSE) {
|
if (book.type == BookType.MJ_REVERSE || book.type == BookType.SD_REVERSE) {
|
||||||
// 判断视频是否存在
|
// 判断视频是否存在
|
||||||
if (book.oldVideoPath == null || book.oldVideoPath == '') {
|
if (book.oldVideoPath == null || book.oldVideoPath == '') {
|
||||||
throw new Error('反推必须传入视频')
|
throw new Error(t('反推必须传入视频'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,9 +177,11 @@ export class BookService extends RealmBaseService {
|
|||||||
// 判断指定的名字在数据库中是否存在
|
// 判断指定的名字在数据库中是否存在
|
||||||
let books = this.realm.objects('Book').filtered('name = $0', book.name)
|
let books = this.realm.objects('Book').filtered('name = $0', book.name)
|
||||||
if (books.length > 0) {
|
if (books.length > 0) {
|
||||||
throw new Error(`小说名字 ${book.name} 已经存在,请更换小说名字`)
|
throw new Error(t("小说名字 {bookName} 已经存在,请更换小说名字", {
|
||||||
|
bookName: book.name
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
console.log(this)
|
|
||||||
// 新增数据
|
// 新增数据
|
||||||
book.id = crypto.randomUUID()
|
book.id = crypto.randomUUID()
|
||||||
book.createTime = new Date()
|
book.createTime = new Date()
|
||||||
@ -209,7 +222,7 @@ export class BookService extends RealmBaseService {
|
|||||||
let generalSetting = await getGeneralSetting()
|
let generalSetting = await getGeneralSetting()
|
||||||
imageCategory = generalSetting.defaultImgGenMethod ?? ImageCategory.Midjourney
|
imageCategory = generalSetting.defaultImgGenMethod ?? ImageCategory.Midjourney
|
||||||
} else {
|
} else {
|
||||||
throw new Error('未知的小说类型')
|
throw new Error(t('未知的小说类型'))
|
||||||
}
|
}
|
||||||
const srtHandle = new SrtHandle()
|
const srtHandle = new SrtHandle()
|
||||||
let srtData = await srtHandle.GetSrtDataByPath(book.srtPath as string)
|
let srtData = await srtHandle.GetSrtDataByPath(book.srtPath as string)
|
||||||
@ -286,7 +299,9 @@ export class BookService extends RealmBaseService {
|
|||||||
.objects('Book')
|
.objects('Book')
|
||||||
.filtered('name = $0 AND id != $1', book.name, book.id)
|
.filtered('name = $0 AND id != $1', book.name, book.id)
|
||||||
if (books.length > 0) {
|
if (books.length > 0) {
|
||||||
throw new Error(`小说名字 ${book.name} 已经存在,请更换小说名字`)
|
throw new Error(t("小说名字 {bookName} 已经存在,请更换小说名字", {
|
||||||
|
bookName: book.name
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
// 两个文件夹地址不能改,删除两个属性
|
// 两个文件夹地址不能改,删除两个属性
|
||||||
delete book.bookFolderPath
|
delete book.bookFolderPath
|
||||||
@ -313,16 +328,16 @@ export class BookService extends RealmBaseService {
|
|||||||
async ModifyBookDataById(bookId: string, bookData: Book.SelectBook): Promise<Book.SelectBook> {
|
async ModifyBookDataById(bookId: string, bookData: Book.SelectBook): Promise<Book.SelectBook> {
|
||||||
try {
|
try {
|
||||||
if (bookId == null) {
|
if (bookId == null) {
|
||||||
throw new Error('修改小说数据失败,缺少小说ID')
|
throw new Error(t("修改小说数据失败,缺少小说ID"))
|
||||||
}
|
}
|
||||||
if (bookData == null) {
|
if (bookData == null) {
|
||||||
throw new Error('修改小说数据失败,缺少小说数据')
|
throw new Error(t('修改小说数据失败,缺少小说数据'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查小说ID对应的数据是不是存在
|
// 检查小说ID对应的数据是不是存在
|
||||||
let bookRes = await this.GetBookDataById(bookId)
|
let bookRes = await this.GetBookDataById(bookId)
|
||||||
if (bookRes == null) {
|
if (bookRes == null) {
|
||||||
throw new Error('修改小说数据失败,小说ID对应的数据不存在')
|
throw new Error(t("修改小说数据失败,小说ID对应的数据不存在"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bookData && bookData.id) {
|
if (bookData && bookData.id) {
|
||||||
@ -336,7 +351,7 @@ export class BookService extends RealmBaseService {
|
|||||||
|
|
||||||
bookRes = await this.GetBookDataById(bookId)
|
bookRes = await this.GetBookDataById(bookId)
|
||||||
if (bookRes == null) {
|
if (bookRes == null) {
|
||||||
throw new Error('获取修改后的小说数据失败,小说ID对应的数据不存在')
|
throw new Error(t("获取修改后的小说数据失败,小说ID对应的数据不存在"))
|
||||||
}
|
}
|
||||||
return bookRes
|
return bookRes
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -353,7 +368,7 @@ export class BookService extends RealmBaseService {
|
|||||||
this.transaction(() => {
|
this.transaction(() => {
|
||||||
let book = this.realm.objectForPrimaryKey('Book', bookId)
|
let book = this.realm.objectForPrimaryKey('Book', bookId)
|
||||||
if (book == null) {
|
if (book == null) {
|
||||||
throw new Error('未找到对应的小说')
|
throw new Error(t("未找到指定ID的小说数据"))
|
||||||
}
|
}
|
||||||
// 删除对应的小说
|
// 删除对应的小说
|
||||||
this.realm.delete(book)
|
this.realm.delete(book)
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { Book } from '@/define/model/book/book'
|
|||||||
import { getProjectPath } from '@/main/service/option/optionCommonService'
|
import { getProjectPath } from '@/main/service/option/optionCommonService'
|
||||||
import { BookTaskDetailModel, ReversePrompt } from '../../model/bookTaskDetail'
|
import { BookTaskDetailModel, ReversePrompt } from '../../model/bookTaskDetail'
|
||||||
import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
import { ValidateJson } from '@/define/Tools/validate'
|
||||||
|
|
||||||
export class BookTaskDetailService extends RealmBaseService {
|
export class BookTaskDetailService extends RealmBaseService {
|
||||||
static instance: BookTaskDetailService | null = null
|
static instance: BookTaskDetailService | null = null
|
||||||
@ -38,7 +40,7 @@ export class BookTaskDetailService extends RealmBaseService {
|
|||||||
): Promise<Book.SelectBookTaskDetail[]> {
|
): Promise<Book.SelectBookTaskDetail[]> {
|
||||||
try {
|
try {
|
||||||
if (condition == null) {
|
if (condition == null) {
|
||||||
throw new Error('查询小说分镜信息,查询条件不能为空')
|
throw new Error(t("查询小说分镜信息,查询条件不能为空!"))
|
||||||
}
|
}
|
||||||
let tasksToDelete = this.realm.objects<BookTaskDetailModel>('BookTaskDetail')
|
let tasksToDelete = this.realm.objects<BookTaskDetailModel>('BookTaskDetail')
|
||||||
if (condition.id) {
|
if (condition.id) {
|
||||||
@ -67,6 +69,23 @@ export class BookTaskDetailService extends RealmBaseService {
|
|||||||
subImagePath: (item.subImagePath as string[])?.map((subImage) => {
|
subImagePath: (item.subImagePath as string[])?.map((subImage) => {
|
||||||
return JoinPath(projectPath, subImage)
|
return JoinPath(projectPath, subImage)
|
||||||
}),
|
}),
|
||||||
|
subVideoPath: (item.subVideoPath as string[]).map((subVideo) => subVideo.toString()),
|
||||||
|
subVideoPathObject: (item.subVideoPath as string[])?.map((subVideo) => {
|
||||||
|
if (isEmpty(subVideo)) {
|
||||||
|
return {}
|
||||||
|
} else {
|
||||||
|
if (!ValidateJson(subVideo)) {
|
||||||
|
return {}
|
||||||
|
} else {
|
||||||
|
let obj = JSON.parse(subVideo)
|
||||||
|
if (!isEmpty(obj.localPath)) {
|
||||||
|
obj.localPath =
|
||||||
|
JoinPath(projectPath, obj.localPath) + '?t=' + new Date().getTime()
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
characterTags: item.characterTags ? item.characterTags.map((tag) => tag) : [],
|
characterTags: item.characterTags ? item.characterTags.map((tag) => tag) : [],
|
||||||
sceneTags: item.sceneTags ? item.sceneTags.map((tag) => tag) : [],
|
sceneTags: item.sceneTags ? item.sceneTags.map((tag) => tag) : [],
|
||||||
styleTags: item.styleTags ? item.styleTags.map((tag) => tag) : [],
|
styleTags: item.styleTags ? item.styleTags.map((tag) => tag) : [],
|
||||||
@ -100,6 +119,42 @@ export class BookTaskDetailService extends RealmBaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过ID获取指定的小说任务分镜详细数据
|
||||||
|
* @param bookTaskDetailId
|
||||||
|
*/
|
||||||
|
public async GetBookTaskDetailDataById(
|
||||||
|
bookTaskDetailId: string,
|
||||||
|
notEmpty: true
|
||||||
|
): Promise<Book.SelectBookTaskDetail>
|
||||||
|
public async GetBookTaskDetailDataById(
|
||||||
|
bookTaskDetailId: string,
|
||||||
|
notEmpty?: false
|
||||||
|
): Promise<Book.SelectBookTaskDetail | null>
|
||||||
|
public async GetBookTaskDetailDataById(
|
||||||
|
bookTaskDetailId: string,
|
||||||
|
notEmpty: boolean = false
|
||||||
|
): Promise<Book.SelectBookTaskDetail | null> {
|
||||||
|
try {
|
||||||
|
if (bookTaskDetailId == null) {
|
||||||
|
throw new Error(t("{data} 不能为空", {
|
||||||
|
data: "ID"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
let bookTaskDetails = await this.GetBookTaskDetailDataByCondition({ id: bookTaskDetailId })
|
||||||
|
if (bookTaskDetails.length <= 0) {
|
||||||
|
if (notEmpty) {
|
||||||
|
throw new Error(t('未找到小说分镜数据,请检查!'))
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return bookTaskDetails[0] as Book.SelectBookTaskDetail
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回指定的小说分镜的指定的属性
|
* 返回指定的小说分镜的指定的属性
|
||||||
* @param bookTaskDetailId 小说分镜的ID
|
* @param bookTaskDetailId 小说分镜的ID
|
||||||
@ -109,34 +164,16 @@ export class BookTaskDetailService extends RealmBaseService {
|
|||||||
async GetBookTaskDetailProperty(bookTaskDetailId: string, property: string) {
|
async GetBookTaskDetailProperty(bookTaskDetailId: string, property: string) {
|
||||||
let bookTaskDetail = await this.GetBookTaskDetailDataById(bookTaskDetailId)
|
let bookTaskDetail = await this.GetBookTaskDetailDataById(bookTaskDetailId)
|
||||||
if (bookTaskDetail == null) {
|
if (bookTaskDetail == null) {
|
||||||
throw new Error('未找到对应的小说任务详细信息 ' + bookTaskDetailId)
|
throw new Error(t("未找到指定ID的小说分镜信息,ID: {id}", {
|
||||||
|
id: bookTaskDetailId
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
if (bookTaskDetail.hasOwnProperty(property)) {
|
if (bookTaskDetail.hasOwnProperty(property)) {
|
||||||
return bookTaskDetail[property]
|
return bookTaskDetail[property]
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`未找到对应的属性 ${property}`)
|
throw new Error(t("未找到对应的属性,属性: {property}", {
|
||||||
}
|
property: property
|
||||||
}
|
}))
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过ID获取指定的小说任务分镜详细数据
|
|
||||||
* @param bookTaskDetailId
|
|
||||||
*/
|
|
||||||
public async GetBookTaskDetailDataById(
|
|
||||||
bookTaskDetailId: string
|
|
||||||
): Promise<Book.SelectBookTaskDetail | null> {
|
|
||||||
try {
|
|
||||||
if (bookTaskDetailId == null) {
|
|
||||||
throw new Error('获取小说任务详细信息失败,缺少ID')
|
|
||||||
}
|
|
||||||
let bookTaskDetails = await this.GetBookTaskDetailDataByCondition({ id: bookTaskDetailId })
|
|
||||||
if (bookTaskDetails.length <= 0) {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
return bookTaskDetails[0] as Book.SelectBookTaskDetail
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +186,7 @@ export class BookTaskDetailService extends RealmBaseService {
|
|||||||
// 判断是不是又小说ID
|
// 判断是不是又小说ID
|
||||||
if (isEmpty(bookTaskDetail.bookId) || isEmpty(bookTaskDetail.bookTaskId)) {
|
if (isEmpty(bookTaskDetail.bookId) || isEmpty(bookTaskDetail.bookTaskId)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'新增小说任务详细信息到数据库失败,数据不完整,缺少小说ID或者小说批次任务ID'
|
t("新增小说分镜到数据库失败,数据不完整,缺少小说ID或者小说批次任务ID")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,10 +233,21 @@ export class BookTaskDetailService extends RealmBaseService {
|
|||||||
updateData: Book.SelectBookTaskDetail
|
updateData: Book.SelectBookTaskDetail
|
||||||
): Promise<Book.SelectBookTaskDetail | null> {
|
): Promise<Book.SelectBookTaskDetail | null> {
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
updateData.hasOwnProperty('generateVideoPath') &&
|
||||||
|
!isEmpty(updateData.generateVideoPath)
|
||||||
|
) {
|
||||||
|
let projectPath = await getProjectPath()
|
||||||
|
updateData.generateVideoPath = path.relative(
|
||||||
|
projectPath,
|
||||||
|
updateData.generateVideoPath as string
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
this.transaction(() => {
|
this.transaction(() => {
|
||||||
let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId)
|
let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId)
|
||||||
if (bookTaskDetail == null) {
|
if (bookTaskDetail == null) {
|
||||||
throw new Error('未找到对应的小说任务详细信息')
|
throw new Error(t("没有找到要更新的小说分镜信息"))
|
||||||
}
|
}
|
||||||
// 开始修改
|
// 开始修改
|
||||||
for (let key in updateData) {
|
for (let key in updateData) {
|
||||||
@ -225,7 +273,7 @@ export class BookTaskDetailService extends RealmBaseService {
|
|||||||
let mjMessageRes = this.realm.objectForPrimaryKey('MJMessage', bookTaskDetailId)
|
let mjMessageRes = this.realm.objectForPrimaryKey('MJMessage', bookTaskDetailId)
|
||||||
let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId)
|
let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId)
|
||||||
if (bookTaskDetail == null) {
|
if (bookTaskDetail == null) {
|
||||||
throw new Error('没有找到要更新的小说分镜信息')
|
throw new Error(t("没有找到要更新的小说分镜信息"))
|
||||||
}
|
}
|
||||||
if (bookTaskDetail.mjMessage == null) {
|
if (bookTaskDetail.mjMessage == null) {
|
||||||
// 新增
|
// 新增
|
||||||
@ -233,7 +281,7 @@ export class BookTaskDetailService extends RealmBaseService {
|
|||||||
bookTaskDetail.mjMessage = mjMessage
|
bookTaskDetail.mjMessage = mjMessage
|
||||||
} else {
|
} else {
|
||||||
if (mjMessageRes == null) {
|
if (mjMessageRes == null) {
|
||||||
throw new Error('没有找到要更新的出图信息')
|
throw new Error(t("没有找到要更新的出图信息"))
|
||||||
}
|
}
|
||||||
for (const key in mjMessage) {
|
for (const key in mjMessage) {
|
||||||
if (key != 'id') mjMessageRes[key] = mjMessage[key]
|
if (key != 'id') mjMessageRes[key] = mjMessage[key]
|
||||||
@ -252,37 +300,41 @@ export class BookTaskDetailService extends RealmBaseService {
|
|||||||
*/
|
*/
|
||||||
async UpdateBookTaskDetailVideoMessage(
|
async UpdateBookTaskDetailVideoMessage(
|
||||||
bookTaskDetailId: string,
|
bookTaskDetailId: string,
|
||||||
videoMessage: BookTaskDetail.VideoMessage
|
videoMessage: Partial<BookTaskDetail.VideoMessage>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let projectPath = await getProjectPath()
|
let projectPath = await getProjectPath()
|
||||||
this.transaction(() => {
|
this.transaction(() => {
|
||||||
let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId)
|
let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId)
|
||||||
let videoMessageRes = this.realm.objectForPrimaryKey('VideoMessage', bookTaskDetailId)
|
|
||||||
if (bookTaskDetail == null) {
|
if (bookTaskDetail == null) {
|
||||||
throw new Error('没有找到要更新的小说分镜信息')
|
throw new Error(t('没有找到要更新的小说分镜信息'))
|
||||||
}
|
}
|
||||||
if (videoMessageRes == null) {
|
|
||||||
throw new Error('没有找到要更新的视频信息')
|
// 处理图片路径 - 转换为相对路径
|
||||||
|
if (videoMessage.imageUrl && !videoMessage.imageUrl.startsWith('http')) {
|
||||||
|
videoMessage.imageUrl = path.relative(projectPath, videoMessage.imageUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bookTaskDetail.videoMessage == null) {
|
if (bookTaskDetail.videoMessage == null) {
|
||||||
// 新增
|
// 新增视频消息
|
||||||
videoMessage.id = bookTaskDetailId
|
videoMessage.id = bookTaskDetailId
|
||||||
bookTaskDetail.videoMessage = videoMessage
|
bookTaskDetail.videoMessage = videoMessage
|
||||||
} else {
|
} else {
|
||||||
|
// 更新现有视频消息
|
||||||
|
let videoMessageRes = this.realm.objectForPrimaryKey('VideoMessage', bookTaskDetailId)
|
||||||
|
if (videoMessageRes == null) {
|
||||||
|
// 如果关联的视频消息对象不存在,重新创建
|
||||||
|
videoMessage.id = bookTaskDetailId
|
||||||
|
bookTaskDetail.videoMessage = videoMessage
|
||||||
|
} else {
|
||||||
|
// 更新现有的视频消息字段
|
||||||
for (const key in videoMessage) {
|
for (const key in videoMessage) {
|
||||||
if (key == 'id') {
|
if (key !== 'id') {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
key == 'imageUrl' &&
|
|
||||||
videoMessage[key] != null &&
|
|
||||||
!videoMessage[key].startsWith('http')
|
|
||||||
) {
|
|
||||||
videoMessage[key] = path.relative(projectPath, videoMessage[key])
|
|
||||||
}
|
|
||||||
videoMessageRes[key] = videoMessage[key]
|
videoMessageRes[key] = videoMessage[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,7 +359,7 @@ export class BookTaskDetailService extends RealmBaseService {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (bookTaskDetails.length <= 0) {
|
if (bookTaskDetails.length <= 0) {
|
||||||
throw new Error('未找到执行的翻译数据,无法写回')
|
throw new Error(t("未找到执行的翻译的小说分镜数据,无法写回"))
|
||||||
}
|
}
|
||||||
let bookTaskDetail = bookTaskDetails[0]
|
let bookTaskDetail = bookTaskDetails[0]
|
||||||
// 直接写入
|
// 直接写入
|
||||||
@ -328,7 +380,7 @@ export class BookTaskDetailService extends RealmBaseService {
|
|||||||
DeleteBookTaskDetail(condition: Book.DeleteBookTaskDetailCondition) {
|
DeleteBookTaskDetail(condition: Book.DeleteBookTaskDetailCondition) {
|
||||||
try {
|
try {
|
||||||
if (isEmpty(condition.id) && isEmpty(condition.bookTaskId) && isEmpty(condition.bookId)) {
|
if (isEmpty(condition.id) && isEmpty(condition.bookTaskId) && isEmpty(condition.bookId)) {
|
||||||
throw new Error('删除小说分镜信息失败,没有必要参数')
|
throw new Error(t('缺少必要的条件,必须传入id,bookId或者bookTaskId其中一个'))
|
||||||
}
|
}
|
||||||
let tasksToDelete = this.realm.objects<BookTaskDetailModel>('BookTaskDetail')
|
let tasksToDelete = this.realm.objects<BookTaskDetailModel>('BookTaskDetail')
|
||||||
if (condition.id) {
|
if (condition.id) {
|
||||||
@ -363,7 +415,7 @@ export class BookTaskDetailService extends RealmBaseService {
|
|||||||
.objects<BookTaskDetailModel>('BookTaskDetail')
|
.objects<BookTaskDetailModel>('BookTaskDetail')
|
||||||
.filtered('id = $0', bookTaskDetailId)
|
.filtered('id = $0', bookTaskDetailId)
|
||||||
if (bookTaskDetails.length <= 0) {
|
if (bookTaskDetails.length <= 0) {
|
||||||
throw new Error('删除小说任务详细信息的反推提示词失败,未找到对应的分镜信息')
|
throw new Error(t("未找到对应的小说分镜信息"))
|
||||||
}
|
}
|
||||||
let bookTaskDetail = bookTaskDetails[0]
|
let bookTaskDetail = bookTaskDetails[0]
|
||||||
|
|
||||||
@ -388,7 +440,7 @@ export class BookTaskDetailService extends RealmBaseService {
|
|||||||
bookTaskDetailId
|
bookTaskDetailId
|
||||||
)
|
)
|
||||||
if (bookTaskDetail == null) {
|
if (bookTaskDetail == null) {
|
||||||
throw new Error('没有找到要删除的分镜信息')
|
throw new Error(t("未找到对应的小说分镜信息"))
|
||||||
}
|
}
|
||||||
if (bookTaskDetail.mjMessage) {
|
if (bookTaskDetail.mjMessage) {
|
||||||
this.realm.delete(bookTaskDetail.mjMessage)
|
this.realm.delete(bookTaskDetail.mjMessage)
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { BookTaskStatus, CopyImageType } from '@/define/enum/bookEnum'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { ImageToVideoModels } from '@/define/enum/video'
|
import { ImageToVideoModels } from '@/define/enum/video'
|
||||||
import { cloneDeep, isEmpty } from 'lodash'
|
import { cloneDeep, isEmpty } from 'lodash'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class BookTaskService extends RealmBaseService {
|
export class BookTaskService extends RealmBaseService {
|
||||||
static instance: BookTaskService | null = null
|
static instance: BookTaskService | null = null
|
||||||
@ -104,15 +105,30 @@ export class BookTaskService extends RealmBaseService {
|
|||||||
/**
|
/**
|
||||||
* 通过ID获取小说批次任务的数据
|
* 通过ID获取小说批次任务的数据
|
||||||
* @param bookTaskId
|
* @param bookTaskId
|
||||||
|
* @param notEmpty 为true时保证返回非空数据,为false时可能返回null
|
||||||
*/
|
*/
|
||||||
async GetBookTaskDataById(bookTaskId: string): Promise<Book.SelectBookTask> {
|
async GetBookTaskDataById(bookTaskId: string, notEmpty: true): Promise<Book.SelectBookTask>
|
||||||
|
async GetBookTaskDataById(
|
||||||
|
bookTaskId: string,
|
||||||
|
notEmpty?: false
|
||||||
|
): Promise<Book.SelectBookTask | null>
|
||||||
|
|
||||||
|
async GetBookTaskDataById(
|
||||||
|
bookTaskId: string,
|
||||||
|
notEmpty: boolean = false
|
||||||
|
): Promise<Book.SelectBookTask | null> {
|
||||||
try {
|
try {
|
||||||
if (bookTaskId == null) {
|
if (bookTaskId == null) {
|
||||||
throw new Error('小说任务ID不能为空')
|
throw new Error(t("{data} 不能为空", {
|
||||||
|
data: "ID"
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
let bookTasks = await this.GetBookTaskDataByCondition({ id: bookTaskId })
|
let bookTasks = await this.GetBookTaskDataByCondition({ id: bookTaskId })
|
||||||
if (bookTasks.bookTasks.length <= 0) {
|
if (bookTasks.bookTasks.length <= 0) {
|
||||||
throw new Error('未找到对应的小说任务')
|
if (notEmpty) {
|
||||||
|
throw new Error(t("未找到对应的小说批次任务信息,请检查!"))
|
||||||
|
}
|
||||||
|
return null
|
||||||
} else {
|
} else {
|
||||||
return bookTasks.bookTasks[0]
|
return bookTasks.bookTasks[0]
|
||||||
}
|
}
|
||||||
@ -132,7 +148,7 @@ export class BookTaskService extends RealmBaseService {
|
|||||||
// 修改对应小说批次任务的状态
|
// 修改对应小说批次任务的状态
|
||||||
let bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId)
|
let bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId)
|
||||||
if (bookTask == null) {
|
if (bookTask == null) {
|
||||||
throw new Error('未找到对应的小说任务')
|
throw new Error(t('未找到对应的小说批次任务'))
|
||||||
}
|
}
|
||||||
bookTask.status = status
|
bookTask.status = status
|
||||||
bookTask.updateTime = new Date()
|
bookTask.updateTime = new Date()
|
||||||
@ -158,14 +174,14 @@ export class BookTaskService extends RealmBaseService {
|
|||||||
this.transaction(() => {
|
this.transaction(() => {
|
||||||
let updateData = this.realm.objectForPrimaryKey('BookTask', bookTaskId)
|
let updateData = this.realm.objectForPrimaryKey('BookTask', bookTaskId)
|
||||||
if (updateData == null) {
|
if (updateData == null) {
|
||||||
throw new Error('未找到对应的小说任务详细信息')
|
throw new Error(t('未找到对应的小说批次任务'))
|
||||||
}
|
}
|
||||||
// 开始修改
|
// 开始修改
|
||||||
for (let key in data) {
|
for (let key in data) {
|
||||||
updateData[key] = data[key]
|
updateData[key] = data[key]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
let res = await this.GetBookTaskDataById(bookTaskId)
|
let res = await this.GetBookTaskDataById(bookTaskId, true)
|
||||||
return res
|
return res
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
@ -177,7 +193,9 @@ export class BookTaskService extends RealmBaseService {
|
|||||||
try {
|
try {
|
||||||
// 新增
|
// 新增
|
||||||
if (bookTask.bookId == '' || bookTask.bookId == null) {
|
if (bookTask.bookId == '' || bookTask.bookId == null) {
|
||||||
throw new Error('小说ID不能为空')
|
throw new Error(t("{data} 不能为空", {
|
||||||
|
data: t('小说ID')
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
bookTask.id = crypto.randomUUID()
|
bookTask.id = crypto.randomUUID()
|
||||||
@ -195,7 +213,7 @@ export class BookTaskService extends RealmBaseService {
|
|||||||
this.realm.create('BookTask', bookTask)
|
this.realm.create('BookTask', bookTask)
|
||||||
})
|
})
|
||||||
// 处理完毕,返回结果
|
// 处理完毕,返回结果
|
||||||
let res = await this.GetBookTaskDataById(bookTask.id)
|
let res = await this.GetBookTaskDataById(bookTask.id, true)
|
||||||
return res
|
return res
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
@ -213,7 +231,7 @@ export class BookTaskService extends RealmBaseService {
|
|||||||
let addBookTaskDetail = [] as Book.SelectBookTaskDetail[]
|
let addBookTaskDetail = [] as Book.SelectBookTaskDetail[]
|
||||||
let book = this.realm.objectForPrimaryKey('Book', sourceBookTask.bookId as string)
|
let book = this.realm.objectForPrimaryKey('Book', sourceBookTask.bookId as string)
|
||||||
if (book == null) {
|
if (book == null) {
|
||||||
throw new Error('未找到对应的小说')
|
throw new Error(t('未找到指定ID的小说数据'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let projectPath = await getProjectPath()
|
let projectPath = await getProjectPath()
|
||||||
@ -263,7 +281,7 @@ export class BookTaskService extends RealmBaseService {
|
|||||||
let subImagePath: string[] | undefined
|
let subImagePath: string[] | undefined
|
||||||
|
|
||||||
if (element.outImagePath == null || isEmpty(element.outImagePath)) {
|
if (element.outImagePath == null || isEmpty(element.outImagePath)) {
|
||||||
throw new Error('部分分镜的输出图片路径为空')
|
throw new Error(t('部分分镜的输出图片路径为空'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (copyImageType == CopyImageType.ALL) {
|
if (copyImageType == CopyImageType.ALL) {
|
||||||
@ -272,7 +290,7 @@ export class BookTaskService extends RealmBaseService {
|
|||||||
subImagePath = element.subImagePath
|
subImagePath = element.subImagePath
|
||||||
} else if (copyImageType == CopyImageType.ONE) {
|
} else if (copyImageType == CopyImageType.ONE) {
|
||||||
if (!element.subImagePath || element.subImagePath.length <= 1) {
|
if (!element.subImagePath || element.subImagePath.length <= 1) {
|
||||||
throw new Error('部分分镜的子图片路径数量不足或为空')
|
throw new Error(t('部分分镜的子图片路径数量不足或为空'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只复制对应的
|
// 只复制对应的
|
||||||
@ -285,7 +303,7 @@ export class BookTaskService extends RealmBaseService {
|
|||||||
outImagePath = undefined
|
outImagePath = undefined
|
||||||
subImagePath = []
|
subImagePath = []
|
||||||
} else {
|
} else {
|
||||||
throw new Error('无效的图片复制类型')
|
throw new Error(t('未知类型'))
|
||||||
}
|
}
|
||||||
if (outImagePath) {
|
if (outImagePath) {
|
||||||
// 单独处理一下显示的图片
|
// 单独处理一下显示的图片
|
||||||
@ -390,7 +408,7 @@ export class BookTaskService extends RealmBaseService {
|
|||||||
this.transaction(() => {
|
this.transaction(() => {
|
||||||
this.ResetBookTaskDataInfo(bookTaskId, resetBase)
|
this.ResetBookTaskDataInfo(bookTaskId, resetBase)
|
||||||
})
|
})
|
||||||
let res = await this.GetBookTaskDataById(bookTaskId)
|
let res = await this.GetBookTaskDataById(bookTaskId, true)
|
||||||
return res
|
return res
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
@ -409,7 +427,7 @@ export class BookTaskService extends RealmBaseService {
|
|||||||
// 删除批次数据
|
// 删除批次数据
|
||||||
let bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId)
|
let bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId)
|
||||||
if (bookTask == null) {
|
if (bookTask == null) {
|
||||||
throw new Error('未找到对应的小说任务,无法执行删除操作')
|
throw new Error(t('未找到对应的小说批次任务'))
|
||||||
}
|
}
|
||||||
this.realm.delete(bookTask)
|
this.realm.delete(bookTask)
|
||||||
})
|
})
|
||||||
@ -423,12 +441,12 @@ export class BookTaskService extends RealmBaseService {
|
|||||||
try {
|
try {
|
||||||
let modifyBookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId)
|
let modifyBookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId)
|
||||||
if (modifyBookTask == null) {
|
if (modifyBookTask == null) {
|
||||||
throw new Error('未找到对应的小说批次任务,无法执行重置操作')
|
throw new Error(t('未找到对应的小说批次任务'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let book = this.realm.objectForPrimaryKey('Book', modifyBookTask.bookId)
|
let book = this.realm.objectForPrimaryKey('Book', modifyBookTask.bookId)
|
||||||
if (book == null) {
|
if (book == null) {
|
||||||
throw new Error('未找到对应的小说,无法执行重置操作')
|
throw new Error(t('未找到指定ID的小说数据'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resetBase) {
|
if (resetBase) {
|
||||||
@ -462,7 +480,7 @@ export class BookTaskService extends RealmBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (bookTaskDetail.reversePrompt) {
|
if (bookTaskDetail.reversePrompt) {
|
||||||
;(bookTaskDetail.reversePrompt as any[]).forEach((item) => {
|
; (bookTaskDetail.reversePrompt as any[]).forEach((item) => {
|
||||||
this.realm.delete(item)
|
this.realm.delete(item)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { Book } from '@/define/model/book/book'
|
|||||||
import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum'
|
import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum'
|
||||||
import { OtherData } from '@/define/enum/softwareEnum'
|
import { OtherData } from '@/define/enum/softwareEnum'
|
||||||
import { TaskModal } from '@/define/model/task'
|
import { TaskModal } from '@/define/model/task'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class TaskListService extends RealmBaseService {
|
export class TaskListService extends RealmBaseService {
|
||||||
static instance: TaskListService | null = null
|
static instance: TaskListService | null = null
|
||||||
@ -224,13 +225,13 @@ export class TaskListService extends RealmBaseService {
|
|||||||
// 通过bookid获取book信息
|
// 通过bookid获取book信息
|
||||||
let book = this.realm.objectForPrimaryKey('Book', bookId)
|
let book = this.realm.objectForPrimaryKey('Book', bookId)
|
||||||
if (book == null) {
|
if (book == null) {
|
||||||
throw new Error('新增后台队列任务到数据库失败,没有找到对应的小说')
|
throw new Error(t('未找到指定ID的小说数据'))
|
||||||
}
|
}
|
||||||
let bookTask
|
let bookTask
|
||||||
if (bookTaskId) {
|
if (bookTaskId) {
|
||||||
bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId)
|
bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId)
|
||||||
if (bookTask == null) {
|
if (bookTask == null) {
|
||||||
throw new Error('新增后台队列任务到数据库失败,没有找到对应的小说批次任务')
|
throw new Error(t('未找到对应的小说批次任务'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,14 +240,15 @@ export class TaskListService extends RealmBaseService {
|
|||||||
bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId)
|
bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId)
|
||||||
if (bookTaskDetail == null) {
|
if (bookTaskDetail == null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'新增后台队列任务到数据库失败,没有找到对应的小说批次任务详情(分镜数据)'
|
t("未找到指定ID的小说分镜信息,ID: {id}", {
|
||||||
|
id: bookTaskDetailId
|
||||||
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始往数据库中写数据
|
// 开始往数据库中写数据
|
||||||
let name = `${book.name}-${bookTask ? bookTask.name : 'default'}-${
|
let name = `${book.name}-${bookTask ? bookTask.name : 'default'}-${bookTaskDetail ? bookTaskDetail.name : 'default'
|
||||||
bookTaskDetail ? bookTaskDetail.name : 'default'
|
|
||||||
}-${taskType}`
|
}-${taskType}`
|
||||||
|
|
||||||
let bookBackTask = {
|
let bookBackTask = {
|
||||||
@ -283,7 +285,7 @@ export class TaskListService extends RealmBaseService {
|
|||||||
try {
|
try {
|
||||||
// 判断数据是不是存在
|
// 判断数据是不是存在
|
||||||
if (isEmpty(bookBackTask.id) || isEmpty(bookBackTask.status)) {
|
if (isEmpty(bookBackTask.id) || isEmpty(bookBackTask.status)) {
|
||||||
throw new Error('修改后台队列任务失败,数据不完整,缺少必要字段')
|
throw new Error(t('修改后台队列任务失败,数据不完整,缺少必要字段'))
|
||||||
}
|
}
|
||||||
// 开始修改
|
// 开始修改
|
||||||
this.transaction(() => {
|
this.transaction(() => {
|
||||||
@ -294,7 +296,7 @@ export class TaskListService extends RealmBaseService {
|
|||||||
) as TaskModal.Task
|
) as TaskModal.Task
|
||||||
// 判断数据是不是存在
|
// 判断数据是不是存在
|
||||||
if (_bookBackTask == null) {
|
if (_bookBackTask == null) {
|
||||||
throw new Error('修改后台队列任务失败,数据不存在')
|
throw new Error(t('未找到指定的后台任务数据'))
|
||||||
}
|
}
|
||||||
// 修改数据
|
// 修改数据
|
||||||
_bookBackTask.status = bookBackTask.status
|
_bookBackTask.status = bookBackTask.status
|
||||||
@ -321,6 +323,33 @@ export class TaskListService extends RealmBaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新后台任务的数据
|
||||||
|
* @param taskId 任务ID
|
||||||
|
* @param backTaskParam 需要更新的任务数据部分字段
|
||||||
|
*/
|
||||||
|
UpdateBackTaskData(taskId: string, backTaskParam: Partial<TaskModal.Task>): void {
|
||||||
|
this.transaction(() => {
|
||||||
|
// 根据ID获取后台任务
|
||||||
|
let backTask = this.realm.objectForPrimaryKey('TaskList', taskId)
|
||||||
|
// 检查任务是否存在
|
||||||
|
if (backTask == null) {
|
||||||
|
throw new Error(
|
||||||
|
t('未找到对应ID的任务,任务ID:{taskId}', { taskId })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 遍历需要更新的字段
|
||||||
|
for (const key in backTaskParam) {
|
||||||
|
// 跳过ID字段,防止主键被修改
|
||||||
|
if (key == 'id') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 更新对应字段的值
|
||||||
|
backTask[key] = backTaskParam[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除满足条件的数据,包含 id、bookId、bookTaskId
|
* 删除满足条件的数据,包含 id、bookId、bookTaskId
|
||||||
* 上面的条件,至少要有一个
|
* 上面的条件,至少要有一个
|
||||||
@ -333,7 +362,7 @@ export class TaskListService extends RealmBaseService {
|
|||||||
!bookBackTask.hasOwnProperty('bookId') &&
|
!bookBackTask.hasOwnProperty('bookId') &&
|
||||||
!bookBackTask.hasOwnProperty('bookTaskId')
|
!bookBackTask.hasOwnProperty('bookTaskId')
|
||||||
) {
|
) {
|
||||||
throw new Error('删除后台队列任务失败,缺少必要的删除条件')
|
throw new Error(t("缺少必要的删除条件,至少需要id、bookId、bookTaskId其中一个"))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.transaction(() => {
|
this.transaction(() => {
|
||||||
@ -372,7 +401,7 @@ export class TaskListService extends RealmBaseService {
|
|||||||
for (let i = 0; i < ids.length; i++) {
|
for (let i = 0; i < ids.length; i++) {
|
||||||
let task = this.realm.objectForPrimaryKey('TaskList', ids[i])
|
let task = this.realm.objectForPrimaryKey('TaskList', ids[i])
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
throw new Error('没有找到对应的任务')
|
throw new Error(t('未找到指定的后台任务数据'))
|
||||||
}
|
}
|
||||||
task.status = BookBackTaskStatus.FAIL
|
task.status = BookBackTaskStatus.FAIL
|
||||||
task.errorMessage = errorMessage
|
task.errorMessage = errorMessage
|
||||||
@ -414,10 +443,10 @@ export class TaskListService extends RealmBaseService {
|
|||||||
for (let i = 0; i < ids.length; i++) {
|
for (let i = 0; i < ids.length; i++) {
|
||||||
const element = this.realm.objectForPrimaryKey('TaskList', ids[i])
|
const element = this.realm.objectForPrimaryKey('TaskList', ids[i])
|
||||||
if (element == null) {
|
if (element == null) {
|
||||||
throw new Error('没有找到对应的任务')
|
throw new Error(t('未找到指定的后台任务数据'))
|
||||||
}
|
}
|
||||||
element.status = BookBackTaskStatus.FAIL
|
element.status = BookBackTaskStatus.FAIL
|
||||||
element.errorMessage = '任务被丢弃'
|
element.errorMessage = t('任务被丢弃')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import Realm from 'realm'
|
|||||||
import { isEmpty, cloneDeep } from 'lodash'
|
import { isEmpty, cloneDeep } from 'lodash'
|
||||||
import { RealmBaseService } from './base/realmBase'
|
import { RealmBaseService } from './base/realmBase'
|
||||||
import { OptionType } from '@/define/enum/option'
|
import { OptionType } from '@/define/enum/option'
|
||||||
|
import { optionSerialization } from '@/main/service/option/optionSerialization'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class OptionRealmService extends RealmBaseService {
|
export class OptionRealmService extends RealmBaseService {
|
||||||
static instance: OptionRealmService | null = null
|
static instance: OptionRealmService | null = null
|
||||||
@ -23,6 +25,7 @@ export class OptionRealmService extends RealmBaseService {
|
|||||||
return OptionRealmService.instance
|
return OptionRealmService.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#region GetOptionByKey
|
||||||
/**
|
/**
|
||||||
* 获取指定的Option,通过key,不存在返回null
|
* 获取指定的Option,通过key,不存在返回null
|
||||||
* @param key
|
* @param key
|
||||||
@ -32,7 +35,7 @@ export class OptionRealmService extends RealmBaseService {
|
|||||||
if (isEmpty(key)) {
|
if (isEmpty(key)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
let res = this.realm.objects('Option').filtered(`key = "${key}"`);
|
let res = this.realm.objects('Option').filtered(`key = "${key}"`)
|
||||||
|
|
||||||
if (res.length > 0) {
|
if (res.length > 0) {
|
||||||
let resData = Array.from(res).map((item) => {
|
let resData = Array.from(res).map((item) => {
|
||||||
@ -43,10 +46,13 @@ export class OptionRealmService extends RealmBaseService {
|
|||||||
})
|
})
|
||||||
return resData[0] as OptionModel.OptionModel
|
return resData[0] as OptionModel.OptionModel
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region ModifyOptionByKey
|
||||||
/**
|
/**
|
||||||
* 修改指定的Option,通过key,不存在则创建
|
* 修改指定的Option,通过key,不存在则创建
|
||||||
* @param key
|
* @param key
|
||||||
@ -56,11 +62,11 @@ export class OptionRealmService extends RealmBaseService {
|
|||||||
if (isEmpty(key)) {
|
if (isEmpty(key)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
let option = this.realm.objectForPrimaryKey('Option', key);
|
let option = this.realm.objectForPrimaryKey('Option', key)
|
||||||
if (option) {
|
if (option) {
|
||||||
this.realm.write(() => {
|
this.realm.write(() => {
|
||||||
option.value = value;
|
option.value = value
|
||||||
option.type = type;
|
option.type = type
|
||||||
option.updateTime = new Date()
|
option.updateTime = new Date()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -76,6 +82,38 @@ export class OptionRealmService extends RealmBaseService {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region GetOptionData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用的获取配置数据方法
|
||||||
|
* @param optionKey 配置项的键名
|
||||||
|
* @param description 配置项的描述,用于错误信息
|
||||||
|
* @param defaultValue 默认值,当配置不存在或解析失败时返回
|
||||||
|
* @returns 返回解析后的配置对象
|
||||||
|
* @throws 当获取设置失败或解析失败时抛出错误
|
||||||
|
*/
|
||||||
|
public GetOptionDataByKey<T>(optionKey: string, description: string, defaultValue?: T): T {
|
||||||
|
try {
|
||||||
|
// 获取设置数据
|
||||||
|
let res = this.GetOptionByKey(optionKey)
|
||||||
|
|
||||||
|
// 开始解析
|
||||||
|
let optionData = optionSerialization<T>(res, description, defaultValue)
|
||||||
|
|
||||||
|
return optionData
|
||||||
|
} catch (error: any) {
|
||||||
|
// 记录错误日志
|
||||||
|
let errorMessage = t("获取 {description} 失败,{error}", {
|
||||||
|
description,
|
||||||
|
error: error.message || t('未知错误')
|
||||||
|
})
|
||||||
|
console.error(errorMessage)
|
||||||
|
|
||||||
|
// 重新抛出带有更详细信息的错误
|
||||||
|
throw new Error(errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { cloneDeep, isEmpty } from 'lodash'
|
|||||||
import { PresetModel } from '../model/preset'
|
import { PresetModel } from '../model/preset'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { PresetModel as DefinePresetModel } from '@/define/model/preset'
|
import { PresetModel as DefinePresetModel } from '@/define/model/preset'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class PresetRealmService extends RealmBaseService {
|
export class PresetRealmService extends RealmBaseService {
|
||||||
static instance: PresetRealmService | null = null
|
static instance: PresetRealmService | null = null
|
||||||
@ -160,13 +161,13 @@ export class PresetRealmService extends RealmBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isEmpty(newPreset.label)) {
|
if (isEmpty(newPreset.label)) {
|
||||||
throw new Error('预设名称不能为空!')
|
throw new Error(t('预设名称不能为空!'))
|
||||||
}
|
}
|
||||||
if (isEmpty(newPreset.type)) {
|
if (isEmpty(newPreset.type)) {
|
||||||
throw new Error('预设类型不能为空!')
|
throw new Error(t('预设分类不能为空!'))
|
||||||
}
|
}
|
||||||
if (isEmpty(newPreset.id)) {
|
if (isEmpty(newPreset.id)) {
|
||||||
throw new Error('预设ID不能为空!')
|
throw new Error(t("预设ID不能为空!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断对应的名字和类型是不是存在
|
// 判断对应的名字和类型是不是存在
|
||||||
@ -174,7 +175,7 @@ export class PresetRealmService extends RealmBaseService {
|
|||||||
.objects('Preset')
|
.objects('Preset')
|
||||||
.filtered('label = $0 && type = $1', newPreset.label, newPreset.type)
|
.filtered('label = $0 && type = $1', newPreset.label, newPreset.type)
|
||||||
if (existingPreset.length > 0) {
|
if (existingPreset.length > 0) {
|
||||||
throw new Error('再相同类型下已存在当前的预设名字,请修改后再试!')
|
throw new Error(t('在相同类型下已存在当前的预设名字,请修改后再试!'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始写入数据
|
// 开始写入数据
|
||||||
@ -196,7 +197,7 @@ export class PresetRealmService extends RealmBaseService {
|
|||||||
* */
|
* */
|
||||||
ModifyPreset(id: string, preset: Partial<DefinePresetModel.Preset>): DefinePresetModel.Preset {
|
ModifyPreset(id: string, preset: Partial<DefinePresetModel.Preset>): DefinePresetModel.Preset {
|
||||||
if (isEmpty(id)) {
|
if (isEmpty(id)) {
|
||||||
throw new Error('要修改的预设ID不能为空!')
|
throw new Error(t("预设ID不能为空!"))
|
||||||
}
|
}
|
||||||
delete preset.id // 删除ID属性,避免修改ID
|
delete preset.id // 删除ID属性,避免修改ID
|
||||||
delete preset.createTime // 删除创建时间属性,避免修改创建时间
|
delete preset.createTime // 删除创建时间属性,避免修改创建时间
|
||||||
@ -204,7 +205,7 @@ export class PresetRealmService extends RealmBaseService {
|
|||||||
this.transaction(() => {
|
this.transaction(() => {
|
||||||
let existingPreset = this.realm.objectForPrimaryKey<PresetModel>('Preset', id)
|
let existingPreset = this.realm.objectForPrimaryKey<PresetModel>('Preset', id)
|
||||||
if (existingPreset == null) {
|
if (existingPreset == null) {
|
||||||
throw new Error('要修改的预设不存在!')
|
throw new Error(t('未找到指定的预设数据'))
|
||||||
}
|
}
|
||||||
// 开始修改
|
// 开始修改
|
||||||
for (let key in preset) {
|
for (let key in preset) {
|
||||||
@ -224,12 +225,12 @@ export class PresetRealmService extends RealmBaseService {
|
|||||||
* */
|
* */
|
||||||
DeletePreset(id: string): void {
|
DeletePreset(id: string): void {
|
||||||
if (isEmpty(id)) {
|
if (isEmpty(id)) {
|
||||||
throw new Error('要删除的预设ID不能为空!')
|
throw new Error(t("预设ID不能为空!"))
|
||||||
}
|
}
|
||||||
this.transaction(() => {
|
this.transaction(() => {
|
||||||
let existingPreset = this.realm.objectForPrimaryKey('Preset', id)
|
let existingPreset = this.realm.objectForPrimaryKey('Preset', id)
|
||||||
if (existingPreset == null) {
|
if (existingPreset == null) {
|
||||||
throw new Error('要删除的预设不存在!')
|
throw new Error(t('未找到指定的预设数据'))
|
||||||
}
|
}
|
||||||
// 开始删除
|
// 开始删除
|
||||||
this.realm.delete(existingPreset)
|
this.realm.delete(existingPreset)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { TaskModal } from '@/define/model/task'
|
import { TaskModal } from '@/define/model/task'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
//#region 小说类型
|
//#region 小说类型
|
||||||
|
|
||||||
@ -19,13 +20,13 @@ export enum BookType {
|
|||||||
export function GetBookTypeLabel(type: BookType) {
|
export function GetBookTypeLabel(type: BookType) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case BookType.ORIGINAL:
|
case BookType.ORIGINAL:
|
||||||
return '原创'
|
return t('原创')
|
||||||
case BookType.SD_REVERSE:
|
case BookType.SD_REVERSE:
|
||||||
return 'SD反推'
|
return t('SD反推')
|
||||||
case BookType.MJ_REVERSE:
|
case BookType.MJ_REVERSE:
|
||||||
return 'MJ反推'
|
return t('MJ反推')
|
||||||
default:
|
default:
|
||||||
return '未知类型'
|
return t('未知类型')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,9 +36,9 @@ export function GetBookTypeLabel(type: BookType) {
|
|||||||
*/
|
*/
|
||||||
export function GetBookTypeOptions() {
|
export function GetBookTypeOptions() {
|
||||||
return [
|
return [
|
||||||
{ label: '原创', value: BookType.ORIGINAL },
|
{ label: t('原创'), value: BookType.ORIGINAL },
|
||||||
{ label: 'SD反推', value: BookType.SD_REVERSE },
|
{ label: t('SD反推'), value: BookType.SD_REVERSE },
|
||||||
{ label: 'MJ反推', value: BookType.MJ_REVERSE }
|
{ label: t('MJ反推'), value: BookType.MJ_REVERSE }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,43 +326,43 @@ export enum BookTagSelectType {
|
|||||||
export function GetBookBackTaskTypeLabel(key: string) {
|
export function GetBookBackTaskTypeLabel(key: string) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case BookBackTaskType.STORYBOARD:
|
case BookBackTaskType.STORYBOARD:
|
||||||
return '分镜计算'
|
return t('分镜计算')
|
||||||
case BookBackTaskType.SPLIT:
|
case BookBackTaskType.SPLIT:
|
||||||
return '分割视频'
|
return t('分割视频')
|
||||||
case BookBackTaskType.AUDIO:
|
case BookBackTaskType.AUDIO:
|
||||||
return '提取音频'
|
return t('提取音频')
|
||||||
case BookBackTaskType.RECOGNIZE:
|
case BookBackTaskType.RECOGNIZE:
|
||||||
return '识别字幕'
|
return t('识别字幕')
|
||||||
case BookBackTaskType.FRAME:
|
case BookBackTaskType.FRAME:
|
||||||
return '抽帧'
|
return t('抽帧')
|
||||||
case BookBackTaskType.MJ_REVERSE:
|
case BookBackTaskType.MJ_REVERSE:
|
||||||
return 'MJ反推'
|
return t('MJ反推')
|
||||||
case BookBackTaskType.SD_REVERSE:
|
case BookBackTaskType.SD_REVERSE:
|
||||||
return 'SD反推'
|
return t('SD反推')
|
||||||
case BookBackTaskType.MJ_IMAGE:
|
case BookBackTaskType.MJ_IMAGE:
|
||||||
return 'MJ生成图片'
|
return t('MJ生成图片')
|
||||||
case BookBackTaskType.SD_IMAGE:
|
case BookBackTaskType.SD_IMAGE:
|
||||||
return 'SD生成图片'
|
return t('SD生成图片')
|
||||||
case BookBackTaskType.FLUX_FORGE_IMAGE:
|
case BookBackTaskType.FLUX_FORGE_IMAGE:
|
||||||
return 'flux forge生成图片'
|
return t('flux forge生成图片')
|
||||||
case BookBackTaskType.FLUX_API_IMAGE:
|
case BookBackTaskType.FLUX_API_IMAGE:
|
||||||
return 'flux api生成图片'
|
return t('flux api生成图片')
|
||||||
case BookBackTaskType.D3_IMAGE:
|
case BookBackTaskType.D3_IMAGE:
|
||||||
return 'D3生成图片'
|
return t('D3生成图片')
|
||||||
case BookBackTaskType.HD:
|
case BookBackTaskType.HD:
|
||||||
return '高清'
|
return t('高清')
|
||||||
case BookBackTaskType.COMPOSING:
|
case BookBackTaskType.COMPOSING:
|
||||||
return '合成视频'
|
return t('合成视频')
|
||||||
case BookBackTaskType.INFERENCE:
|
case BookBackTaskType.INFERENCE:
|
||||||
return '推理'
|
return t('推理')
|
||||||
case BookBackTaskType.TRANSLATE:
|
case BookBackTaskType.TRANSLATE:
|
||||||
return '翻译'
|
return t('翻译')
|
||||||
case BookBackTaskType.RUNWAY_VIDEO:
|
case BookBackTaskType.RUNWAY_VIDEO:
|
||||||
return 'runway生成视频'
|
return t('runway生成视频')
|
||||||
case BookBackTaskType.LUMA_VIDEO:
|
case BookBackTaskType.LUMA_VIDEO:
|
||||||
return 'luma生成视频'
|
return t('luma生成视频')
|
||||||
case BookBackTaskType.KLING_VIDEO:
|
case BookBackTaskType.KLING_VIDEO:
|
||||||
return 'kling生成视频'
|
return t('kling生成视频')
|
||||||
default:
|
default:
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
@ -377,199 +378,199 @@ export function GetBookTaskDetailStatusLabel(key: string): TaskModal.TaskStatus
|
|||||||
case BookTaskStatus.WAIT:
|
case BookTaskStatus.WAIT:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.WAIT,
|
status: BookTaskStatus.WAIT,
|
||||||
label: '等待',
|
label: t('等待'),
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.STORYBOARD:
|
case BookTaskStatus.STORYBOARD:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.STORYBOARD,
|
status: BookTaskStatus.STORYBOARD,
|
||||||
label: '分镜计算中',
|
label: t('分镜计算中'),
|
||||||
type: 'info'
|
type: 'info'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.STORYBOARD_FAIL:
|
case BookTaskStatus.STORYBOARD_FAIL:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.STORYBOARD_FAIL,
|
status: BookTaskStatus.STORYBOARD_FAIL,
|
||||||
label: '分镜计算失败',
|
label: t('分镜计算失败'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.STORYBOARD_DONE:
|
case BookTaskStatus.STORYBOARD_DONE:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.STORYBOARD_DONE,
|
status: BookTaskStatus.STORYBOARD_DONE,
|
||||||
label: '分镜计算完成',
|
label: t('分镜计算完成'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.SPLIT:
|
case BookTaskStatus.SPLIT:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.SPLIT,
|
status: BookTaskStatus.SPLIT,
|
||||||
label: '分割视频中',
|
label: t('分割视频中'),
|
||||||
type: 'info'
|
type: 'info'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.SPLIT_FAIL:
|
case BookTaskStatus.SPLIT_FAIL:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.SPLIT_FAIL,
|
status: BookTaskStatus.SPLIT_FAIL,
|
||||||
label: '分割视频失败',
|
label: t('分割视频失败'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.SPLIT_DONE:
|
case BookTaskStatus.SPLIT_DONE:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.SPLIT_DONE,
|
status: BookTaskStatus.SPLIT_DONE,
|
||||||
label: '分割视频完成',
|
label: t('分割视频完成'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.AUDIO:
|
case BookTaskStatus.AUDIO:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.AUDIO,
|
status: BookTaskStatus.AUDIO,
|
||||||
label: '提取音频中',
|
label: t('提取音频中'),
|
||||||
type: 'info'
|
type: 'info'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.AUDIO_FAIL:
|
case BookTaskStatus.AUDIO_FAIL:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.AUDIO_FAIL,
|
status: BookTaskStatus.AUDIO_FAIL,
|
||||||
label: '提取音频失败',
|
label: t('提取音频失败'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.AUDIO_DONE:
|
case BookTaskStatus.AUDIO_DONE:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.AUDIO_DONE,
|
status: BookTaskStatus.AUDIO_DONE,
|
||||||
label: '提取音频完成',
|
label: t('提取音频完成'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.RECOGNIZE:
|
case BookTaskStatus.RECOGNIZE:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.RECOGNIZE,
|
status: BookTaskStatus.RECOGNIZE,
|
||||||
label: '识别字幕中',
|
label: t('识别字幕中'),
|
||||||
type: 'info'
|
type: 'info'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.RECOGNIZE_FAIL:
|
case BookTaskStatus.RECOGNIZE_FAIL:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.RECOGNIZE_FAIL,
|
status: BookTaskStatus.RECOGNIZE_FAIL,
|
||||||
label: '识别字幕失败',
|
label: t('识别字幕失败'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.RECOGNIZE_DONE:
|
case BookTaskStatus.RECOGNIZE_DONE:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.RECOGNIZE_DONE,
|
status: BookTaskStatus.RECOGNIZE_DONE,
|
||||||
label: '识别字幕完成',
|
label: t('识别字幕完成'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.FRAME:
|
case BookTaskStatus.FRAME:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.FRAME,
|
status: BookTaskStatus.FRAME,
|
||||||
label: '抽帧中',
|
label: t('抽帧中'),
|
||||||
type: 'info'
|
type: 'info'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.FRAME_FAIL:
|
case BookTaskStatus.FRAME_FAIL:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.FRAME_FAIL,
|
status: BookTaskStatus.FRAME_FAIL,
|
||||||
label: '抽帧失败',
|
label: t('抽帧失败'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.FRAME_DONE:
|
case BookTaskStatus.FRAME_DONE:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.FRAME_DONE,
|
status: BookTaskStatus.FRAME_DONE,
|
||||||
label: '抽帧完成',
|
label: t('抽帧完成'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.REVERSE:
|
case BookTaskStatus.REVERSE:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.REVERSE,
|
status: BookTaskStatus.REVERSE,
|
||||||
label: '反推中',
|
label: t('反推中'),
|
||||||
type: 'info'
|
type: 'info'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.REVERSE_FAIL:
|
case BookTaskStatus.REVERSE_FAIL:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.REVERSE_FAIL,
|
status: BookTaskStatus.REVERSE_FAIL,
|
||||||
label: '反推失败',
|
label: t('反推失败'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.REVERSE_DONE:
|
case BookTaskStatus.REVERSE_DONE:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.REVERSE_DONE,
|
status: BookTaskStatus.REVERSE_DONE,
|
||||||
label: '反推完成',
|
label: t('反推完成'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.IMAGE:
|
case BookTaskStatus.IMAGE:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.IMAGE,
|
status: BookTaskStatus.IMAGE,
|
||||||
label: '生成图片中',
|
label: t('生成图片中'),
|
||||||
type: 'info'
|
type: 'info'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.IMAGE_FAIL:
|
case BookTaskStatus.IMAGE_FAIL:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.IMAGE_FAIL,
|
status: BookTaskStatus.IMAGE_FAIL,
|
||||||
label: '生成图片失败',
|
label: t('生成图片失败'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.IMAGE_DONE:
|
case BookTaskStatus.IMAGE_DONE:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.IMAGE_DONE,
|
status: BookTaskStatus.IMAGE_DONE,
|
||||||
label: '生成图片完成',
|
label: t('生成图片完成'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.HD:
|
case BookTaskStatus.HD:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.HD,
|
status: BookTaskStatus.HD,
|
||||||
label: '高清中',
|
label: t('高清中'),
|
||||||
type: 'info'
|
type: 'info'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.HD_FAIL:
|
case BookTaskStatus.HD_FAIL:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.HD_FAIL,
|
status: BookTaskStatus.HD_FAIL,
|
||||||
label: '高清失败',
|
label: t('高清失败'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.HD_DONE:
|
case BookTaskStatus.HD_DONE:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.HD_DONE,
|
status: BookTaskStatus.HD_DONE,
|
||||||
label: '高清完成',
|
label: t('高清完成'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.COMPOSING:
|
case BookTaskStatus.COMPOSING:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.COMPOSING,
|
status: BookTaskStatus.COMPOSING,
|
||||||
label: '合成视频中',
|
label: t('合成视频中'),
|
||||||
type: 'info'
|
type: 'info'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.COMPOSING_FAIL:
|
case BookTaskStatus.COMPOSING_FAIL:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.COMPOSING_FAIL,
|
status: BookTaskStatus.COMPOSING_FAIL,
|
||||||
label: '合成视频失败',
|
label: t('合成视频失败'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.COMPOSING_DONE:
|
case BookTaskStatus.COMPOSING_DONE:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.COMPOSING_DONE,
|
status: BookTaskStatus.COMPOSING_DONE,
|
||||||
label: '合成视频完成',
|
label: t('合成视频完成'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.DRAFT_DONE:
|
case BookTaskStatus.DRAFT_DONE:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.DRAFT_DONE,
|
status: BookTaskStatus.DRAFT_DONE,
|
||||||
label: '添加草稿完成',
|
label: t('添加草稿完成'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.DRAFT_FAIL:
|
case BookTaskStatus.DRAFT_FAIL:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.DRAFT_FAIL,
|
status: BookTaskStatus.DRAFT_FAIL,
|
||||||
label: '添加草稿失败',
|
label: t('添加草稿失败'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.IMAGE_TO_VIDEO_ERROR:
|
case BookTaskStatus.IMAGE_TO_VIDEO_ERROR:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.IMAGE_TO_VIDEO_ERROR,
|
status: BookTaskStatus.IMAGE_TO_VIDEO_ERROR,
|
||||||
label: '图转视频失败',
|
label: t('图转视频失败'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
}
|
}
|
||||||
case BookTaskStatus.IMAGE_TO_VIDEO_SUCCESS:
|
case BookTaskStatus.IMAGE_TO_VIDEO_SUCCESS:
|
||||||
return {
|
return {
|
||||||
status: BookTaskStatus.IMAGE_TO_VIDEO_SUCCESS,
|
status: BookTaskStatus.IMAGE_TO_VIDEO_SUCCESS,
|
||||||
label: '图转视频成功',
|
label: t('图转视频成功'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
status: 'UNKNOWN',
|
status: 'UNKNOWN',
|
||||||
label: '未知',
|
label: t('未知'),
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -585,43 +586,43 @@ export function GetBookBackTaskStatusLabel(key: string): TaskModal.TaskStatus {
|
|||||||
case BookBackTaskStatus.WAIT:
|
case BookBackTaskStatus.WAIT:
|
||||||
return {
|
return {
|
||||||
status: BookBackTaskStatus.WAIT,
|
status: BookBackTaskStatus.WAIT,
|
||||||
label: '等待',
|
label: t('等待'),
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}
|
}
|
||||||
case BookBackTaskStatus.RUNNING:
|
case BookBackTaskStatus.RUNNING:
|
||||||
return {
|
return {
|
||||||
status: BookBackTaskStatus.RUNNING,
|
status: BookBackTaskStatus.RUNNING,
|
||||||
label: '运行中',
|
label: t('运行中'),
|
||||||
type: 'info'
|
type: 'info'
|
||||||
}
|
}
|
||||||
case BookBackTaskStatus.PAUSE:
|
case BookBackTaskStatus.PAUSE:
|
||||||
return {
|
return {
|
||||||
status: BookBackTaskStatus.PAUSE,
|
status: BookBackTaskStatus.PAUSE,
|
||||||
label: '暂停',
|
label: t('暂停'),
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}
|
}
|
||||||
case BookBackTaskStatus.DONE:
|
case BookBackTaskStatus.DONE:
|
||||||
return {
|
return {
|
||||||
status: BookBackTaskStatus.DONE,
|
status: BookBackTaskStatus.DONE,
|
||||||
label: '完成',
|
label: t('完成'),
|
||||||
type: 'success'
|
type: 'success'
|
||||||
}
|
}
|
||||||
case BookBackTaskStatus.FAIL:
|
case BookBackTaskStatus.FAIL:
|
||||||
return {
|
return {
|
||||||
status: BookBackTaskStatus.FAIL,
|
status: BookBackTaskStatus.FAIL,
|
||||||
label: '失败',
|
label: t('失败'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
}
|
}
|
||||||
case BookBackTaskStatus.RECONNECT:
|
case BookBackTaskStatus.RECONNECT:
|
||||||
return {
|
return {
|
||||||
status: BookBackTaskStatus.RECONNECT,
|
status: BookBackTaskStatus.RECONNECT,
|
||||||
label: '重连',
|
label: t('重连'),
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
status: 'UNKNOWN',
|
status: 'UNKNOWN',
|
||||||
label: '未知',
|
label: t('未知'),
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { t } from "@/i18n";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 剪映关键帧类型枚举
|
* 剪映关键帧类型枚举
|
||||||
*
|
*
|
||||||
@ -32,19 +34,19 @@ export enum JianyingKeyFrameEnum {
|
|||||||
export function getJianyingKeyFrameOptions(): Array<{ label: string; value: JianyingKeyFrameEnum }> {
|
export function getJianyingKeyFrameOptions(): Array<{ label: string; value: JianyingKeyFrameEnum }> {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: '上下关键帧',
|
label: t('上下关键帧'),
|
||||||
value: JianyingKeyFrameEnum.KFTypePositionY
|
value: JianyingKeyFrameEnum.KFTypePositionY
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '左右关键帧',
|
label: t('左右关键帧'),
|
||||||
value: JianyingKeyFrameEnum.KFTypePositionX
|
value: JianyingKeyFrameEnum.KFTypePositionX
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '缩放关键帧',
|
label: t('缩放关键帧'),
|
||||||
value: JianyingKeyFrameEnum.KFTypeScale
|
value: JianyingKeyFrameEnum.KFTypeScale
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '随机关键帧',
|
label: t('随机关键帧'),
|
||||||
value: JianyingKeyFrameEnum.KFTypeRandom
|
value: JianyingKeyFrameEnum.KFTypeRandom
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -103,5 +103,12 @@ export const OptionKeyName = {
|
|||||||
|
|
||||||
/** Comfy UI 基础设置 */
|
/** Comfy UI 基础设置 */
|
||||||
ComfyUISimpleSetting: 'SD_ComfyUISimpleSetting'
|
ComfyUISimpleSetting: 'SD_ComfyUISimpleSetting'
|
||||||
|
},
|
||||||
|
Video: {
|
||||||
|
/** 是否开启右侧控制面板 */
|
||||||
|
ShowRightPanel: 'Video_ShowRightPanel',
|
||||||
|
|
||||||
|
/** 是否开启分页 */
|
||||||
|
ShowPagination: 'Video_ShowPagination'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,30 +53,31 @@ export enum SoftColor {
|
|||||||
|
|
||||||
// 错误红色
|
// 错误红色
|
||||||
ERROR_RED = '#c8161d'
|
ERROR_RED = '#c8161d'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ResponseMessageType {
|
export enum ResponseMessageType {
|
||||||
GET_TEXT = 'getText', // 获取文案
|
GET_TEXT = 'getText', // 获取文案
|
||||||
REMOVE_WATERMARK = "REMOVE_WATERMARK",// 删除水印
|
REMOVE_WATERMARK = 'REMOVE_WATERMARK', // 删除水印
|
||||||
MJ_REVERSE = 'MJ_REVERSE',// MJ反推,返回反推结果
|
MJ_REVERSE = 'MJ_REVERSE', // MJ反推,返回反推结果
|
||||||
SD_REVERSE = 'SD_REVERSE',// MJ反推,返回反推结果
|
SD_REVERSE = 'SD_REVERSE', // MJ反推,返回反推结果
|
||||||
REVERSE_PROMPT_TRANSLATE = 'REVERSE_PROMPT_TRANSLATE',// 反推提示词翻译
|
REVERSE_PROMPT_TRANSLATE = 'REVERSE_PROMPT_TRANSLATE', // 反推提示词翻译
|
||||||
GPT_PROMPT_TRANSLATE = 'GPT_PROMPT_TRANSLATE', // GPT提示词翻译
|
GPT_PROMPT_TRANSLATE = 'GPT_PROMPT_TRANSLATE', // GPT提示词翻译
|
||||||
MJ_IMAGE = 'MJ_IMAGE',// MJ 生成图片
|
MJ_IMAGE = 'MJ_IMAGE', // MJ 生成图片
|
||||||
ComfyUI_IMAGE = 'ComfyUI_IMAGE',// ComfyUI 生成图片
|
ComfyUI_IMAGE = 'ComfyUI_IMAGE', // ComfyUI 生成图片
|
||||||
HD_IMAGE = 'HD_IMAGE',// HD 生成图片
|
HD_IMAGE = 'HD_IMAGE', // HD 生成图片
|
||||||
RUNWAY_VIDEO = "RUNWAY_VIDEO",// Runway生成视频
|
RUNWAY_VIDEO = 'RUNWAY_VIDEO', // Runway生成视频
|
||||||
LUMA_VIDEO = "LUMA_VIDEO",// Luma生成视频
|
LUMA_VIDEO = 'LUMA_VIDEO', // Luma生成视频
|
||||||
KLING_VIDEO = "KLING_VIDEO",// Kling生成视频
|
KLING_VIDEO = 'KLING_VIDEO', // Kling生成视频
|
||||||
VIDEO_SUCESS = "VIDEO_SUCESS" //视频生成成功
|
MJ_VIDEO = 'MJ_VIDEO', // MJ生成视频
|
||||||
|
MJ_VIDEO_EXTEND = 'MJ_VIDEO_EXTEND', // MJ生成视频拓展
|
||||||
|
VIDEO_SUCESS = 'VIDEO_SUCESS' //视频生成成功
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LaiAPIType {
|
export enum LaiAPIType {
|
||||||
// 主要的
|
// 主要的
|
||||||
MAIN = "main",
|
MAIN = 'main',
|
||||||
// 香港代理
|
// 香港代理
|
||||||
HK_PROXY = "hk-proxy",
|
HK_PROXY = 'hk-proxy',
|
||||||
// 备用站点
|
// 备用站点
|
||||||
BAK_MAIN = 'bak-main'
|
BAK_MAIN = 'bak-main'
|
||||||
}
|
}
|
||||||
@ -84,6 +85,5 @@ export enum LaiAPIType {
|
|||||||
// 同步GPTkey的分类
|
// 同步GPTkey的分类
|
||||||
export enum SyncGptKeyType {
|
export enum SyncGptKeyType {
|
||||||
// 字幕设置
|
// 字幕设置
|
||||||
SUBTITLE_SETTING = 'subtitle_setting',
|
SUBTITLE_SETTING = 'subtitle_setting'
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,39 +1,38 @@
|
|||||||
|
|
||||||
//#region 图转视频类型
|
//#region 图转视频类型
|
||||||
|
|
||||||
import { BookBackTaskType } from "./bookEnum";
|
import { t } from '@/i18n'
|
||||||
|
import { BookBackTaskType } from './bookEnum'
|
||||||
|
|
||||||
/** 图片转视频的方式 */
|
/** 图片转视频的方式 */
|
||||||
export enum ImageToVideoModels {
|
export enum ImageToVideoModels {
|
||||||
/** runway 生成视频 */
|
/** runway 生成视频 */
|
||||||
RUNWAY = "RUNWAY",
|
RUNWAY = 'RUNWAY',
|
||||||
/** luma 生成视频 */
|
/** luma 生成视频 */
|
||||||
LUMA = "LUMA",
|
LUMA = 'LUMA',
|
||||||
/** 可灵生成视频 */
|
/** 可灵生成视频 */
|
||||||
KLING = "KLING",
|
KLING = 'KLING',
|
||||||
/** Pika 生成视频 */
|
/** Pika 生成视频 */
|
||||||
PIKA = "PIKA",
|
PIKA = 'PIKA',
|
||||||
/** MJ 图转视频 */
|
/** MJ 图转视频 */
|
||||||
MJ_VIDEO = "MJ_VIDEO",
|
MJ_VIDEO = 'MJ_VIDEO',
|
||||||
/** MJ 视频拓展 */
|
/** MJ 视频拓展 */
|
||||||
MJ_VIDEO_EXTEND = "MJ_VIDEO_EXTEND"
|
MJ_VIDEO_EXTEND = 'MJ_VIDEO_EXTEND'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const MappingTaskTypeToVideoModel = (type: BookBackTaskType | string) => {
|
export const MappingTaskTypeToVideoModel = (type: BookBackTaskType | string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case BookBackTaskType.LUMA_VIDEO:
|
case BookBackTaskType.LUMA_VIDEO:
|
||||||
return ImageToVideoModels.LUMA;
|
return ImageToVideoModels.LUMA
|
||||||
case BookBackTaskType.RUNWAY_VIDEO:
|
case BookBackTaskType.RUNWAY_VIDEO:
|
||||||
return ImageToVideoModels.RUNWAY;
|
return ImageToVideoModels.RUNWAY
|
||||||
case BookBackTaskType.KLING_VIDEO:
|
case BookBackTaskType.KLING_VIDEO:
|
||||||
return ImageToVideoModels.KLING;
|
return ImageToVideoModels.KLING
|
||||||
case BookBackTaskType.MJ_VIDEO:
|
case BookBackTaskType.MJ_VIDEO:
|
||||||
return ImageToVideoModels.MJ_VIDEO;
|
return ImageToVideoModels.MJ_VIDEO
|
||||||
case BookBackTaskType.MJ_VIDEO_EXTEND:
|
case BookBackTaskType.MJ_VIDEO_EXTEND:
|
||||||
return ImageToVideoModels.MJ_VIDEO_EXTEND;
|
return ImageToVideoModels.MJ_VIDEO_EXTEND
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN"
|
return 'UNKNOWN'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,17 +44,17 @@ export const MappingTaskTypeToVideoModel = (type: BookBackTaskType | string) =>
|
|||||||
export const GetImageToVideoModelsLabel = (model: ImageToVideoModels | string) => {
|
export const GetImageToVideoModelsLabel = (model: ImageToVideoModels | string) => {
|
||||||
switch (model) {
|
switch (model) {
|
||||||
case ImageToVideoModels.RUNWAY:
|
case ImageToVideoModels.RUNWAY:
|
||||||
return "Runway";
|
return 'Runway'
|
||||||
case ImageToVideoModels.LUMA:
|
case ImageToVideoModels.LUMA:
|
||||||
return "Luma";
|
return 'Luma'
|
||||||
case ImageToVideoModels.KLING:
|
case ImageToVideoModels.KLING:
|
||||||
return "可灵";
|
return t('可灵')
|
||||||
case ImageToVideoModels.PIKA:
|
case ImageToVideoModels.PIKA:
|
||||||
return "Pika";
|
return 'Pika'
|
||||||
case ImageToVideoModels.MJ_VIDEO:
|
case ImageToVideoModels.MJ_VIDEO:
|
||||||
return "MJ视频";
|
return t('MJ视频')
|
||||||
default:
|
default:
|
||||||
return "未知";
|
return '未知'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,55 +69,63 @@ export const GetImageToVideoModelsLabel = (model: ImageToVideoModels | string) =
|
|||||||
*/
|
*/
|
||||||
export const GetImageToVideoModelsOptions = () => {
|
export const GetImageToVideoModelsOptions = () => {
|
||||||
return [
|
return [
|
||||||
{ label: GetImageToVideoModelsLabel(ImageToVideoModels.MJ_VIDEO), value: ImageToVideoModels.MJ_VIDEO },
|
{
|
||||||
{ label: GetImageToVideoModelsLabel(ImageToVideoModels.RUNWAY), value: ImageToVideoModels.RUNWAY },
|
label: GetImageToVideoModelsLabel(ImageToVideoModels.MJ_VIDEO),
|
||||||
|
value: ImageToVideoModels.MJ_VIDEO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: GetImageToVideoModelsLabel(ImageToVideoModels.RUNWAY),
|
||||||
|
value: ImageToVideoModels.RUNWAY
|
||||||
|
},
|
||||||
{ label: GetImageToVideoModelsLabel(ImageToVideoModels.LUMA), value: ImageToVideoModels.LUMA },
|
{ label: GetImageToVideoModelsLabel(ImageToVideoModels.LUMA), value: ImageToVideoModels.LUMA },
|
||||||
{ label: GetImageToVideoModelsLabel(ImageToVideoModels.KLING), value: ImageToVideoModels.KLING },
|
{
|
||||||
{ label: GetImageToVideoModelsLabel(ImageToVideoModels.PIKA), value: ImageToVideoModels.PIKA },
|
label: GetImageToVideoModelsLabel(ImageToVideoModels.KLING),
|
||||||
|
value: ImageToVideoModels.KLING
|
||||||
|
},
|
||||||
|
{ label: GetImageToVideoModelsLabel(ImageToVideoModels.PIKA), value: ImageToVideoModels.PIKA }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
|
||||||
//#region 通用
|
//#region 通用
|
||||||
|
|
||||||
/** 生成视频的方式 */
|
/** 生成视频的方式 */
|
||||||
export enum VideoModel {
|
export enum VideoModel {
|
||||||
/** 文生视频 */
|
/** 文生视频 */
|
||||||
TEXT_TO_VIDEO = "textToVideo",
|
TEXT_TO_VIDEO = 'textToVideo',
|
||||||
/** 图生视频 */
|
/** 图生视频 */
|
||||||
IMAGE_TO_VIDEO = "imageToVideo",
|
IMAGE_TO_VIDEO = 'imageToVideo'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 图转视频的状态 */
|
/** 图转视频的状态 */
|
||||||
export enum VideoStatus {
|
export enum VideoStatus {
|
||||||
/** 等待 */
|
/** 等待 */
|
||||||
WAIT = "wait",
|
WAIT = 'wait',
|
||||||
/** 处理中 */
|
/** 处理中 */
|
||||||
PROCESSING = "processing",
|
PROCESSING = 'processing',
|
||||||
/** 完成 */
|
/** 完成 */
|
||||||
SUCCESS = "success",
|
SUCCESS = 'success',
|
||||||
/** 失败 */
|
/** 失败 */
|
||||||
FAIL = "fail",
|
FAIL = 'fail'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GetVideoStatus = (status: VideoStatus | string) => {
|
export const GetVideoStatus = (status: VideoStatus | string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case VideoStatus.WAIT:
|
case VideoStatus.WAIT:
|
||||||
case "0":
|
case '0':
|
||||||
return "等待";
|
return t('等待')
|
||||||
case VideoStatus.PROCESSING:
|
case VideoStatus.PROCESSING:
|
||||||
case "1":
|
case '1':
|
||||||
return "处理中";
|
return t('处理中')
|
||||||
case VideoStatus.SUCCESS:
|
case VideoStatus.SUCCESS:
|
||||||
case "3":
|
case '3':
|
||||||
return "完成";
|
return t('完成')
|
||||||
case VideoStatus.FAIL:
|
case VideoStatus.FAIL:
|
||||||
case '2':
|
case '2':
|
||||||
return "失败";
|
return t('失败')
|
||||||
default:
|
default:
|
||||||
return "未知";
|
return t('未知')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,14 +135,14 @@ export const GetVideoStatus = (status: VideoStatus | string) => {
|
|||||||
|
|
||||||
/** runway 生成视频的模型 */
|
/** runway 生成视频的模型 */
|
||||||
export enum RunawayModel {
|
export enum RunawayModel {
|
||||||
GNE2 = "gen2",
|
GNE2 = 'gen2',
|
||||||
GNE3 = "gen3",
|
GNE3 = 'gen3'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** runway 合成视频的时长 */
|
/** runway 合成视频的时长 */
|
||||||
export enum RunwaySeconds {
|
export enum RunwaySeconds {
|
||||||
FIVE = 5,
|
FIVE = 5,
|
||||||
TEN = 10,
|
TEN = 10
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
@ -143,9 +150,9 @@ export enum RunwaySeconds {
|
|||||||
|
|
||||||
export enum KlingMode {
|
export enum KlingMode {
|
||||||
/** 高性能 */
|
/** 高性能 */
|
||||||
STD = "std",
|
STD = 'std',
|
||||||
/** 高表现 */
|
/** 高表现 */
|
||||||
PRO = "pro"
|
PRO = 'pro'
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
@ -156,24 +163,25 @@ export enum KlingMode {
|
|||||||
* 对视频任务进行操作。不为空时,index、taskId必填
|
* 对视频任务进行操作。不为空时,index、taskId必填
|
||||||
*/
|
*/
|
||||||
export enum MJVideoAction {
|
export enum MJVideoAction {
|
||||||
Extend = "extend",
|
Extend = 'extend'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 首帧图片,扩展时可为空
|
* 首帧图片,扩展时可为空
|
||||||
*/
|
*/
|
||||||
export enum MJVideoImageType {
|
export enum MJVideoImageType {
|
||||||
Base64 = "base64",
|
Base64 = 'base64',
|
||||||
Url = "url",
|
Url = 'url'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MJ Video的动作幅度
|
* MJ Video的动作幅度
|
||||||
*/
|
*/
|
||||||
export enum MJVideoMotion {
|
export enum MJVideoMotion {
|
||||||
High = "high",
|
High = 'high',
|
||||||
Low = "low",
|
Low = 'low'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取MJ视频动作幅度的标签
|
* 获取MJ视频动作幅度的标签
|
||||||
*
|
*
|
||||||
@ -183,11 +191,11 @@ export enum MJVideoMotion {
|
|||||||
export function GetMJVideoMotionLabel(model: MJVideoMotion | string) {
|
export function GetMJVideoMotionLabel(model: MJVideoMotion | string) {
|
||||||
switch (model) {
|
switch (model) {
|
||||||
case MJVideoMotion.High:
|
case MJVideoMotion.High:
|
||||||
return "高 (High)";
|
return t('高 (High)')
|
||||||
case MJVideoMotion.Low:
|
case MJVideoMotion.Low:
|
||||||
return "低 (Low)";
|
return t('低 (Low)')
|
||||||
default:
|
default:
|
||||||
return "无效"
|
return t('未知')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,9 +207,107 @@ export function GetMJVideoMotionLabel(model: MJVideoMotion | string) {
|
|||||||
export function GetMJVideoMotionOptions() {
|
export function GetMJVideoMotionOptions() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: GetMJVideoMotionLabel(MJVideoMotion.Low), value: MJVideoMotion.Low
|
label: GetMJVideoMotionLabel(MJVideoMotion.Low),
|
||||||
}, {
|
value: MJVideoMotion.Low
|
||||||
label: GetMJVideoMotionLabel(MJVideoMotion.High), value: MJVideoMotion.High
|
},
|
||||||
|
{
|
||||||
|
label: GetMJVideoMotionLabel(MJVideoMotion.High),
|
||||||
|
value: MJVideoMotion.High
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MJ Video 视频质量类型
|
||||||
|
*/
|
||||||
|
export enum MJVideoType {
|
||||||
|
/** 标清 480p */
|
||||||
|
SD = 'vid_1.1_i2v_480',
|
||||||
|
/** 高清 720p */
|
||||||
|
HD = 'vid_1.1_i2v_720'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取MJ视频质量类型的标签
|
||||||
|
*
|
||||||
|
* @param type MJ视频质量类型枚举值或字符串
|
||||||
|
* @returns 返回对应的中英文标签
|
||||||
|
*/
|
||||||
|
export function GetMJVideoTypeLabel(type: MJVideoType | string) {
|
||||||
|
switch (type) {
|
||||||
|
case MJVideoType.SD:
|
||||||
|
return t('标清 (SD 480p)')
|
||||||
|
case MJVideoType.HD:
|
||||||
|
return t('高清 (HD 720p)')
|
||||||
|
default:
|
||||||
|
return t('未知')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取MJ视频质量类型的选项列表
|
||||||
|
*
|
||||||
|
* @returns 返回包含标签和值的选项数组,用于下拉选择框等UI组件
|
||||||
|
*/
|
||||||
|
export function GetMJVideoTypeOptions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: GetMJVideoTypeLabel(MJVideoType.SD),
|
||||||
|
value: MJVideoType.SD
|
||||||
|
},
|
||||||
|
{ label: GetMJVideoTypeLabel(MJVideoType.HD), value: MJVideoType.HD }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MJ Video 批次大小
|
||||||
|
*/
|
||||||
|
export enum MJVideoBatchSize {
|
||||||
|
/** 生成1个视频 */
|
||||||
|
ONE = 1,
|
||||||
|
/** 生成2个视频 */
|
||||||
|
TWO = 2,
|
||||||
|
/** 生成4个视频 */
|
||||||
|
FOUR = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取MJ视频批次大小的标签
|
||||||
|
*
|
||||||
|
* @param batchSize MJ视频批次大小枚举值或数字
|
||||||
|
* @returns 返回对应的中文标签
|
||||||
|
*/
|
||||||
|
export function GetMJVideoBatchSizeLabel(batchSize: MJVideoBatchSize | number) {
|
||||||
|
switch (batchSize) {
|
||||||
|
case MJVideoBatchSize.ONE:
|
||||||
|
return t('1个视频')
|
||||||
|
case MJVideoBatchSize.TWO:
|
||||||
|
return t('2个视频')
|
||||||
|
case MJVideoBatchSize.FOUR:
|
||||||
|
return t('4个视频')
|
||||||
|
default:
|
||||||
|
return t('未知')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取MJ视频批次大小的选项列表
|
||||||
|
*
|
||||||
|
* @returns 返回包含标签和值的选项数组,用于下拉选择框等UI组件
|
||||||
|
*/
|
||||||
|
export function GetMJVideoBatchSizeOptions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: GetMJVideoBatchSizeLabel(MJVideoBatchSize.ONE),
|
||||||
|
value: MJVideoBatchSize.ONE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: GetMJVideoBatchSizeLabel(MJVideoBatchSize.TWO),
|
||||||
|
value: MJVideoBatchSize.TWO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: GetMJVideoBatchSizeLabel(MJVideoBatchSize.FOUR),
|
||||||
|
value: MJVideoBatchSize.FOUR
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/define/ipc/subIpc/bookIPC/bookVideoIpc.ts
Normal file
27
src/define/ipc/subIpc/bookIPC/bookVideoIpc.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { DEFINE_STRING } from '@/define/ipcDefineString'
|
||||||
|
import { ipcMain } from 'electron'
|
||||||
|
import { bookHandle } from '@/main/service/book'
|
||||||
|
import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
||||||
|
|
||||||
|
export function bookVideoIpc() {
|
||||||
|
// 处理获取小说图转视频信息列表的请求
|
||||||
|
ipcMain.handle(
|
||||||
|
DEFINE_STRING.BOOK.GET_VIDEO_BOOK_INFO_LIST,
|
||||||
|
async (_, condition: BookVideo.BookVideoInfoListQuertCondition) =>
|
||||||
|
await bookHandle.GetVideoBookInfoList(condition)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 处理初始化视频消息数据的请求
|
||||||
|
ipcMain.handle(
|
||||||
|
DEFINE_STRING.BOOK.INIT_VIDEO_MESSAGE,
|
||||||
|
async (_, bookTaskId: string) =>
|
||||||
|
await bookHandle.InitVideoMessage(bookTaskId)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 处理更新小说分镜视频消息的请求
|
||||||
|
ipcMain.handle(
|
||||||
|
DEFINE_STRING.BOOK.UPDATE_BOOK_TASK_DETAIL_VIDEO_MESSAGE,
|
||||||
|
async (_, bookTaskDetailId: string, videoMessage: Partial<BookTaskDetail.VideoMessage>) =>
|
||||||
|
await bookHandle.UpdateBookTaskDetailVideoMessage(bookTaskDetailId, videoMessage)
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import { bookTaskDetailIpc } from './bookIPC/bookTaskDetailIpc'
|
|||||||
import { bookPromptIpc } from './bookIPC/bookPromptIpc'
|
import { bookPromptIpc } from './bookIPC/bookPromptIpc'
|
||||||
import { bookImageIpc } from './bookIPC/bookImageIpc'
|
import { bookImageIpc } from './bookIPC/bookImageIpc'
|
||||||
import { bookExportIpc } from './bookIPC/bookExportIpc'
|
import { bookExportIpc } from './bookIPC/bookExportIpc'
|
||||||
|
import { bookVideoIpc } from './bookIPC/bookVideoIpc'
|
||||||
|
|
||||||
function BookIpc() {
|
function BookIpc() {
|
||||||
// 小说数据相关
|
// 小说数据相关
|
||||||
@ -23,6 +24,9 @@ function BookIpc() {
|
|||||||
|
|
||||||
// 小说导出相关
|
// 小说导出相关
|
||||||
bookExportIpc()
|
bookExportIpc()
|
||||||
|
|
||||||
|
// 小说视频 相关
|
||||||
|
bookVideoIpc()
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BookIpc
|
export default BookIpc
|
||||||
|
|||||||
@ -127,6 +127,12 @@ function SystemIpc() {
|
|||||||
async (_, extensions?: string[]) => await electronInterface.SelectFolderOrFile(extensions)
|
async (_, extensions?: string[]) => await electronInterface.SelectFolderOrFile(extensions)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** 读取文本文件内容 */
|
||||||
|
ipcMain.handle(
|
||||||
|
DEFINE_STRING.SYSTEM.READ_TEXT_FILE,
|
||||||
|
async (_, filePath: string) => await electronInterface.ReadTextFile(filePath)
|
||||||
|
)
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { DEFINE_STRING } from '@/define/ipcDefineString'
|
import { DEFINE_STRING } from '@/define/ipcDefineString'
|
||||||
import { ipcMain } from 'electron'
|
import { ipcMain } from 'electron'
|
||||||
import { TaskHandle } from '@/main/service/task'
|
import { TaskHandle } from '@/main/service/task'
|
||||||
import { BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum'
|
import { TaskModal } from '@/define/model/task'
|
||||||
|
|
||||||
let taskHandle: TaskHandle = new TaskHandle()
|
let taskHandle: TaskHandle = new TaskHandle()
|
||||||
|
|
||||||
@ -15,23 +15,7 @@ function TaskIpc() {
|
|||||||
/** 添加单个个任务 */
|
/** 添加单个个任务 */
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
DEFINE_STRING.TASK.ADD_ONE_TASK,
|
DEFINE_STRING.TASK.ADD_ONE_TASK,
|
||||||
async (
|
async (_, task: TaskModal.Task) => await taskHandle.AddOneTask(task)
|
||||||
_,
|
|
||||||
bookId: string,
|
|
||||||
taskType: BookBackTaskType,
|
|
||||||
executeType: TaskExecuteType = TaskExecuteType.AUTO,
|
|
||||||
bookTaskId: string | undefined = undefined,
|
|
||||||
bookTaskDetailId: string | undefined = undefined,
|
|
||||||
responseMessageName?: string
|
|
||||||
) =>
|
|
||||||
await taskHandle.AddOneTask(
|
|
||||||
bookId,
|
|
||||||
taskType,
|
|
||||||
executeType,
|
|
||||||
bookTaskId,
|
|
||||||
bookTaskDetailId,
|
|
||||||
responseMessageName
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 添加多个任务 */
|
/** 添加多个任务 */
|
||||||
|
|||||||
@ -138,9 +138,26 @@ const BOOK = {
|
|||||||
DOWNLOAD_IMAGE_URL_AND_SPLIT: 'DOWNLOAD_IMAGE_URL_AND_SPLIT',
|
DOWNLOAD_IMAGE_URL_AND_SPLIT: 'DOWNLOAD_IMAGE_URL_AND_SPLIT',
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 视频相关
|
//#region 导出相关
|
||||||
/** 添加剪映草稿 */
|
/** 添加剪映草稿 */
|
||||||
ADD_JIANYING_DRAFT: 'ADD_JIANYING_DRAFT'
|
ADD_JIANYING_DRAFT: 'ADD_JIANYING_DRAFT',
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
// #region 视频相关
|
||||||
|
|
||||||
|
/** 获取指定的条件的图转视频的数据,包含字批次 */
|
||||||
|
GET_VIDEO_BOOK_INFO_LIST: 'GET_VIDEO_BOOK_INFO_LIST',
|
||||||
|
|
||||||
|
/** 初始化视频消息数据 */
|
||||||
|
INIT_VIDEO_MESSAGE: 'INIT_VIDEO_MESSAGE',
|
||||||
|
|
||||||
|
/** 更新小说分镜的视频消息 */
|
||||||
|
UPDATE_BOOK_TASK_DETAIL_VIDEO_MESSAGE: 'UPDATE_BOOK_TASK_DETAIL_VIDEO_MESSAGE',
|
||||||
|
|
||||||
|
/** MJ VIDEO 图转视频返回前端数据任务 */
|
||||||
|
MJ_VIDEO_TO_VIDEO_RETURN: 'MJ_VIDEO_TO_VIDEO_RETURN'
|
||||||
|
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,6 +48,9 @@ const SYSTEM = {
|
|||||||
/** 选择文件夹或指定后缀的文件 */
|
/** 选择文件夹或指定后缀的文件 */
|
||||||
SELECT_FOLDER_OR_FILE: 'SELECT_FOLDER_OR_FILE',
|
SELECT_FOLDER_OR_FILE: 'SELECT_FOLDER_OR_FILE',
|
||||||
|
|
||||||
|
/** 读取文本文件内容 */
|
||||||
|
READ_TEXT_FILE: 'READ_TEXT_FILE',
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
/** 打开指定的url */
|
/** 打开指定的url */
|
||||||
|
|||||||
3
src/define/model/book/book.d.ts
vendored
3
src/define/model/book/book.d.ts
vendored
@ -24,6 +24,7 @@ declare namespace Book {
|
|||||||
no?: number
|
no?: number
|
||||||
name?: string
|
name?: string
|
||||||
bookFolderPath?: string
|
bookFolderPath?: string
|
||||||
|
children?: SelectBookTask[]
|
||||||
type?: BookType
|
type?: BookType
|
||||||
oldVideoPath?: string
|
oldVideoPath?: string
|
||||||
srtPath?: string
|
srtPath?: string
|
||||||
@ -141,6 +142,8 @@ declare namespace Book {
|
|||||||
bookTaskId?: string
|
bookTaskId?: string
|
||||||
videoPath?: string // 视频地址
|
videoPath?: string // 视频地址
|
||||||
generateVideoPath?: string // 生成的视频地址
|
generateVideoPath?: string // 生成的视频地址
|
||||||
|
subVideoPath?: string[] // 生成的批次视频的地址
|
||||||
|
subVideoPathObject?: subVideoPathModel[] //生成视频的完成结构显示
|
||||||
audioPath?: string // 音频地址
|
audioPath?: string // 音频地址
|
||||||
draftDepend?: string // 草稿依赖
|
draftDepend?: string // 草稿依赖
|
||||||
word?: string // 文案
|
word?: string // 文案
|
||||||
|
|||||||
65
src/define/model/book/bookTaskDetail.d.ts
vendored
65
src/define/model/book/bookTaskDetail.d.ts
vendored
@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
ImageToVideoModels,
|
ImageToVideoModels,
|
||||||
KlingMode,
|
KlingMode,
|
||||||
|
MJVideoBatchSize,
|
||||||
|
MJVideoType,
|
||||||
RunawayModel,
|
RunawayModel,
|
||||||
RunwaySeconds,
|
RunwaySeconds,
|
||||||
VideoModel,
|
VideoModel,
|
||||||
@ -26,6 +28,9 @@ declare namespace BookTaskDetail {
|
|||||||
runwayOptions?: string
|
runwayOptions?: string
|
||||||
lumaOptions?: string
|
lumaOptions?: string
|
||||||
klingOptions?: string
|
klingOptions?: string
|
||||||
|
mjVideoOptions?: string
|
||||||
|
messageData?: string
|
||||||
|
videoUrls?: string[] // 视频地址数组
|
||||||
messageData?: string
|
messageData?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,6 +83,66 @@ declare namespace BookTaskDetail {
|
|||||||
callback_url?: string // 回调地址,可选,生成视频完成后,会向该地址发送通知
|
callback_url?: string // 回调地址,可选,生成视频完成后,会向该地址发送通知
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MjVideoOptions {
|
||||||
|
/**
|
||||||
|
* 对视频任务进行操作。不为空时,index、taskId必填
|
||||||
|
*/
|
||||||
|
action?: MJVideoAction
|
||||||
|
/**
|
||||||
|
* 首帧图片,扩展时可为空
|
||||||
|
*/
|
||||||
|
image: string
|
||||||
|
/**
|
||||||
|
* 执行的视频索引号
|
||||||
|
*/
|
||||||
|
index?: number
|
||||||
|
/**
|
||||||
|
* 运动变化
|
||||||
|
*/
|
||||||
|
motion: MJVideoMotion
|
||||||
|
/**
|
||||||
|
* True时,返回官方链接
|
||||||
|
*/
|
||||||
|
noStorage?: boolean
|
||||||
|
/**
|
||||||
|
* 回调地址
|
||||||
|
*/
|
||||||
|
notifyHook?: string
|
||||||
|
/** 提示词 */
|
||||||
|
prompt?: null | string
|
||||||
|
|
||||||
|
state?: string
|
||||||
|
/**
|
||||||
|
* 需要操作的视频父任务ID
|
||||||
|
*/
|
||||||
|
taskId?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否减少视频的创意
|
||||||
|
*/
|
||||||
|
raw?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频质量类型(SD标清/HD高清)
|
||||||
|
*/
|
||||||
|
videoType?: MJVideoType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批次大小,一次生成的视频数量
|
||||||
|
*/
|
||||||
|
batchSize?: MJVideoBatchSize
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尾帧图片
|
||||||
|
*/
|
||||||
|
endImageUrl?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否首尾循环
|
||||||
|
*/
|
||||||
|
loop?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 小说文案相关
|
//#region 小说文案相关
|
||||||
|
|||||||
13
src/define/model/book/bookVideo.d.ts
vendored
Normal file
13
src/define/model/book/bookVideo.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
declare namespace BookVideo {
|
||||||
|
/**
|
||||||
|
* 表示用于检索小说转视频信息列表的查询条件的接口。
|
||||||
|
*
|
||||||
|
* @interface BookVideoInfoListQuertCondition
|
||||||
|
* @property {string?} [bookId] - 用于筛选视频的小说ID
|
||||||
|
* @property {string?} [bookTaskId] - 用于筛选视频的小说任务ID
|
||||||
|
*/
|
||||||
|
interface BookVideoInfoListQuertCondition {
|
||||||
|
bookId?: string // 小说ID
|
||||||
|
bookTaskId?: string // 小说任务ID
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/define/model/setting.d.ts
vendored
4
src/define/model/setting.d.ts
vendored
@ -1,5 +1,7 @@
|
|||||||
|
import { ImageToVideoCategory } from '@/define/data/imageData'
|
||||||
import { ImageGenerateMode, MJRobotType, MJSpeed } from '../data/mjData'
|
import { ImageGenerateMode, MJRobotType, MJSpeed } from '../data/mjData'
|
||||||
import { JianyingKeyFrameEnum } from '../enum/jianyingEnum'
|
import { JianyingKeyFrameEnum } from '../enum/jianyingEnum'
|
||||||
|
import { ImageToVideoModels } from '@/define/enum/video'
|
||||||
|
|
||||||
declare namespace SettingModal {
|
declare namespace SettingModal {
|
||||||
//#region 基础设置
|
//#region 基础设置
|
||||||
@ -24,7 +26,7 @@ declare namespace SettingModal {
|
|||||||
hdScale: number
|
hdScale: number
|
||||||
|
|
||||||
/** 默认图转视频方式 */
|
/** 默认图转视频方式 */
|
||||||
defaultImg2Video: ImageToVideoCategory
|
defaultImg2Video: ImageToVideoModels
|
||||||
|
|
||||||
/** 系统语言 */
|
/** 系统语言 */
|
||||||
language: string
|
language: string
|
||||||
|
|||||||
2
src/define/model/task.d.ts
vendored
2
src/define/model/task.d.ts
vendored
@ -21,6 +21,8 @@ declare namespace TaskModal {
|
|||||||
startTime?: number
|
startTime?: number
|
||||||
endTime?: number
|
endTime?: number
|
||||||
messageName?: string
|
messageName?: string
|
||||||
|
taskId?: string // 任务ID,可能是第三方服务的任务ID
|
||||||
|
taskMessage?: string // 任务消息,可能是第三方服务的任务消息
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TaskCondition {
|
interface TaskCondition {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { t } from '@/i18n'
|
||||||
import { DEFINE_STRING } from './ipcDefineString'
|
import { DEFINE_STRING } from './ipcDefineString'
|
||||||
import { TimeDelay } from './Tools/time'
|
import { TimeDelay } from './Tools/time'
|
||||||
|
|
||||||
@ -101,7 +102,7 @@ export class AsyncQueue {
|
|||||||
if (this.showEndTime) {
|
if (this.showEndTime) {
|
||||||
this.global.wins[0].webContents.send(DEFINE_STRING.SYSTEM.SHOW_MESSAGE_DIALOG, {
|
this.global.wins[0].webContents.send(DEFINE_STRING.SYSTEM.SHOW_MESSAGE_DIALOG, {
|
||||||
code: 0,
|
code: 0,
|
||||||
message: '试用时间已到,请联系客服'
|
message: t('试用时间已到,请联系客服')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.showEndTime = false
|
this.showEndTime = false
|
||||||
@ -134,7 +135,7 @@ export class AsyncQueue {
|
|||||||
await TimeDelay(2000)
|
await TimeDelay(2000)
|
||||||
this.global.wins[0].webContents.send(DEFINE_STRING.SYSTEM.SHOW_MAIN_NOTIFICATION, {
|
this.global.wins[0].webContents.send(DEFINE_STRING.SYSTEM.SHOW_MAIN_NOTIFICATION, {
|
||||||
code: 0,
|
code: 0,
|
||||||
message: `生图失败,共5次尝试,目前第${retryCount}次`
|
message: t("生图失败,共5次尝试,目前第 {retryCount} 次", { retryCount })
|
||||||
})
|
})
|
||||||
task()
|
task()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -155,7 +156,11 @@ export class AsyncQueue {
|
|||||||
if (errorFunc) {
|
if (errorFunc) {
|
||||||
errorFunc(
|
errorFunc(
|
||||||
batchId,
|
batchId,
|
||||||
`任务 ${taskId} 执行失败,错误信息:${error.toString()},开始清理整个批次,批次ID:${batchId}`
|
t("任务 {taskId} 执行失败,错误信息:{error},开始清理整个批次,批次ID:{batchId}", {
|
||||||
|
taskId,
|
||||||
|
error: error.message,
|
||||||
|
batchId
|
||||||
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!this.manualMode) {
|
if (!this.manualMode) {
|
||||||
@ -175,7 +180,11 @@ export class AsyncQueue {
|
|||||||
if (errorFunc) {
|
if (errorFunc) {
|
||||||
errorFunc(
|
errorFunc(
|
||||||
batchId,
|
batchId,
|
||||||
`任务 ${taskId} 执行失败,错误信息:${error.toString()},开始清理整个批次,批次ID:${batchId}`
|
t("任务 {taskId} 执行失败,错误信息:{error},开始清理整个批次,批次ID:{batchId}", {
|
||||||
|
taskId,
|
||||||
|
error: error.message,
|
||||||
|
batchId
|
||||||
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!this.manualMode) {
|
if (!this.manualMode) {
|
||||||
|
|||||||
716
src/i18n/README.md
Normal file
716
src/i18n/README.md
Normal file
@ -0,0 +1,716 @@
|
|||||||
|
# 多语言国际化系统 (i18n)
|
||||||
|
|
||||||
|
这是一个为 LaiTool Pro 设计的完整多语言国际化解决方案,支持对象键和字符串键两种使用方式,提供完整的代码提示和类型安全。
|
||||||
|
|
||||||
|
## ✨ 特性
|
||||||
|
|
||||||
|
- 🌍 **5种语言支持**:简体中文、繁体中文、英文、日文、韩文
|
||||||
|
- 🎯 **智能代码提示**:使用对象键获得完整的 IntelliSense 支持
|
||||||
|
- 🛡️ **类型安全**:完整的 TypeScript 支持,编译时检查
|
||||||
|
- 💰 **多货币格式化**:支持 CNY、USD、EUR、JPY、KRW 等
|
||||||
|
- 📅 **日期时间本地化**:根据语言自动格式化日期时间
|
||||||
|
- 🔄 **动态语言切换**:运行时无缝切换语言
|
||||||
|
- 💾 **自动持久化**:语言设置自动保存到本地存储
|
||||||
|
- 🔧 **向下兼容**:同时支持新旧两种 API 使用方式
|
||||||
|
- ⚡ **Electron 优化**:解决了 Electron 环境下的动态导入问题
|
||||||
|
|
||||||
|
## 🔧 最新修复 (2025年9月5日)
|
||||||
|
|
||||||
|
- ✅ **修复了 Electron 环境下的语言文件加载问题**
|
||||||
|
- ✅ **使用静态导入替代动态导入,避免路径解析错误**
|
||||||
|
- ✅ **优化了语言切换性能,现在是同步操作**
|
||||||
|
- ✅ **增强了错误处理和日志记录**
|
||||||
|
|
||||||
|
## 🏗️ 架构结构
|
||||||
|
|
||||||
|
```
|
||||||
|
src/i18n/
|
||||||
|
├── index.ts # 核心 I18nManager 类
|
||||||
|
├── main.ts # 统一导出入口
|
||||||
|
├── keys.ts # 翻译键对象定义(提供代码提示)
|
||||||
|
├── types.ts # TypeScript 类型定义
|
||||||
|
├── constants.ts # 常量和配置
|
||||||
|
├── locales/ # 语言包目录
|
||||||
|
│ ├── zh-cn.ts # 简体中文
|
||||||
|
│ ├── zh-tw.ts # 繁体中文
|
||||||
|
│ ├── en.ts # 英文
|
||||||
|
│ ├── ja.ts # 日文
|
||||||
|
│ └── ko.ts # 韩文
|
||||||
|
└── utils/
|
||||||
|
└── helpers.ts # 便捷工具函数
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 基础用法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { t, menu, common, video, formatPrice } from '@/i18n/main'
|
||||||
|
|
||||||
|
// 🎯 推荐方式:使用对象键(有完整代码提示)
|
||||||
|
const title = t(menu.home) // ✅ 输入 "menu." 显示所有选项
|
||||||
|
const save = t(common.save) // ✅ 类型安全,编译时检查
|
||||||
|
const generate = t(video.generate) // ✅ 自动补全,不会拼写错误
|
||||||
|
|
||||||
|
// 传统方式:使用字符串键(仍然支持)
|
||||||
|
const titleStr = t('menu.home') // ⚠️ 没有代码提示
|
||||||
|
|
||||||
|
// 价格格式化
|
||||||
|
const price = formatPrice(99.99, 'CNY') // "¥99.99"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 在 Vue 组件中使用
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>{{ t(menu.home) }}</h1>
|
||||||
|
<button @click="save">{{ t(common.save) }}</button>
|
||||||
|
<button @click="cancel">{{ t(common.cancel) }}</button>
|
||||||
|
<p>{{ formatPrice(99.99, 'CNY') }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { t, formatPrice, menu, common } from '@/i18n/main'
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
console.log(t(common.loading))
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel() {
|
||||||
|
console.log(t(common.cancelled))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 可用的键对象
|
||||||
|
|
||||||
|
导入键对象后,您可以享受完整的代码提示:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
menu, // 菜单相关
|
||||||
|
common, // 通用操作
|
||||||
|
video, // 视频功能
|
||||||
|
settings, // 设置选项
|
||||||
|
price, // 价格相关
|
||||||
|
task, // 任务管理
|
||||||
|
file, // 文件操作
|
||||||
|
project, // 项目管理
|
||||||
|
error, // 错误信息
|
||||||
|
validation // 表单验证
|
||||||
|
} from '@/i18n/main'
|
||||||
|
|
||||||
|
// 使用示例
|
||||||
|
const homeTitle = t(menu.home) // "首页"
|
||||||
|
const saveButton = t(common.save) // "保存"
|
||||||
|
const videoGenerate = t(video.generate) // "生成视频"
|
||||||
|
const generalSettings = t(settings.general) // "通用设置"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ API 参考
|
||||||
|
|
||||||
|
### 核心翻译函数
|
||||||
|
|
||||||
|
#### `t(key, params?)`
|
||||||
|
支持对象键和字符串键的翻译函数
|
||||||
|
```typescript
|
||||||
|
// 对象键方式(推荐)
|
||||||
|
t(menu.home) // "首页"
|
||||||
|
t(common.welcome, { name: '张三' }) // "欢迎 张三"
|
||||||
|
|
||||||
|
// 字符串键方式
|
||||||
|
t('menu.home') // "首页"
|
||||||
|
t('common.welcome', { name: '张三' }) // "欢迎 张三"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 格式化函数
|
||||||
|
|
||||||
|
#### `formatPrice(amount, currency?)`
|
||||||
|
价格格式化
|
||||||
|
```typescript
|
||||||
|
formatPrice(99.99, 'CNY') // "¥99.99"
|
||||||
|
formatPrice(99.99, 'USD') // "$99.99"
|
||||||
|
formatPrice(99.99, 'EUR') // "€99.99"
|
||||||
|
formatPrice(99.99, 'JPY') // "¥100"
|
||||||
|
formatPrice(99.99, 'KRW') // "₩100"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `formatNumber(number, type?, locale?)`
|
||||||
|
数字格式化
|
||||||
|
```typescript
|
||||||
|
formatNumber(1234.56, 'decimal') // "1,234.56"
|
||||||
|
formatNumber(0.856, 'percent') // "85.6%"
|
||||||
|
formatNumber(1234, 'integer') // "1,234"
|
||||||
|
formatNumber(99.99, 'currency') // "¥99.99"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `formatDate(date, type?, locale?)`
|
||||||
|
日期格式化
|
||||||
|
```typescript
|
||||||
|
formatDate(new Date(), 'short') // "2025/9/5"
|
||||||
|
formatDate(new Date(), 'medium') // "2025年9月5日"
|
||||||
|
formatDate(new Date(), 'long') // "2025年9月5日 星期四"
|
||||||
|
formatDate(new Date(), 'time') // "14:30:45"
|
||||||
|
formatDate(new Date(), 'dateTime') // "2025年9月5日 14:30"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 语言管理
|
||||||
|
|
||||||
|
#### `switchLocale(locale)`
|
||||||
|
切换语言
|
||||||
|
```typescript
|
||||||
|
await switchLocale('en') // 切换到英文
|
||||||
|
await switchLocale('zh-tw') // 切换到繁体中文
|
||||||
|
await switchLocale('ja') // 切换到日文
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `getCurrentLocale()`
|
||||||
|
获取当前语言
|
||||||
|
```typescript
|
||||||
|
const currentLang = getCurrentLocale() // "zh-cn"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `getSupportedLocales()`
|
||||||
|
获取支持的语言列表
|
||||||
|
```typescript
|
||||||
|
const languages = getSupportedLocales()
|
||||||
|
// ['zh-cn', 'zh-tw', 'en', 'ja', 'ko']
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `detectAndSetLanguage()`
|
||||||
|
自动检测并设置语言
|
||||||
|
```typescript
|
||||||
|
const detectedLang = await detectAndSetLanguage()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌍 支持的语言
|
||||||
|
|
||||||
|
| 语言代码 | 语言名称 | 默认货币 | 示例 |
|
||||||
|
|---------|---------|---------|------|
|
||||||
|
| zh-cn | 简体中文 | CNY (¥) | 首页 |
|
||||||
|
| zh-tw | 繁体中文 | TWD (NT$) | 首頁 |
|
||||||
|
| en | English | USD ($) | Home |
|
||||||
|
| ja | 日本語 | JPY (¥) | ホーム |
|
||||||
|
| ko | 한국어 | KRW (₩) | 홈 |
|
||||||
|
|
||||||
|
## 💰 货币格式化
|
||||||
|
|
||||||
|
| 货币代码 | 符号 | 示例输出 |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| CNY | ¥ | ¥99.99 |
|
||||||
|
| USD | $ | $99.99 |
|
||||||
|
| EUR | € | €99.99 |
|
||||||
|
| JPY | ¥ | ¥100 |
|
||||||
|
| KRW | ₩ | ₩100 |
|
||||||
|
| TWD | NT$ | NT$100 |
|
||||||
|
|
||||||
|
## 🎯 最佳实践
|
||||||
|
|
||||||
|
### ✅ 推荐做法
|
||||||
|
|
||||||
|
1. **使用对象键获得代码提示**
|
||||||
|
```typescript
|
||||||
|
import { t, menu, common } from '@/i18n/main'
|
||||||
|
|
||||||
|
const title = t(menu.home) // ✅ 有完整提示
|
||||||
|
const save = t(common.save) // ✅ 类型安全
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **在组件中使用 computed**
|
||||||
|
```typescript
|
||||||
|
const pageTitle = computed(() => t(menu.home))
|
||||||
|
const buttonText = computed(() => t(common.save))
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **创建翻译 Hook**
|
||||||
|
```typescript
|
||||||
|
function usePageTranslations() {
|
||||||
|
return {
|
||||||
|
title: computed(() => t(menu.home)),
|
||||||
|
buttons: {
|
||||||
|
save: computed(() => t(common.save)),
|
||||||
|
cancel: computed(() => t(common.cancel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ 避免的做法
|
||||||
|
|
||||||
|
1. **不要使用魔法字符串**
|
||||||
|
```typescript
|
||||||
|
const title = t('menu.home') // ❌ 容易出错,没有提示
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **不要在模板中直接使用字符串键**
|
||||||
|
```vue
|
||||||
|
<!-- ❌ 不推荐 -->
|
||||||
|
<h1>{{ t('menu.home') }}</h1>
|
||||||
|
|
||||||
|
<!-- ✅ 推荐 -->
|
||||||
|
<h1>{{ t(menu.home) }}</h1>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 开发指南
|
||||||
|
|
||||||
|
### 添加新的翻译键
|
||||||
|
|
||||||
|
1. **在 `keys.ts` 中添加键定义**
|
||||||
|
```typescript
|
||||||
|
export const newCategory = {
|
||||||
|
newKey: 'newCategory.newKey'
|
||||||
|
} as const
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **在所有语言文件中添加翻译**
|
||||||
|
```typescript
|
||||||
|
// zh-cn.ts
|
||||||
|
newCategory: {
|
||||||
|
newKey: '新功能'
|
||||||
|
}
|
||||||
|
|
||||||
|
// en.ts
|
||||||
|
newCategory: {
|
||||||
|
newKey: 'New Feature'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **在 `main.ts` 中导出**
|
||||||
|
```typescript
|
||||||
|
export { newCategory } from './keys'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试多语言功能
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { switchLocale, t, menu } from '@/i18n/main'
|
||||||
|
|
||||||
|
// 测试所有语言
|
||||||
|
const languages = ['zh-cn', 'zh-tw', 'en', 'ja', 'ko']
|
||||||
|
|
||||||
|
for (const lang of languages) {
|
||||||
|
await switchLocale(lang)
|
||||||
|
console.log(`${lang}: ${t(menu.home)}`)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐛 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
**Q: 代码提示不显示?**
|
||||||
|
A: 确保正确导入了键对象:`import { menu, common } from '@/i18n/main'`
|
||||||
|
|
||||||
|
**Q: 翻译显示为键名?**
|
||||||
|
A: 检查语言文件中是否包含对应的键,确保所有语言文件结构一致。
|
||||||
|
|
||||||
|
**Q: 语言切换后页面没更新?**
|
||||||
|
A: 在 Vue 组件中使用 `computed(() => t(key))` 确保响应式更新。
|
||||||
|
|
||||||
|
**Q: TypeScript 报错键不存在?**
|
||||||
|
A: 检查是否在 `keys.ts` 中定义了对应的键,并在 `main.ts` 中正确导出。
|
||||||
|
|
||||||
|
## 📖 使用示例
|
||||||
|
|
||||||
|
### 完整的多语言页面
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="page">
|
||||||
|
<header>
|
||||||
|
<h1>{{ t(menu.home) }}</h1>
|
||||||
|
<select @change="changeLanguage" v-model="currentLang">
|
||||||
|
<option value="zh-cn">简体中文</option>
|
||||||
|
<option value="zh-tw">繁体中文</option>
|
||||||
|
<option value="en">English</option>
|
||||||
|
<option value="ja">日本語</option>
|
||||||
|
<option value="ko">한국어</option>
|
||||||
|
</select>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="actions">
|
||||||
|
<button @click="save">{{ t(common.save) }}</button>
|
||||||
|
<button @click="cancel">{{ t(common.cancel) }}</button>
|
||||||
|
<button @click="generateVideo">{{ t(video.generate) }}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="price">
|
||||||
|
<p>{{ t(price.currentPrice) }}: {{ formatPrice(99.99, 'CNY') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status">
|
||||||
|
<p v-if="loading">{{ t(common.loading) }}</p>
|
||||||
|
<p v-if="success">{{ t(common.success) }}</p>
|
||||||
|
<p v-if="error">{{ t(common.error) }}</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import {
|
||||||
|
t,
|
||||||
|
formatPrice,
|
||||||
|
switchLocale,
|
||||||
|
getCurrentLocale,
|
||||||
|
menu,
|
||||||
|
common,
|
||||||
|
video,
|
||||||
|
price
|
||||||
|
} from '@/i18n/main'
|
||||||
|
|
||||||
|
const currentLang = ref(getCurrentLocale())
|
||||||
|
const loading = ref(false)
|
||||||
|
const success = ref(false)
|
||||||
|
const error = ref(false)
|
||||||
|
|
||||||
|
async function changeLanguage(event) {
|
||||||
|
const newLang = event.target.value
|
||||||
|
await switchLocale(newLang)
|
||||||
|
currentLang.value = newLang
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
loading.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
loading.value = false
|
||||||
|
success.value = true
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel() {
|
||||||
|
console.log(t(common.cancel))
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateVideo() {
|
||||||
|
console.log(t(video.generate))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
这就是您的多语言系统的完整使用指南!🎉
|
||||||
|
|
||||||
|
## 🔀 翻译文本合并功能
|
||||||
|
|
||||||
|
新增了翻译文本合并功能,可以根据不同语言使用相应的分隔符来合并多个翻译文本。
|
||||||
|
|
||||||
|
### 主要功能
|
||||||
|
|
||||||
|
#### 1. `mergeTranslations` - 合并两个翻译
|
||||||
|
|
||||||
|
合并两个翻译键对应的文本,支持自定义分隔符和结尾符号。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { mergeTranslations } from '@/i18n'
|
||||||
|
|
||||||
|
// 基本使用
|
||||||
|
const result = mergeTranslations('common.save', 'common.cancel')
|
||||||
|
// 中文: "保存,取消"
|
||||||
|
// 英文: "Save, Cancel"
|
||||||
|
|
||||||
|
// 带选项使用
|
||||||
|
const result = mergeTranslations(
|
||||||
|
'video.generate',
|
||||||
|
'video.processing',
|
||||||
|
{
|
||||||
|
separator: 'comma', // 分隔符类型
|
||||||
|
suffix: 'period', // 结尾符号
|
||||||
|
locale: 'zh-cn' // 指定语言
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// 结果: "生成视频,处理中。"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. `mergeMultipleTranslations` - 合并多个翻译
|
||||||
|
|
||||||
|
合并多个翻译键对应的文本。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { mergeMultipleTranslations } from '@/i18n'
|
||||||
|
|
||||||
|
const result = mergeMultipleTranslations(
|
||||||
|
['common.save', 'common.edit', 'common.delete'],
|
||||||
|
{
|
||||||
|
separator: 'comma',
|
||||||
|
suffix: 'period'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// 中文: "保存,编辑,删除。"
|
||||||
|
// 英文: "Save, Edit, Delete."
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. `getLocaleSeparators` - 获取语言分隔符
|
||||||
|
|
||||||
|
获取指定语言的分隔符配置。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { getLocaleSeparators } from '@/i18n'
|
||||||
|
|
||||||
|
const separators = getLocaleSeparators('zh-cn')
|
||||||
|
// 返回: {
|
||||||
|
// comma: ',', semicolon: ';', period: '。', colon: ':', space: ' ',
|
||||||
|
// exclamation: '!', question: '?', ellipsis: '……'
|
||||||
|
// }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置选项
|
||||||
|
|
||||||
|
#### MergeTranslationOptions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface MergeTranslationOptions {
|
||||||
|
separator?: SeparatorType | string // 分隔符
|
||||||
|
suffix?: SeparatorType | string | null // 结尾符号
|
||||||
|
locale?: string // 指定语言
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 支持的分隔符类型
|
||||||
|
|
||||||
|
- `comma` - 逗号分隔
|
||||||
|
- `semicolon` - 分号分隔
|
||||||
|
- `period` - 句号分隔
|
||||||
|
- `colon` - 冒号分隔
|
||||||
|
- `space` - 空格分隔
|
||||||
|
- `exclamation` - 叹号分隔 🆕
|
||||||
|
- `question` - 问号分隔 🆕
|
||||||
|
- `ellipsis` - 省略号分隔 🆕
|
||||||
|
- 或者直接传入自定义字符串
|
||||||
|
|
||||||
|
#### 🎯 推荐使用 Separators 常量(智能提示)🆕
|
||||||
|
|
||||||
|
为了获得更好的开发体验,我们提供了 `Separators` 常量对象:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { mergeTranslations, Separators } from '@/i18n'
|
||||||
|
|
||||||
|
// ✅ 推荐方式:使用 Separators 常量(有完整智能提示)
|
||||||
|
const result = mergeTranslations('common.save', 'common.cancel', {
|
||||||
|
separator: Separators.comma, // 输入 Separators. 显示所有选项
|
||||||
|
suffix: Separators.period // 每个选项都有详细注释说明
|
||||||
|
})
|
||||||
|
|
||||||
|
// ❌ 传统方式:使用字符串(容易拼错,没有提示)
|
||||||
|
const oldWay = mergeTranslations('common.save', 'common.cancel', {
|
||||||
|
separator: 'comma', // 没有智能提示
|
||||||
|
suffix: 'peroid' // 可能拼写错误
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Separators 常量的优势:**
|
||||||
|
- ✅ 完整的智能提示和自动补全
|
||||||
|
- ✅ 类型安全,编译时检查错误
|
||||||
|
- ✅ 每个选项都有详细注释说明效果
|
||||||
|
- ✅ 重构友好,修改时自动更新引用
|
||||||
|
- ✅ 更好的代码可读性和维护性
|
||||||
|
|
||||||
|
### 语言分隔符支持
|
||||||
|
|
||||||
|
目前支持以下语言的专用分隔符:
|
||||||
|
|
||||||
|
| 语言 | 逗号 | 分号 | 句号 | 冒号 | 空格 | 叹号 | 问号 | 省略号 |
|
||||||
|
|------|------|------|------|------|------|------|------|--------|
|
||||||
|
| zh-cn | , | ; | 。 | : | (空格) | ! | ? | …… |
|
||||||
|
| zh-tw | , | ; | 。 | : | (空格) | ! | ? | …… |
|
||||||
|
| en | , (带空格) | ; (带空格) | . (带空格) | : (带空格) | (空格) | ! (带空格) | ? (带空格) | ... |
|
||||||
|
| ja | 、 | ; | 。 | : | (空格) | ! | ? | …… |
|
||||||
|
| ko | , (带空格) | ; (带空格) | . (带空格) | : (带空格) | (空格) | ! (带空格) | ? (带空格) | ... |
|
||||||
|
|
||||||
|
### 合并功能使用场景
|
||||||
|
|
||||||
|
#### 🎯 推荐使用方式(使用 Separators 常量)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { mergeTranslations, mergeMultipleTranslations, Separators } from '@/i18n'
|
||||||
|
|
||||||
|
// ✅ 组合操作按钮(智能提示)
|
||||||
|
const actionButtons = mergeMultipleTranslations(
|
||||||
|
['common.save', 'common.edit', 'common.delete'],
|
||||||
|
{ separator: Separators.space } // 输入 Separators. 显示所有可用选项
|
||||||
|
)
|
||||||
|
|
||||||
|
// ✅ 成功提示(叹号)
|
||||||
|
const successMessage = mergeTranslations(
|
||||||
|
'common.success',
|
||||||
|
'file.upload',
|
||||||
|
{
|
||||||
|
separator: Separators.exclamation,
|
||||||
|
suffix: Separators.exclamation
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ✅ 确认对话框(问号)
|
||||||
|
const confirmDialog = mergeTranslations(
|
||||||
|
'common.confirm',
|
||||||
|
'common.delete',
|
||||||
|
{
|
||||||
|
separator: Separators.space,
|
||||||
|
suffix: Separators.question
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ✅ 加载状态(省略号)
|
||||||
|
const loadingStatus = mergeTranslations(
|
||||||
|
'common.loading',
|
||||||
|
'video.processing',
|
||||||
|
{ separator: Separators.ellipsis }
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 传统使用方式(仍然支持)
|
||||||
|
|
||||||
|
#### 1. 组合操作按钮
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const actionButtons = mergeMultipleTranslations(
|
||||||
|
['common.save', 'common.edit', 'common.delete'],
|
||||||
|
{ separator: 'space' }
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 文件信息显示
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const fileInfo = mergeTranslations(
|
||||||
|
'file.size',
|
||||||
|
'file.type',
|
||||||
|
{ separator: 'comma' }
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 任务状态
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const taskStatus = mergeTranslations(
|
||||||
|
'task.status',
|
||||||
|
'task.progress',
|
||||||
|
{ separator: 'colon' }
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. 错误信息
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const errorMessage = mergeTranslations(
|
||||||
|
'error.uploadFailed',
|
||||||
|
'common.error_information',
|
||||||
|
{ separator: 'period', suffix: 'period' }
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. 带参数的翻译合并
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const validation = mergeTranslations(
|
||||||
|
'validation.minLength',
|
||||||
|
'validation.maxLength',
|
||||||
|
{ separator: 'semicolon' },
|
||||||
|
{ min: 3 }, // 第一个翻译的参数
|
||||||
|
{ max: 20 } // 第二个翻译的参数
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6. 新增标点符号使用场景 🆕
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 成功提示(叹号)
|
||||||
|
const successMessage = mergeTranslations(
|
||||||
|
'common.success',
|
||||||
|
'file.upload',
|
||||||
|
{ separator: 'exclamation', suffix: 'exclamation' }
|
||||||
|
)
|
||||||
|
// 中文: "成功!上传文件!" 英文: "Success! Upload File!"
|
||||||
|
|
||||||
|
// 确认对话框(问号)
|
||||||
|
const confirmDialog = mergeTranslations(
|
||||||
|
'common.confirm',
|
||||||
|
'common.delete',
|
||||||
|
{ separator: 'space', suffix: 'question' }
|
||||||
|
)
|
||||||
|
// 中文: "确认 删除?" 英文: "Confirm Delete?"
|
||||||
|
|
||||||
|
// 加载状态(省略号)
|
||||||
|
const loadingStatus = mergeTranslations(
|
||||||
|
'common.loading',
|
||||||
|
'video.processing',
|
||||||
|
{ separator: 'ellipsis', suffix: null }
|
||||||
|
)
|
||||||
|
// 中文: "加载中……处理中" 英文: "Loading...Processing"
|
||||||
|
|
||||||
|
// 警告信息(组合使用)
|
||||||
|
const warningMessage = mergeMultipleTranslations(
|
||||||
|
['common.warning', 'error.network', 'common.retry'],
|
||||||
|
{ separator: 'exclamation', suffix: 'question' }
|
||||||
|
)
|
||||||
|
// 中文: "警告!网络错误!重试?" 英文: "Warning! Network Error! Retry?"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 合并功能注意事项
|
||||||
|
|
||||||
|
1. 如果指定的语言不存在分隔符配置,会回退到英文配置
|
||||||
|
2. `suffix` 设置为 `null` 时不添加结尾符号
|
||||||
|
3. 自定义分隔符会直接使用传入的字符串,不会根据语言调整
|
||||||
|
4. 函数会自动获取当前语言设置,也可以通过 `locale` 参数强制指定
|
||||||
|
|
||||||
|
### 完整的合并功能示例
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="merge-demo">
|
||||||
|
<h2>{{ t(menu.home) }}</h2>
|
||||||
|
|
||||||
|
<!-- 基本合并 -->
|
||||||
|
<p>{{ mergeTranslations('common.save', 'common.cancel') }}</p>
|
||||||
|
|
||||||
|
<!-- 多个合并 -->
|
||||||
|
<p>{{ fileOperations }}</p>
|
||||||
|
|
||||||
|
<!-- 带结尾符号 -->
|
||||||
|
<p>{{ taskInfo }}</p>
|
||||||
|
|
||||||
|
<!-- 自定义分隔符 -->
|
||||||
|
<p>{{ customSeparator }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import {
|
||||||
|
t,
|
||||||
|
mergeTranslations,
|
||||||
|
mergeMultipleTranslations,
|
||||||
|
menu
|
||||||
|
} from '@/i18n/main'
|
||||||
|
|
||||||
|
// 文件操作组合
|
||||||
|
const fileOperations = computed(() =>
|
||||||
|
mergeMultipleTranslations(
|
||||||
|
['file.upload', 'file.download', 'file.delete'],
|
||||||
|
{ separator: 'comma', suffix: 'period' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 任务信息
|
||||||
|
const taskInfo = computed(() =>
|
||||||
|
mergeTranslations(
|
||||||
|
'task.status',
|
||||||
|
'task.running',
|
||||||
|
{ separator: 'colon' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 自定义分隔符
|
||||||
|
const customSeparator = computed(() =>
|
||||||
|
mergeTranslations(
|
||||||
|
'book.title',
|
||||||
|
'book.author',
|
||||||
|
{ separator: ' | ', suffix: null }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
这个完整的多语言系统现在包含了基础翻译功能和高级的文本合并功能!🚀
|
||||||
211
src/i18n/constants.ts
Normal file
211
src/i18n/constants.ts
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import type { LocaleConfig, SupportedLocale, DateTimeFormats, NumberFormats, PriceConfig } from './types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支持的语言配置
|
||||||
|
*/
|
||||||
|
export const SUPPORTED_LOCALES: LocaleConfig[] = [
|
||||||
|
{
|
||||||
|
code: 'zh-cn',
|
||||||
|
name: '简体中文',
|
||||||
|
flag: '🇨🇳',
|
||||||
|
currency: '¥',
|
||||||
|
currencyCode: 'CNY'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'zh-tw',
|
||||||
|
name: '繁體中文',
|
||||||
|
flag: '🇹🇼',
|
||||||
|
currency: 'NT$',
|
||||||
|
currencyCode: 'TWD'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en',
|
||||||
|
name: 'English',
|
||||||
|
flag: '🇺🇸',
|
||||||
|
currency: '$',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'ja',
|
||||||
|
name: '日本語',
|
||||||
|
flag: '🇯🇵',
|
||||||
|
currency: '¥',
|
||||||
|
currencyCode: 'JPY'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'ko',
|
||||||
|
name: '한국어',
|
||||||
|
flag: '🇰🇷',
|
||||||
|
currency: '₩',
|
||||||
|
currencyCode: 'KRW'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认语言
|
||||||
|
*/
|
||||||
|
export const DEFAULT_LOCALE: SupportedLocale = 'zh-cn'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备用语言
|
||||||
|
*/
|
||||||
|
export const FALLBACK_LOCALE: SupportedLocale = 'en'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 价格配置
|
||||||
|
*/
|
||||||
|
export const PRICE_CONFIGS: Record<SupportedLocale, PriceConfig> = {
|
||||||
|
'zh-cn': {
|
||||||
|
symbol: '¥',
|
||||||
|
code: 'CNY',
|
||||||
|
decimal: 2,
|
||||||
|
thousands: ',',
|
||||||
|
decimalSeparator: '.',
|
||||||
|
symbolPosition: 'before'
|
||||||
|
},
|
||||||
|
'zh-tw': {
|
||||||
|
symbol: 'NT$',
|
||||||
|
code: 'TWD',
|
||||||
|
decimal: 0,
|
||||||
|
thousands: ',',
|
||||||
|
decimalSeparator: '.',
|
||||||
|
symbolPosition: 'before'
|
||||||
|
},
|
||||||
|
'en': {
|
||||||
|
symbol: '$',
|
||||||
|
code: 'USD',
|
||||||
|
decimal: 2,
|
||||||
|
thousands: ',',
|
||||||
|
decimalSeparator: '.',
|
||||||
|
symbolPosition: 'before'
|
||||||
|
},
|
||||||
|
'ja': {
|
||||||
|
symbol: '¥',
|
||||||
|
code: 'JPY',
|
||||||
|
decimal: 0,
|
||||||
|
thousands: ',',
|
||||||
|
decimalSeparator: '.',
|
||||||
|
symbolPosition: 'before'
|
||||||
|
},
|
||||||
|
'ko': {
|
||||||
|
symbol: '₩',
|
||||||
|
code: 'KRW',
|
||||||
|
decimal: 0,
|
||||||
|
thousands: ',',
|
||||||
|
decimalSeparator: '.',
|
||||||
|
symbolPosition: 'before'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日期时间格式配置
|
||||||
|
*/
|
||||||
|
export const DATETIME_FORMATS: DateTimeFormats = {
|
||||||
|
'zh-cn': {
|
||||||
|
short: { year: 'numeric', month: '2-digit', day: '2-digit' },
|
||||||
|
medium: { year: 'numeric', month: 'short', day: 'numeric' },
|
||||||
|
long: { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' },
|
||||||
|
time: { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false },
|
||||||
|
dateTime: {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'zh-tw': {
|
||||||
|
short: { year: 'numeric', month: '2-digit', day: '2-digit' },
|
||||||
|
medium: { year: 'numeric', month: 'short', day: 'numeric' },
|
||||||
|
long: { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' },
|
||||||
|
time: { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false },
|
||||||
|
dateTime: {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'en': {
|
||||||
|
short: { year: 'numeric', month: '2-digit', day: '2-digit' },
|
||||||
|
medium: { year: 'numeric', month: 'short', day: 'numeric' },
|
||||||
|
long: { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' },
|
||||||
|
time: { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true },
|
||||||
|
dateTime: {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'ja': {
|
||||||
|
short: { year: 'numeric', month: '2-digit', day: '2-digit' },
|
||||||
|
medium: { year: 'numeric', month: 'short', day: 'numeric' },
|
||||||
|
long: { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' },
|
||||||
|
time: { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false },
|
||||||
|
dateTime: {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'ko': {
|
||||||
|
short: { year: 'numeric', month: '2-digit', day: '2-digit' },
|
||||||
|
medium: { year: 'numeric', month: 'short', day: 'numeric' },
|
||||||
|
long: { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' },
|
||||||
|
time: { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false },
|
||||||
|
dateTime: {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数字格式配置
|
||||||
|
*/
|
||||||
|
export const NUMBER_FORMATS: NumberFormats = {
|
||||||
|
'zh-cn': {
|
||||||
|
currency: { style: 'currency', currency: 'CNY', currencyDisplay: 'symbol' },
|
||||||
|
decimal: { minimumFractionDigits: 2, maximumFractionDigits: 2 },
|
||||||
|
percent: { style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1 },
|
||||||
|
integer: { minimumFractionDigits: 0, maximumFractionDigits: 0 }
|
||||||
|
},
|
||||||
|
'zh-tw': {
|
||||||
|
currency: { style: 'currency', currency: 'TWD', currencyDisplay: 'symbol' },
|
||||||
|
decimal: { minimumFractionDigits: 2, maximumFractionDigits: 2 },
|
||||||
|
percent: { style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1 },
|
||||||
|
integer: { minimumFractionDigits: 0, maximumFractionDigits: 0 }
|
||||||
|
},
|
||||||
|
'en': {
|
||||||
|
currency: { style: 'currency', currency: 'USD', currencyDisplay: 'symbol' },
|
||||||
|
decimal: { minimumFractionDigits: 2, maximumFractionDigits: 2 },
|
||||||
|
percent: { style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1 },
|
||||||
|
integer: { minimumFractionDigits: 0, maximumFractionDigits: 0 }
|
||||||
|
},
|
||||||
|
'ja': {
|
||||||
|
currency: { style: 'currency', currency: 'JPY', currencyDisplay: 'symbol' },
|
||||||
|
decimal: { minimumFractionDigits: 2, maximumFractionDigits: 2 },
|
||||||
|
percent: { style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1 },
|
||||||
|
integer: { minimumFractionDigits: 0, maximumFractionDigits: 0 }
|
||||||
|
},
|
||||||
|
'ko': {
|
||||||
|
currency: { style: 'currency', currency: 'KRW', currencyDisplay: 'symbol' },
|
||||||
|
decimal: { minimumFractionDigits: 2, maximumFractionDigits: 2 },
|
||||||
|
percent: { style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1 },
|
||||||
|
integer: { minimumFractionDigits: 0, maximumFractionDigits: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
267
src/i18n/index.ts
Normal file
267
src/i18n/index.ts
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
import type { SupportedLocale, I18nMessage, PriceConfig } from './types'
|
||||||
|
import {
|
||||||
|
DEFAULT_LOCALE,
|
||||||
|
FALLBACK_LOCALE,
|
||||||
|
SUPPORTED_LOCALES,
|
||||||
|
PRICE_CONFIGS,
|
||||||
|
DATETIME_FORMATS,
|
||||||
|
NUMBER_FORMATS
|
||||||
|
} from './constants'
|
||||||
|
|
||||||
|
// 静态导入所有语言文件
|
||||||
|
import zhCnMessages from './locales/zh-cn'
|
||||||
|
import zhTwMessages from './locales/zh-tw'
|
||||||
|
import enMessages from './locales/en'
|
||||||
|
import jaMessages from './locales/ja'
|
||||||
|
import koMessages from './locales/ko'
|
||||||
|
|
||||||
|
// 语言文件映射
|
||||||
|
const LOCALE_MESSAGES: Record<SupportedLocale, I18nMessage> = {
|
||||||
|
'zh-cn': zhCnMessages,
|
||||||
|
'zh-tw': zhTwMessages,
|
||||||
|
en: enMessages,
|
||||||
|
ja: jaMessages,
|
||||||
|
ko: koMessages
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多语系管理器
|
||||||
|
*/
|
||||||
|
export class I18nManager {
|
||||||
|
private static instance: I18nManager | null = null
|
||||||
|
private currentLocale: SupportedLocale = DEFAULT_LOCALE
|
||||||
|
private messages: Record<SupportedLocale, I18nMessage> = {} as any
|
||||||
|
// private fallbackMessages: I18nMessage = {}
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单例实例
|
||||||
|
*/
|
||||||
|
public static getInstance(): I18nManager {
|
||||||
|
if (!I18nManager.instance) {
|
||||||
|
I18nManager.instance = new I18nManager()
|
||||||
|
}
|
||||||
|
return I18nManager.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
private async init() {
|
||||||
|
// 加载默认语言包
|
||||||
|
this.loadLocale(DEFAULT_LOCALE)
|
||||||
|
this.loadLocale(FALLBACK_LOCALE)
|
||||||
|
// this.fallbackMessages = this.messages[FALLBACK_LOCALE] || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载指定语言包
|
||||||
|
*/
|
||||||
|
public loadLocale(locale: SupportedLocale) {
|
||||||
|
try {
|
||||||
|
const messages = LOCALE_MESSAGES[locale]
|
||||||
|
if (messages) {
|
||||||
|
this.messages[locale] = messages
|
||||||
|
} else {
|
||||||
|
console.warn(`Unsupported locale: ${locale}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to load locale: ${locale}`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前语言
|
||||||
|
*/
|
||||||
|
public setLocale(locale: SupportedLocale) {
|
||||||
|
if (!this.messages[locale]) {
|
||||||
|
this.loadLocale(locale)
|
||||||
|
}
|
||||||
|
this.currentLocale = locale
|
||||||
|
|
||||||
|
// 保存到本地存储
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
localStorage.setItem('app-locale', locale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前语言
|
||||||
|
*/
|
||||||
|
public getLocale(): SupportedLocale {
|
||||||
|
return this.currentLocale
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取翻译文本
|
||||||
|
* 支持字符串键和对象键两种方式:
|
||||||
|
* t('menu.home') 或 t(menu.home)
|
||||||
|
*/
|
||||||
|
public t(key: string, params?: Record<string, any>): string {
|
||||||
|
const keyString = typeof key === 'string' ? key : String(key)
|
||||||
|
const message =
|
||||||
|
this.getMessage(keyString, this.currentLocale) ||
|
||||||
|
this.getMessage(keyString, FALLBACK_LOCALE) ||
|
||||||
|
keyString
|
||||||
|
|
||||||
|
return params ? this.interpolate(message, params) : message
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定语言的翻译文本
|
||||||
|
* 支持字符串键和对象键两种方式
|
||||||
|
*/
|
||||||
|
public tl(
|
||||||
|
key: string,
|
||||||
|
locale: SupportedLocale,
|
||||||
|
params?: Record<string, any>
|
||||||
|
): string {
|
||||||
|
const keyString = typeof key === 'string' ? key : String(key)
|
||||||
|
const message =
|
||||||
|
this.getMessage(keyString, locale) || this.getMessage(keyString, FALLBACK_LOCALE) || keyString
|
||||||
|
|
||||||
|
return params ? this.interpolate(message, params) : message
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从嵌套对象中获取消息
|
||||||
|
*/
|
||||||
|
private getMessage(key: string, locale: SupportedLocale): string {
|
||||||
|
const messages = this.messages[locale]
|
||||||
|
if (!messages) return ''
|
||||||
|
|
||||||
|
let result: any = messages
|
||||||
|
|
||||||
|
if (result && typeof result === 'object' && key in result) {
|
||||||
|
result = result[key]
|
||||||
|
} else {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof result === 'string' ? result : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插值替换
|
||||||
|
*/
|
||||||
|
private interpolate(message: string, params: Record<string, any>): string {
|
||||||
|
return message.replace(/\{(\w+)\}/g, (match, key) => {
|
||||||
|
return params[key] !== undefined ? String(params[key]) : match
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化价格
|
||||||
|
*/
|
||||||
|
public formatPrice(amount: number, locale?: SupportedLocale): string {
|
||||||
|
const targetLocale = locale || this.currentLocale
|
||||||
|
const config = PRICE_CONFIGS[targetLocale]
|
||||||
|
|
||||||
|
if (!config) return String(amount)
|
||||||
|
|
||||||
|
const formatter = new Intl.NumberFormat(targetLocale, {
|
||||||
|
style: 'currency',
|
||||||
|
currency: config.code,
|
||||||
|
minimumFractionDigits: config.decimal,
|
||||||
|
maximumFractionDigits: config.decimal
|
||||||
|
})
|
||||||
|
|
||||||
|
return formatter.format(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化数字
|
||||||
|
*/
|
||||||
|
public formatNumber(
|
||||||
|
number: number,
|
||||||
|
type: 'currency' | 'decimal' | 'percent' | 'integer' = 'decimal',
|
||||||
|
locale?: SupportedLocale
|
||||||
|
): string {
|
||||||
|
const targetLocale = locale || this.currentLocale
|
||||||
|
const formats = NUMBER_FORMATS[targetLocale]
|
||||||
|
|
||||||
|
if (!formats || !formats[type]) return String(number)
|
||||||
|
|
||||||
|
const formatter = new Intl.NumberFormat(targetLocale, formats[type])
|
||||||
|
return formatter.format(number)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期
|
||||||
|
*/
|
||||||
|
public formatDate(
|
||||||
|
date: Date,
|
||||||
|
type: 'short' | 'medium' | 'long' | 'time' | 'dateTime' = 'medium',
|
||||||
|
locale?: SupportedLocale
|
||||||
|
): string {
|
||||||
|
const targetLocale = locale || this.currentLocale
|
||||||
|
const formats = DATETIME_FORMATS[targetLocale]
|
||||||
|
|
||||||
|
if (!formats || !formats[type]) return date.toLocaleDateString()
|
||||||
|
|
||||||
|
const formatter = new Intl.DateTimeFormat(targetLocale, formats[type])
|
||||||
|
return formatter.format(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取支持的语言列表
|
||||||
|
*/
|
||||||
|
public getSupportedLocales() {
|
||||||
|
return SUPPORTED_LOCALES
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前语言的价格配置
|
||||||
|
*/
|
||||||
|
public getPriceConfig(locale?: SupportedLocale): PriceConfig {
|
||||||
|
return PRICE_CONFIGS[locale || this.currentLocale]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测浏览器语言
|
||||||
|
*/
|
||||||
|
public detectBrowserLanguage(): SupportedLocale {
|
||||||
|
if (typeof navigator === 'undefined') return DEFAULT_LOCALE
|
||||||
|
|
||||||
|
const browserLang = navigator.language.toLowerCase()
|
||||||
|
|
||||||
|
// 精确匹配
|
||||||
|
for (const locale of SUPPORTED_LOCALES) {
|
||||||
|
if (browserLang === locale.code) {
|
||||||
|
return locale.code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 语言代码匹配(如 zh-cn 匹配 zh)
|
||||||
|
const langCode = browserLang.split('-')[0]
|
||||||
|
for (const locale of SUPPORTED_LOCALES) {
|
||||||
|
if (locale.code.startsWith(langCode)) {
|
||||||
|
return locale.code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_LOCALE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从本地存储加载语言设置
|
||||||
|
*/
|
||||||
|
public loadFromStorage(): SupportedLocale {
|
||||||
|
if (typeof localStorage === 'undefined') return DEFAULT_LOCALE
|
||||||
|
|
||||||
|
const saved = localStorage.getItem('app-locale') as SupportedLocale
|
||||||
|
if (saved && SUPPORTED_LOCALES.some((l) => l.code === saved)) {
|
||||||
|
return saved
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.detectBrowserLanguage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出工具函数
|
||||||
|
export * from './utils/helpers'
|
||||||
|
export * from './types'
|
||||||
|
export * from './constants'
|
||||||
1846
src/i18n/locales/en.ts
Normal file
1846
src/i18n/locales/en.ts
Normal file
File diff suppressed because it is too large
Load Diff
1
src/i18n/locales/ja.ts
Normal file
1
src/i18n/locales/ja.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default {}
|
||||||
1
src/i18n/locales/ko.ts
Normal file
1
src/i18n/locales/ko.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default {}
|
||||||
1846
src/i18n/locales/zh-cn.ts
Normal file
1846
src/i18n/locales/zh-cn.ts
Normal file
File diff suppressed because it is too large
Load Diff
1
src/i18n/locales/zh-tw.ts
Normal file
1
src/i18n/locales/zh-tw.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default {}
|
||||||
50
src/i18n/main.ts
Normal file
50
src/i18n/main.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 导出核心类型
|
||||||
|
export type { SupportedLocale, LocaleConfig, I18nMessage, PriceConfig, I18nConfig } from './types'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 导出常量
|
||||||
|
export {
|
||||||
|
SUPPORTED_LOCALES,
|
||||||
|
DEFAULT_LOCALE,
|
||||||
|
FALLBACK_LOCALE,
|
||||||
|
PRICE_CONFIGS,
|
||||||
|
DATETIME_FORMATS,
|
||||||
|
NUMBER_FORMATS,
|
||||||
|
} from './constants'
|
||||||
|
|
||||||
|
// 导出核心管理器
|
||||||
|
export { I18nManager } from './index'
|
||||||
|
|
||||||
|
// 导出工具函数
|
||||||
|
export {
|
||||||
|
useI18n,
|
||||||
|
t,
|
||||||
|
formatPrice,
|
||||||
|
formatNumber,
|
||||||
|
formatDate,
|
||||||
|
switchLocale,
|
||||||
|
getCurrentLocale,
|
||||||
|
getSupportedLocales,
|
||||||
|
detectAndSetLanguage
|
||||||
|
} from './utils/helpers'
|
||||||
|
|
||||||
|
// 导出语言包
|
||||||
|
import zhCN from './locales/zh-cn'
|
||||||
|
// import zhTW from './locales/zh-tw'
|
||||||
|
import en from './locales/en'
|
||||||
|
// import ja from './locales/ja'
|
||||||
|
// import ko from './locales/ko'
|
||||||
|
|
||||||
|
export const locales = {
|
||||||
|
'zh-cn': zhCN,
|
||||||
|
// 'zh-tw': zhTW,
|
||||||
|
en: en,
|
||||||
|
// ja: ja,
|
||||||
|
// ko: ko
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认导出 i18n 实例
|
||||||
|
import { I18nManager } from './index'
|
||||||
|
export const i18nManager = I18nManager.getInstance()
|
||||||
|
export default i18nManager
|
||||||
79
src/i18n/types.ts
Normal file
79
src/i18n/types.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* 多语系类型定义
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type SupportedLocale = 'zh-cn' | 'zh-tw' | 'en' | 'ja' | 'ko'
|
||||||
|
|
||||||
|
export interface LocaleConfig {
|
||||||
|
/** 语言代码 */
|
||||||
|
code: SupportedLocale
|
||||||
|
/** 语言名称 */
|
||||||
|
name: string
|
||||||
|
/** 语言标识符(emoji 或图标) */
|
||||||
|
flag: string
|
||||||
|
/** 是否为从右到左的语言 */
|
||||||
|
rtl?: boolean
|
||||||
|
/** 货币符号 */
|
||||||
|
currency: string
|
||||||
|
/** 货币代码 */
|
||||||
|
currencyCode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TranslationKey {
|
||||||
|
[key: string]: string | TranslationKey
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface I18nMessage {
|
||||||
|
[key: string]: string | I18nMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PriceConfig {
|
||||||
|
/** 货币符号 */
|
||||||
|
symbol: string
|
||||||
|
/** 货币代码 */
|
||||||
|
code: string
|
||||||
|
/** 小数位数 */
|
||||||
|
decimal: number
|
||||||
|
/** 千分位分隔符 */
|
||||||
|
thousands: string
|
||||||
|
/** 小数点分隔符 */
|
||||||
|
decimalSeparator: string
|
||||||
|
/** 货币符号位置 */
|
||||||
|
symbolPosition: 'before' | 'after'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DateTimeFormats {
|
||||||
|
[locale: string]: {
|
||||||
|
short: Intl.DateTimeFormatOptions
|
||||||
|
medium: Intl.DateTimeFormatOptions
|
||||||
|
long: Intl.DateTimeFormatOptions
|
||||||
|
time: Intl.DateTimeFormatOptions
|
||||||
|
dateTime: Intl.DateTimeFormatOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NumberFormats {
|
||||||
|
[locale: string]: {
|
||||||
|
currency: Intl.NumberFormatOptions
|
||||||
|
decimal: Intl.NumberFormatOptions
|
||||||
|
percent: Intl.NumberFormatOptions
|
||||||
|
integer: Intl.NumberFormatOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface I18nConfig {
|
||||||
|
/** 当前语言 */
|
||||||
|
locale: SupportedLocale
|
||||||
|
/** 备用语言 */
|
||||||
|
fallbackLocale: SupportedLocale
|
||||||
|
/** 可用语言列表 */
|
||||||
|
availableLocales: LocaleConfig[]
|
||||||
|
/** 是否检测浏览器语言 */
|
||||||
|
detectBrowserLanguage: boolean
|
||||||
|
/** 日期时间格式 */
|
||||||
|
datetimeFormats: DateTimeFormats
|
||||||
|
/** 数字格式 */
|
||||||
|
numberFormats: NumberFormats
|
||||||
|
/** 价格配置 */
|
||||||
|
priceConfigs: Record<SupportedLocale, PriceConfig>
|
||||||
|
}
|
||||||
299
src/i18n/utils/helpers.ts
Normal file
299
src/i18n/utils/helpers.ts
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
import { I18nManager, SupportedLocale } from '../index'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分隔符常量对象 - 提供智能提示和类型安全
|
||||||
|
* 使用方式: Separators.comma, Separators.period 等
|
||||||
|
*/
|
||||||
|
export const Separators = {
|
||||||
|
/** 逗号分隔符:中文(,) 英文(, ) */
|
||||||
|
comma: 'comma' as const,
|
||||||
|
/** 分号分隔符:中文(;) 英文(; ) */
|
||||||
|
semicolon: 'semicolon' as const,
|
||||||
|
/** 句号分隔符:中文(。) 英文(. ) */
|
||||||
|
period: 'period' as const,
|
||||||
|
/** 冒号分隔符:中文(:) 英文(: ) */
|
||||||
|
colon: 'colon' as const,
|
||||||
|
/** 空格分隔符:所有语言( ) */
|
||||||
|
space: 'space' as const,
|
||||||
|
/** 叹号分隔符:中文(!) 英文(! ) */
|
||||||
|
exclamation: 'exclamation' as const,
|
||||||
|
/** 问号分隔符:中文(?) 英文(? ) */
|
||||||
|
question: 'question' as const,
|
||||||
|
/** 省略号分隔符:中文(……) 英文(...) */
|
||||||
|
ellipsis: 'ellipsis' as const
|
||||||
|
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分隔符类型定义 - 基于 Separators 常量自动生成
|
||||||
|
*/
|
||||||
|
export type SeparatorType = keyof typeof Separators
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语言分隔符配置接口
|
||||||
|
* 定义了每种语言环境下各种标点符号的具体表示
|
||||||
|
*/
|
||||||
|
export interface LocaleSeparatorConfig {
|
||||||
|
comma: string // 逗号:用于列举、分隔项目
|
||||||
|
semicolon: string // 分号:用于分隔相关但独立的语句
|
||||||
|
period: string // 句号:用于结束句子
|
||||||
|
colon: string // 冒号:用于引出说明、列表等
|
||||||
|
space: string // 空格:用于分隔单词或元素
|
||||||
|
exclamation: string // 叹号:表示强调、惊叹、感叹
|
||||||
|
question: string // 问号:表示疑问、询问
|
||||||
|
ellipsis: string // 省略号:表示省略、延续或停顿
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译合并选项接口
|
||||||
|
* 定义翻译文本合并时的各种配置参数
|
||||||
|
*/
|
||||||
|
export interface MergeTranslationOptions {
|
||||||
|
/** 分隔符类型:用于连接两个翻译文本的标点符号,默认为 'comma' */
|
||||||
|
separator?: SeparatorType | string
|
||||||
|
/** 结尾符号:添加到合并结果末尾的标点符号,设为 null 则不添加结尾符号 */
|
||||||
|
suffix?: SeparatorType | string
|
||||||
|
/** 指定语言:强制使用特定语言的分隔符,不指定则使用当前语言设置 */
|
||||||
|
locale?: SupportedLocale
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 i18n 实例
|
||||||
|
*/
|
||||||
|
export function useI18n() {
|
||||||
|
return I18nManager.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译函数的简化版本
|
||||||
|
* @param key 翻译键,如 'common.welcome' 或 'validation.minLength'
|
||||||
|
* @param params 可选的参数对象,用于替换翻译文本中的占位符变量
|
||||||
|
* 格式: { variableName: value }
|
||||||
|
* 翻译文本中使用 {variableName} 作为占位符
|
||||||
|
* @returns 翻译后的文本,如果有参数则会替换占位符
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 基本使用
|
||||||
|
* t('common.save') // "保存"
|
||||||
|
*
|
||||||
|
* // 带参数使用
|
||||||
|
* t('common.welcome', { name: '张三' }) // "欢迎 张三"
|
||||||
|
* t('validation.minLength', { min: 3 }) // "至少需要 3 个字符"
|
||||||
|
* t('file.size', { size: '10MB', type: 'jpg' }) // "文件大小: 10MB, 类型: jpg"
|
||||||
|
*/
|
||||||
|
export function t(key: string, params?: Record<string, any>): string {
|
||||||
|
return useI18n().t(key, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化价格的简化版本
|
||||||
|
*/
|
||||||
|
export function formatPrice(amount: number, locale?: string): string {
|
||||||
|
return useI18n().formatPrice(amount, locale as any)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化数字的简化版本
|
||||||
|
*/
|
||||||
|
export function formatNumber(
|
||||||
|
number: number,
|
||||||
|
type: 'currency' | 'decimal' | 'percent' | 'integer' = 'decimal',
|
||||||
|
locale?: string
|
||||||
|
): string {
|
||||||
|
return useI18n().formatNumber(number, type, locale as any)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期的简化版本
|
||||||
|
*/
|
||||||
|
export function formatDate(
|
||||||
|
date: Date,
|
||||||
|
type: 'short' | 'medium' | 'long' | 'time' | 'dateTime' = 'medium',
|
||||||
|
locale?: string
|
||||||
|
): string {
|
||||||
|
return useI18n().formatDate(date, type, locale as any)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换语言
|
||||||
|
*/
|
||||||
|
export async function switchLocale(locale: string) {
|
||||||
|
const i18n = useI18n()
|
||||||
|
i18n.setLocale(locale as any)
|
||||||
|
|
||||||
|
// 触发全局事件,通知应用语言已更改
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.dispatchEvent(new CustomEvent('locale-changed', { detail: locale }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前语言
|
||||||
|
*/
|
||||||
|
export function getCurrentLocale(): string {
|
||||||
|
return useI18n().getLocale()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取支持的语言列表
|
||||||
|
*/
|
||||||
|
export function getSupportedLocales() {
|
||||||
|
return useI18n().getSupportedLocales()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测并设置浏览器语言
|
||||||
|
*/
|
||||||
|
export async function detectAndSetLanguage() {
|
||||||
|
const i18n = useI18n()
|
||||||
|
const detectedLocale = i18n.loadFromStorage()
|
||||||
|
i18n.setLocale(detectedLocale)
|
||||||
|
return detectedLocale
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据语言获取相应的分隔符配置
|
||||||
|
*/
|
||||||
|
export function getLocaleSeparators(locale?: string): LocaleSeparatorConfig {
|
||||||
|
const currentLocale = locale || getCurrentLocale()
|
||||||
|
|
||||||
|
// 根据不同语言返回不同的分隔符
|
||||||
|
const separatorConfig: Record<string, LocaleSeparatorConfig> = {
|
||||||
|
'zh-cn': {
|
||||||
|
comma: ',',
|
||||||
|
semicolon: ';',
|
||||||
|
period: '。',
|
||||||
|
colon: ':',
|
||||||
|
space: ' ',
|
||||||
|
exclamation: '!',
|
||||||
|
question: '?',
|
||||||
|
ellipsis: '……'
|
||||||
|
},
|
||||||
|
'zh-tw': {
|
||||||
|
comma: ',',
|
||||||
|
semicolon: ';',
|
||||||
|
period: '。',
|
||||||
|
colon: ':',
|
||||||
|
space: ' ',
|
||||||
|
exclamation: '!',
|
||||||
|
question: '?',
|
||||||
|
ellipsis: '……'
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
comma: ', ',
|
||||||
|
semicolon: '; ',
|
||||||
|
period: '. ',
|
||||||
|
colon: ': ',
|
||||||
|
space: ' ',
|
||||||
|
exclamation: '! ',
|
||||||
|
question: '? ',
|
||||||
|
ellipsis: '...'
|
||||||
|
},
|
||||||
|
ja: {
|
||||||
|
comma: '、',
|
||||||
|
semicolon: ';',
|
||||||
|
period: '。',
|
||||||
|
colon: ':',
|
||||||
|
space: ' ',
|
||||||
|
exclamation: '!',
|
||||||
|
question: '?',
|
||||||
|
ellipsis: '……'
|
||||||
|
},
|
||||||
|
ko: {
|
||||||
|
comma: ', ',
|
||||||
|
semicolon: '; ',
|
||||||
|
period: '. ',
|
||||||
|
colon: ': ',
|
||||||
|
space: ' ',
|
||||||
|
exclamation: '! ',
|
||||||
|
question: '? ',
|
||||||
|
ellipsis: '...'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return separatorConfig[currentLocale] || separatorConfig['en']
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并两个翻译文本
|
||||||
|
* @param key1 第一个翻译键
|
||||||
|
* @param key2 第二个翻译键
|
||||||
|
* @param options 合并选项
|
||||||
|
* @param params1 第一个翻译的参数
|
||||||
|
* @param params2 第二个翻译的参数
|
||||||
|
*/
|
||||||
|
export function mergeTranslations(
|
||||||
|
key1: string,
|
||||||
|
key2: string,
|
||||||
|
options: MergeTranslationOptions = {},
|
||||||
|
params1?: Record<string, any>,
|
||||||
|
params2?: Record<string, any>
|
||||||
|
): string {
|
||||||
|
const { separator = 'comma', suffix = null, locale } = options
|
||||||
|
|
||||||
|
// 获取翻译文本
|
||||||
|
const text1 = t(key1, params1)
|
||||||
|
const text2 = t(key2, params2)
|
||||||
|
|
||||||
|
// 获取当前语言的分隔符配置
|
||||||
|
const separators = getLocaleSeparators(locale)
|
||||||
|
|
||||||
|
// 获取分隔符
|
||||||
|
const getSeparator = (type: string) => {
|
||||||
|
if (type in separators) {
|
||||||
|
return separators[type]
|
||||||
|
}
|
||||||
|
return type // 如果不是预定义类型,直接使用传入的字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = text1 + getSeparator(separator) + text2
|
||||||
|
|
||||||
|
// 添加结尾符号
|
||||||
|
if (suffix !== null) {
|
||||||
|
result += getSeparator(suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并多个翻译文本
|
||||||
|
* @param keys 翻译键数组
|
||||||
|
* @param options 合并选项
|
||||||
|
* @param paramsArray 参数数组,与keys一一对应
|
||||||
|
*/
|
||||||
|
export function mergeMultipleTranslations(
|
||||||
|
keys: string[],
|
||||||
|
options: MergeTranslationOptions = {},
|
||||||
|
paramsArray?: Array<Record<string, any> | undefined>
|
||||||
|
): string {
|
||||||
|
const { separator = 'comma', suffix = null, locale } = options
|
||||||
|
|
||||||
|
if (keys.length === 0) return ''
|
||||||
|
|
||||||
|
// 获取所有翻译文本
|
||||||
|
const texts = keys.map((key, index) => {
|
||||||
|
const params = paramsArray?.[index]
|
||||||
|
return t(key, params)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取当前语言的分隔符配置
|
||||||
|
const separators = getLocaleSeparators(locale)
|
||||||
|
|
||||||
|
// 获取分隔符
|
||||||
|
const getSeparator = (type: string) => {
|
||||||
|
if (type in separators) {
|
||||||
|
return separators[type]
|
||||||
|
}
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = texts.join(getSeparator(separator))
|
||||||
|
|
||||||
|
// 添加结尾符号
|
||||||
|
if (suffix !== null) {
|
||||||
|
result += getSeparator(suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ import { AiReasonCommon } from '../aiReason/aiReasonCommon'
|
|||||||
import { groupWordsByCharCount } from '@/define/Tools/write'
|
import { groupWordsByCharCount } from '@/define/Tools/write'
|
||||||
import { cloneDeep, isEmpty } from 'lodash'
|
import { cloneDeep, isEmpty } from 'lodash'
|
||||||
import { OptionKeyName } from '@/define/enum/option'
|
import { OptionKeyName } from '@/define/enum/option'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI分镜处理类
|
* AI分镜处理类
|
||||||
@ -33,19 +34,18 @@ export class AIStoryboard extends BookBasicHandle {
|
|||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
|
|
||||||
// 根据ID获取小说批次任务
|
// 根据ID获取小说批次任务
|
||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId)
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId, true)
|
||||||
if (!bookTask) throw new Error('小说批次任务未找到')
|
|
||||||
|
|
||||||
// 获取任务详情中的分镜数据
|
// 获取任务详情中的分镜数据
|
||||||
let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
||||||
bookTaskId: bookTask.id
|
bookTaskId: bookTask.id
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!bookTaskDetails || bookTaskDetails.length === 0) throw new Error('小说分镜信息未找到')
|
if (!bookTaskDetails || bookTaskDetails.length === 0) throw new Error(t('未找到对应的小说分镜信息'))
|
||||||
|
|
||||||
// 提取AI处理后的分镜文本
|
// 提取AI处理后的分镜文本
|
||||||
let word = bookTaskDetails.map((item) => item.afterGpt)
|
let word = bookTaskDetails.map((item) => item.afterGpt)
|
||||||
if (word == undefined || word.length === 0) throw new Error('分镜内容为空')
|
if (word == undefined || word.length === 0) throw new Error(t('所有分镜文案内容为空,无法进行AI合并'))
|
||||||
|
|
||||||
// 将文本按500字符分组,避免单次请求过长
|
// 将文本按500字符分组,避免单次请求过长
|
||||||
let groupWord = groupWordsByCharCount(word as string[], 500)
|
let groupWord = groupWordsByCharCount(word as string[], 500)
|
||||||
@ -65,7 +65,7 @@ export class AIStoryboard extends BookBasicHandle {
|
|||||||
} else if (type === 'short') {
|
} else if (type === 'short') {
|
||||||
request = cloneDeep(AIWordMergeShort)
|
request = cloneDeep(AIWordMergeShort)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('不支持的分镜合并类型')
|
throw new Error(t('不支持的分镜合并类型'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将当前分组的文本合并为一个字符串
|
// 将当前分组的文本合并为一个字符串
|
||||||
@ -99,7 +99,6 @@ export class AIStoryboard extends BookBasicHandle {
|
|||||||
result.push(res)
|
result.push(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('分镜合并结果:', result)
|
|
||||||
return result
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import axios from 'axios'
|
|||||||
import { RetryWithBackoff } from '@/define/Tools/common'
|
import { RetryWithBackoff } from '@/define/Tools/common'
|
||||||
import { Book } from '@/define/model/book/book'
|
import { Book } from '@/define/model/book/book'
|
||||||
import { AiInferenceModelModel, GetAIPromptOptionByValue } from '@/define/data/aiData/aiData'
|
import { AiInferenceModelModel, GetAIPromptOptionByValue } from '@/define/data/aiData/aiData'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI推理通用工具类
|
* AI推理通用工具类
|
||||||
@ -55,7 +56,7 @@ export class AiReasonCommon {
|
|||||||
|
|
||||||
let aiReasonSetting = optionSerialization<SettingModal.InferenceAISettings>(
|
let aiReasonSetting = optionSerialization<SettingModal.InferenceAISettings>(
|
||||||
res,
|
res,
|
||||||
'‘设置-> 推理设置’'
|
t('设置 -> 推理设置')
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
isEmpty(aiReasonSetting.apiProvider) ||
|
isEmpty(aiReasonSetting.apiProvider) ||
|
||||||
@ -64,7 +65,9 @@ export class AiReasonCommon {
|
|||||||
isEmpty(aiReasonSetting.aiPromptValue)
|
isEmpty(aiReasonSetting.aiPromptValue)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'请检查 ‘设置-> 推理设置’ 的API提供商、API令牌、推理模型、推理模式等是不是存在!'
|
t("请检查 ‘{path}’ 的API提供商、API令牌、推理模型、推理模式等是不是存在!", {
|
||||||
|
path: t('设置 -> 推理设置')
|
||||||
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +91,7 @@ export class AiReasonCommon {
|
|||||||
GetInferenceModelMessage(): AiInferenceModelModel {
|
GetInferenceModelMessage(): AiInferenceModelModel {
|
||||||
let selectInferenceModel = GetAIPromptOptionByValue(this.aiReasonSetting.aiPromptValue)
|
let selectInferenceModel = GetAIPromptOptionByValue(this.aiReasonSetting.aiPromptValue)
|
||||||
if (isEmpty(selectInferenceModel)) {
|
if (isEmpty(selectInferenceModel)) {
|
||||||
throw new Error('请检查推理模型是否存在!')
|
throw new Error(t('请检查推理模型是否存在!'))
|
||||||
}
|
}
|
||||||
return selectInferenceModel
|
return selectInferenceModel
|
||||||
}
|
}
|
||||||
@ -270,12 +273,12 @@ export class AiReasonCommon {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (isEmpty(characterString) && selectInferenceModel.mustCharacter) {
|
if (isEmpty(characterString) && selectInferenceModel.mustCharacter) {
|
||||||
throw new Error('当前模式需要提前分析或者设置角色场景数据,请先分析角色/场景数据!')
|
throw new Error(t('当前模式需要提前分析或者设置角色场景数据,请先分析角色/场景数据!'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let requestBody = selectInferenceModel.requestBody
|
let requestBody = selectInferenceModel.requestBody
|
||||||
if (requestBody == null) {
|
if (requestBody == null) {
|
||||||
throw new Error('未找到对应的分镜预设的请求数据,请检查')
|
throw new Error(t('未找到对应的分镜预设的请求数据,请检查'))
|
||||||
}
|
}
|
||||||
|
|
||||||
requestBody.messages = this.replaceMessageObject(requestBody.messages, {
|
requestBody.messages = this.replaceMessageObject(requestBody.messages, {
|
||||||
|
|||||||
@ -4,19 +4,18 @@
|
|||||||
* 提供小说相关的导出功能,包括视频处理等操作的统一入口点。
|
* 提供小说相关的导出功能,包括视频处理等操作的统一入口点。
|
||||||
* 负责协调各种导出操作,简化外部调用接口。
|
* 负责协调各种导出操作,简化外部调用接口。
|
||||||
*/
|
*/
|
||||||
import { BookVideoHandle } from "../subBookHandle/bookVideoHandle"
|
import { BookExportHandle } from '../subBookHandle/bookExportHandle'
|
||||||
|
|
||||||
|
|
||||||
export class BookExportEntrance {
|
export class BookExportEntrance {
|
||||||
bookVideoHandle: BookVideoHandle
|
bookExportHandle: BookExportHandle
|
||||||
constructor() {
|
constructor() {
|
||||||
this.bookVideoHandle = new BookVideoHandle()
|
this.bookExportHandle = new BookExportHandle()
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region 视频相关
|
//#region 视频相关
|
||||||
/** 添加剪映草稿 */
|
/** 添加剪映草稿 */
|
||||||
AddJianyingDraft = async (bookTaskId: string) =>
|
AddJianyingDraft = async (bookTaskId: string) =>
|
||||||
await this.bookVideoHandle.AddJianyingDraft(bookTaskId)
|
await this.bookExportHandle.AddJianyingDraft(bookTaskId)
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
38
src/main/service/book/bookIndex/bookVideoEntrance.ts
Normal file
38
src/main/service/book/bookIndex/bookVideoEntrance.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { TaskModal } from '@/define/model/task'
|
||||||
|
import { BookVideoServiceHandle } from '../subBookHandle/bookVideoServiceHandle'
|
||||||
|
import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
||||||
|
|
||||||
|
export class BookVideoEntrance {
|
||||||
|
bookVideoServiceHandle!: BookVideoServiceHandle
|
||||||
|
constructor() {
|
||||||
|
this.bookVideoServiceHandle = new BookVideoServiceHandle()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取用于视频生成的小说信息列表 根据查询条件返回小说数据,
|
||||||
|
* 如果指定了bookTaskId,则返回对应小说的单个任务数据 否则返回所有启用了视频生成功能的小说及其任务数据
|
||||||
|
*/
|
||||||
|
async GetVideoBookInfoList(condition: BookVideo.BookVideoInfoListQuertCondition) {
|
||||||
|
return this.bookVideoServiceHandle.GetVideoBookInfoList(condition)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化视频消息数据 */
|
||||||
|
async InitVideoMessage(bookTaskId: string) {
|
||||||
|
return this.bookVideoServiceHandle.InitVideoMessage(bookTaskId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新小说分镜的视频消息 */
|
||||||
|
async UpdateBookTaskDetailVideoMessage(
|
||||||
|
bookTaskDetailId: string,
|
||||||
|
videoMessage: Partial<BookTaskDetail.VideoMessage>
|
||||||
|
) {
|
||||||
|
return this.bookVideoServiceHandle.UpdateBookTaskDetailVideoMessage(
|
||||||
|
bookTaskDetailId,
|
||||||
|
videoMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 图文转视频 */
|
||||||
|
async MediaToVideo(task: TaskModal.Task) {
|
||||||
|
return this.bookVideoServiceHandle.MediaToVideo(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import { BookPromptEntrance } from './bookIndex/bookPromptEntrance'
|
|||||||
import { BookImageEntrance } from './bookIndex/bookImageEntrance'
|
import { BookImageEntrance } from './bookIndex/bookImageEntrance'
|
||||||
import { BookExportEntrance } from './bookIndex/bookExportEntrance'
|
import { BookExportEntrance } from './bookIndex/bookExportEntrance'
|
||||||
import { mixin } from '@/main/utils/mixin'
|
import { mixin } from '@/main/utils/mixin'
|
||||||
|
import { BookVideoEntrance } from './bookIndex/bookVideoEntrance'
|
||||||
|
|
||||||
// 使用装饰器实现多重继承
|
// 使用装饰器实现多重继承
|
||||||
// 类型接口声明,让TypeScript知道合并后的类型
|
// 类型接口声明,让TypeScript知道合并后的类型
|
||||||
@ -14,14 +15,16 @@ interface BookHandle
|
|||||||
BookTaskDetailEntrance,
|
BookTaskDetailEntrance,
|
||||||
BookPromptEntrance,
|
BookPromptEntrance,
|
||||||
BookImageEntrance,
|
BookImageEntrance,
|
||||||
BookExportEntrance {}
|
BookExportEntrance,
|
||||||
|
BookVideoEntrance {}
|
||||||
|
|
||||||
@mixin(
|
@mixin(
|
||||||
BookTaskEntrance,
|
BookTaskEntrance,
|
||||||
BookTaskDetailEntrance,
|
BookTaskDetailEntrance,
|
||||||
BookPromptEntrance,
|
BookPromptEntrance,
|
||||||
BookImageEntrance,
|
BookImageEntrance,
|
||||||
BookExportEntrance
|
BookExportEntrance,
|
||||||
|
BookVideoEntrance
|
||||||
)
|
)
|
||||||
class BookHandle extends BookDataEntrance {
|
class BookHandle extends BookDataEntrance {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@ -2,12 +2,14 @@ import { BookTaskDetailService } from '@/define/db/service/book/bookTaskDetailSe
|
|||||||
import { BookTaskService } from '@/define/db/service/book/bookTaskService'
|
import { BookTaskService } from '@/define/db/service/book/bookTaskService'
|
||||||
import { OptionRealmService } from '@/define/db/service/optionService'
|
import { OptionRealmService } from '@/define/db/service/optionService'
|
||||||
import { BookService } from '@/define/db/service/book/bookService'
|
import { BookService } from '@/define/db/service/book/bookService'
|
||||||
|
import { TaskListService } from '@/define/db/service/book/taskListService'
|
||||||
|
|
||||||
export class BookBasicHandle {
|
export class BookBasicHandle {
|
||||||
bookTaskDetailService!: BookTaskDetailService
|
bookTaskDetailService!: BookTaskDetailService
|
||||||
bookTaskService!: BookTaskService
|
bookTaskService!: BookTaskService
|
||||||
optionRealmService!: OptionRealmService
|
optionRealmService!: OptionRealmService
|
||||||
bookService!: BookService
|
bookService!: BookService
|
||||||
|
taskListService!: TaskListService
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// 初始化
|
// 初始化
|
||||||
@ -27,6 +29,9 @@ export class BookBasicHandle {
|
|||||||
if (!this.bookService) {
|
if (!this.bookService) {
|
||||||
this.bookService = await BookService.getInstance()
|
this.bookService = await BookService.getInstance()
|
||||||
}
|
}
|
||||||
|
if (!this.taskListService) {
|
||||||
|
this.taskListService = await TaskListService.getInstance()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async transaction(callback: (realm: any) => void) {
|
async transaction(callback: (realm: any) => void) {
|
||||||
|
|||||||
@ -22,8 +22,9 @@ import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
|||||||
import { ValidateJson } from '@/define/Tools/validate'
|
import { ValidateJson } from '@/define/Tools/validate'
|
||||||
const execAsync = util.promisify(exec)
|
const execAsync = util.promisify(exec)
|
||||||
import JianyingService from '../../jianying/jianyingService'
|
import JianyingService from '../../jianying/jianyingService'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class BookVideoHandle extends BookBasicHandle {
|
export class BookExportHandle extends BookBasicHandle {
|
||||||
jianyingService: JianyingService
|
jianyingService: JianyingService
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
@ -70,7 +71,7 @@ export class BookVideoHandle extends BookBasicHandle {
|
|||||||
if (!isEmpty(bookTask.backgroundMusic) && bookTask.backgroundMusic != null) {
|
if (!isEmpty(bookTask.backgroundMusic) && bookTask.backgroundMusic != null) {
|
||||||
// 检查背景音乐文件或文件夹是否存在
|
// 检查背景音乐文件或文件夹是否存在
|
||||||
if (!CheckFileOrDirExist(bookTask.backgroundMusic)) {
|
if (!CheckFileOrDirExist(bookTask.backgroundMusic)) {
|
||||||
throw new Error('背景音乐文件夹或文件不存在,请检查')
|
throw new Error(t('背景音乐文件夹或文件不存在,请检查'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断背景音乐是文件还是文件夹
|
// 判断背景音乐是文件还是文件夹
|
||||||
@ -86,7 +87,7 @@ export class BookVideoHandle extends BookBasicHandle {
|
|||||||
// 如果是文件夹,从中随机选择一个音频文件
|
// 如果是文件夹,从中随机选择一个音频文件
|
||||||
let files = await GetFilesWithExtensions(bookTask.backgroundMusic, ['.mp3', '.wav'])
|
let files = await GetFilesWithExtensions(bookTask.backgroundMusic, ['.mp3', '.wav'])
|
||||||
if (files.length <= 0) {
|
if (files.length <= 0) {
|
||||||
throw new Error('背景音乐文件夹下面未存在有效的音频文件')
|
throw new Error(t('背景音乐文件夹下面未存在有效的音频文件'))
|
||||||
} else {
|
} else {
|
||||||
const randomIndex = Math.floor(Math.random() * files.length)
|
const randomIndex = Math.floor(Math.random() * files.length)
|
||||||
musicPath = files[randomIndex]
|
musicPath = files[randomIndex]
|
||||||
@ -166,12 +167,12 @@ export class BookVideoHandle extends BookBasicHandle {
|
|||||||
|
|
||||||
// 验证时间信息完整性
|
// 验证时间信息完整性
|
||||||
if (element.startTime == null || element.endTime == null) {
|
if (element.startTime == null || element.endTime == null) {
|
||||||
throw new Error('字幕时间信息不完整')
|
throw new Error(t('字幕时间信息不完整'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证字幕内容完整性
|
// 验证字幕内容完整性
|
||||||
if (element.subValue == null) {
|
if (element.subValue == null) {
|
||||||
throw new Error('字幕内容信息不完整')
|
throw new Error(t('字幕内容信息不完整'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理字幕内容:如果是字符串格式的JSON,则解析为对象
|
// 处理字幕内容:如果是字符串格式的JSON,则解析为对象
|
||||||
@ -179,7 +180,7 @@ export class BookVideoHandle extends BookBasicHandle {
|
|||||||
if (ValidateJson(element.subValue)) {
|
if (ValidateJson(element.subValue)) {
|
||||||
element.subValue = JSON.parse(element.subValue)
|
element.subValue = JSON.parse(element.subValue)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('字幕内容信息不完整')
|
throw new Error(t('字幕内容信息不完整'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,23 +253,27 @@ export class BookVideoHandle extends BookBasicHandle {
|
|||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId)
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId)
|
||||||
|
|
||||||
if (bookTask == null) {
|
if (bookTask == null) {
|
||||||
return errorMessage('没有找到小说批次任务,请检查', 'BookVideoHandle_AddJianyingDraft')
|
return errorMessage(t("未找到对应的小说批次任务"), 'BookVideoHandle_AddJianyingDraft')
|
||||||
}
|
}
|
||||||
let book = await this.bookService.GetBookDataById(bookTask.bookId as string)
|
let book = await this.bookService.GetBookDataById(bookTask.bookId as string)
|
||||||
if (book == null) {
|
if (book == null) {
|
||||||
return errorMessage('没有找到小说,请检查', 'BookVideoHandle_AddJianyingDraft')
|
return errorMessage(t("未找到指定ID的小说数据"), 'BookVideoHandle_AddJianyingDraft')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEmpty(bookTask.draftDepend) && bookTask.draftDepend != null) {
|
if (!isEmpty(bookTask.draftDepend) && bookTask.draftDepend != null) {
|
||||||
if (bookTask.name == bookTask.draftDepend) {
|
if (bookTask.name == bookTask.draftDepend) {
|
||||||
throw new Error('草稿名称不能和任务名称相同,请修改任务名称')
|
throw new Error(t('草稿名称不能和任务名称相同,请修改任务名称'))
|
||||||
}
|
}
|
||||||
await this.jianyingService.GenerateDraftFromDepend(
|
await this.jianyingService.GenerateDraftFromDepend(
|
||||||
bookTask.draftDepend as string,
|
bookTask.draftDepend as string,
|
||||||
bookTask.imageFolder as string,
|
bookTask.imageFolder as string,
|
||||||
bookTask.name as string
|
bookTask.name as string
|
||||||
)
|
)
|
||||||
return successMessage(bookTask.name, '添加剪映草稿成功!', 'BookVideoHandle_AddJianyingDraft')
|
return successMessage(
|
||||||
|
bookTask.name,
|
||||||
|
t("导出剪映草稿成功!"),
|
||||||
|
'BookVideoHandle_AddJianyingDraft'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let { draftName, configJsonPath } = await this.GenerateConfigFile(book, bookTask)
|
let { draftName, configJsonPath } = await this.GenerateConfigFile(book, bookTask)
|
||||||
@ -276,7 +281,7 @@ export class BookVideoHandle extends BookBasicHandle {
|
|||||||
// 开始调用 exe 执行 草稿的导出
|
// 开始调用 exe 执行 草稿的导出
|
||||||
let jianyingExePath = path.join(define.scripts_path, 'xiangbei_jianying_main.exe')
|
let jianyingExePath = path.join(define.scripts_path, 'xiangbei_jianying_main.exe')
|
||||||
if (!CheckFileOrDirExist(jianyingExePath)) {
|
if (!CheckFileOrDirExist(jianyingExePath)) {
|
||||||
throw new Error('没有找到导出剪映的执行文件,请检查')
|
throw new Error(t('没有找到导出剪映的执行文件,请检查') + ':' + jianyingExePath)
|
||||||
}
|
}
|
||||||
let result: string = ''
|
let result: string = ''
|
||||||
try {
|
try {
|
||||||
@ -335,14 +340,16 @@ export class BookVideoHandle extends BookBasicHandle {
|
|||||||
}
|
}
|
||||||
await fs.promises.writeFile(errorLogPath, JSON.stringify(errorInfo, null, 2), 'utf-8')
|
await fs.promises.writeFile(errorLogPath, JSON.stringify(errorInfo, null, 2), 'utf-8')
|
||||||
|
|
||||||
throw new Error(`详细错误已记录到: ${errorLogPath}`)
|
throw new Error(JSON.stringify(errorInfo, null, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 所有的草稿都添加完毕之后开始返回
|
// 所有的草稿都添加完毕之后开始返回
|
||||||
return successMessage(result, `添加剪映草稿成功!`, 'BookVideoHandle_AddJianyingDraft')
|
return successMessage(result, t("导出剪映草稿成功!"), 'BookVideoHandle_AddJianyingDraft')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'添加剪映草稿失败,错误信息如下:' + error.toString(),
|
t('导出剪映草稿失败:{error}', {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookVideoHandle_AddJianyingDraft'
|
'BookVideoHandle_AddJianyingDraft'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -24,6 +24,7 @@ import { SettingModal } from '@/define/model/setting'
|
|||||||
const execAsync = util.promisify(exec)
|
const execAsync = util.promisify(exec)
|
||||||
import { MJApiService } from '../../mj/mjApiService'
|
import { MJApiService } from '../../mj/mjApiService'
|
||||||
import { ImageSplit } from '@/define/Tools/image'
|
import { ImageSplit } from '@/define/Tools/image'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class BookImageHandle extends BookBasicHandle {
|
export class BookImageHandle extends BookBasicHandle {
|
||||||
mjApiService: MJApiService
|
mjApiService: MJApiService
|
||||||
@ -66,16 +67,14 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
): Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
|
): Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
|
||||||
try {
|
try {
|
||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId)
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId, true)
|
||||||
let bookTaskDetail =
|
let bookTaskDetail =
|
||||||
await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId)
|
await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId, true)
|
||||||
if (bookTaskDetail == null) {
|
|
||||||
throw new Error('没有找到对应的小说子任务数据,请检查ID是否正确')
|
|
||||||
}
|
|
||||||
// 获取小说项目的地址
|
// 获取小说项目的地址
|
||||||
let imageFolder = bookTask.imageFolder
|
let imageFolder = bookTask.imageFolder
|
||||||
if (imageFolder == null) {
|
if (imageFolder == null) {
|
||||||
throw new Error('没有找到对应的小说项目的图片地址,请检查')
|
throw new Error(t("没有找到对应的小说批次任务的图片输出地址,请检查"))
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentImagePath = path.join(imageFolder, `${bookTaskDetail.name}.png`)
|
let currentImagePath = path.join(imageFolder, `${bookTaskDetail.name}.png`)
|
||||||
@ -93,7 +92,7 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
}
|
}
|
||||||
sourceImagePath = path.resolve(sourceImagePath)
|
sourceImagePath = path.resolve(sourceImagePath)
|
||||||
if (!(await CheckFileOrDirExist(sourceImagePath))) {
|
if (!(await CheckFileOrDirExist(sourceImagePath))) {
|
||||||
throw new Error(`图片文件 ${sourceImagePath} 不存在,请检查`)
|
throw new Error(t("图片文件 {sourceImagePath} 不存在,请检查", { sourceImagePath }))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制文件,判断父文件夹是不是存在
|
// 复制文件,判断父文件夹是不是存在
|
||||||
@ -107,12 +106,14 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
// 返回数据
|
// 返回数据
|
||||||
return successMessage(
|
return successMessage(
|
||||||
currentImagePath + `?t=${Date.now()}`,
|
currentImagePath + `?t=${Date.now()}`,
|
||||||
'将指定的图片放到主图中成功',
|
t('将指定的图片放到主图中成功'),
|
||||||
'BookImage_MoveImageToMainImage'
|
'BookImage_MoveImageToMainImage'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'将指定的图片放到主图中失败,错误信息如下:' + error.message,
|
t("将指定的图片放到主图中失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookImage_MoveImageToMainImage'
|
'BookImage_MoveImageToMainImage'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -163,19 +164,17 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
bookTaskId: id
|
bookTaskId: id
|
||||||
})
|
})
|
||||||
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
|
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
|
||||||
let tempBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id)
|
let tempBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id, true)
|
||||||
if (tempBookTaskDetail == null) {
|
|
||||||
throw new Error('没有找到要删除的分镜数据,请检查ID是否正确')
|
|
||||||
}
|
|
||||||
bookTaskDetails = [tempBookTaskDetail]
|
bookTaskDetails = [tempBookTaskDetail]
|
||||||
} else {
|
} else {
|
||||||
throw new Error('不支持的操作类型,请检查')
|
throw new Error(t("未知操作"))
|
||||||
}
|
}
|
||||||
//这边过滤被锁定的数据
|
//这边过滤被锁定的数据
|
||||||
bookTaskDetails = bookTaskDetails.filter((item) => !item.imageLock)
|
bookTaskDetails = bookTaskDetails.filter((item) => !item.imageLock)
|
||||||
|
|
||||||
if (bookTaskDetails.length <= 0) {
|
if (bookTaskDetails.length <= 0) {
|
||||||
throw new Error('没有要删除的分镜数据,请检查')
|
throw new Error(t("没有要删除的分镜数据,请检查"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始删除数据,要删除图片数据和出图的信息
|
// 开始删除数据,要删除图片数据和出图的信息
|
||||||
@ -191,12 +190,14 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
|
|
||||||
return successMessage(
|
return successMessage(
|
||||||
bookTaskDetails,
|
bookTaskDetails,
|
||||||
'删除所有的生图数据成功',
|
t('删除所有的生图数据成功'),
|
||||||
'BookImage_ResetGenerateImage'
|
'BookImage_ResetGenerateImage'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'删除所有的图片数据失败,失败信息如下:' + error.toString(),
|
t("删除所有的图片数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookImage_ResetGenerateImage'
|
'BookImage_ResetGenerateImage'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -233,14 +234,14 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
console.log('开始上传图片', bookTaskDetailId, imageFile, option)
|
console.log('开始上传图片', bookTaskDetailId, imageFile, option)
|
||||||
if (!(await CheckFileOrDirExist(path.resolve(imageFile)))) {
|
if (!(await CheckFileOrDirExist(path.resolve(imageFile)))) {
|
||||||
throw new Error(`图片文件 ${imageFile} 不存在,请检查`)
|
throw new Error(t("图片文件 {sourceImagePath} 不存在,请检查", {
|
||||||
|
sourceImagePath: imageFile
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
// 修改数据库数据
|
// 修改数据库数据
|
||||||
let bookTaskDetail =
|
let bookTaskDetail =
|
||||||
await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId)
|
await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId, true)
|
||||||
if (bookTaskDetail == null) {
|
|
||||||
throw new Error('没有找到对应的小说子任务数据,请检查ID是否正确')
|
|
||||||
}
|
|
||||||
let projectPath = await getProjectPath()
|
let projectPath = await getProjectPath()
|
||||||
// 将图片复制到对应的文件夹中
|
// 将图片复制到对应的文件夹中
|
||||||
// 生成一个0-10的随机数
|
// 生成一个0-10的随机数
|
||||||
@ -254,7 +255,8 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
if (option == 'outImagePath') {
|
if (option == 'outImagePath') {
|
||||||
let outImagePath = bookTaskDetail.outImagePath
|
let outImagePath = bookTaskDetail.outImagePath
|
||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
||||||
bookTaskDetail.bookTaskId as string
|
bookTaskDetail.bookTaskId as string,
|
||||||
|
true
|
||||||
)
|
)
|
||||||
if (isEmpty(bookTaskDetail.outImagePath)) {
|
if (isEmpty(bookTaskDetail.outImagePath)) {
|
||||||
outImagePath = path.join(
|
outImagePath = path.join(
|
||||||
@ -284,7 +286,7 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
})
|
})
|
||||||
return newImagePath + '?t=' + new Date().getTime()
|
return newImagePath + '?t=' + new Date().getTime()
|
||||||
} else {
|
} else {
|
||||||
throw new Error('无效的操作类型,请检查')
|
throw new Error(t('未知操作'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,7 +333,7 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
let res = await this.UploadOne(bookTaskDetailId, imageFile as string, option)
|
let res = await this.UploadOne(bookTaskDetailId, imageFile as string, option)
|
||||||
return successMessage(
|
return successMessage(
|
||||||
res,
|
res,
|
||||||
'上传图片,并修改小说信息成功',
|
t('上传图片,并修改小说信息成功'),
|
||||||
'BookImage_UpLoadImageToBookAndUpdateMessage'
|
'BookImage_UpLoadImageToBookAndUpdateMessage'
|
||||||
)
|
)
|
||||||
} else if (option == 'subImagePath') {
|
} else if (option == 'subImagePath') {
|
||||||
@ -342,15 +344,17 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
}
|
}
|
||||||
return successMessage(
|
return successMessage(
|
||||||
subImage as string[],
|
subImage as string[],
|
||||||
'上传图片,并修改小说信息成功',
|
t('上传图片,并修改小说信息成功'),
|
||||||
'BookImage_UpLoadImageToBookAndUpdateMessage'
|
'BookImage_UpLoadImageToBookAndUpdateMessage'
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('无效的操作类型,请检查')
|
throw new Error(t('未知操作'))
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'上传图片到小说中,并修改小说信息失败,错误信息如下:' + error.message,
|
t("上传图片,并修改小说信息失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookImage_UpLoadImageToBookAndUpdateMessage'
|
'BookImage_UpLoadImageToBookAndUpdateMessage'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -379,18 +383,17 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
*/
|
*/
|
||||||
private async HDOneImagePrivate(bookTaskDetailId: string, scale: number = 2): Promise<string> {
|
private async HDOneImagePrivate(bookTaskDetailId: string, scale: number = 2): Promise<string> {
|
||||||
let bookTaskDetail =
|
let bookTaskDetail =
|
||||||
await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId)
|
await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId, true)
|
||||||
if (bookTaskDetail == null) {
|
|
||||||
throw new Error('没有找到要高清的分镜数据,请检查ID是否正确')
|
|
||||||
}
|
|
||||||
|
|
||||||
let outImagePath = bookTaskDetail.outImagePath as string
|
let outImagePath = bookTaskDetail.outImagePath as string
|
||||||
if (isEmpty(outImagePath)) {
|
if (isEmpty(outImagePath)) {
|
||||||
throw new Error('没有找到要高清的分镜数据,请检查ID是否正确')
|
throw new Error(t("未检测到分镜的输出图片,无法进行高清处理"))
|
||||||
}
|
}
|
||||||
let imageExist = await CheckFileOrDirExist(outImagePath as string)
|
let imageExist = await CheckFileOrDirExist(outImagePath as string)
|
||||||
if (!imageExist) {
|
if (!imageExist) {
|
||||||
throw new Error('没有找到要高清的图片,请检查图片是否存在')
|
throw new Error(t("图片文件 {sourceImagePath} 不存在,请检查", {
|
||||||
|
sourceImagePath: outImagePath
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
let newImagePath = path.join(
|
let newImagePath = path.join(
|
||||||
@ -439,11 +442,13 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
// 高清完成
|
// 高清完成
|
||||||
return successMessage(
|
return successMessage(
|
||||||
outImagePath + '?t=' + new Date().getTime(),
|
outImagePath + '?t=' + new Date().getTime(),
|
||||||
'高清分镜成功',
|
t('分镜高清图片成功'),
|
||||||
'BookImage_HDOneImage'
|
'BookImage_HDOneImage'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage('高清分镜失败,失败信息如下:' + error.toString(), 'BookImage_HDOneImage')
|
return errorMessage(t("分镜高清图片失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}), 'BookImage_HDOneImage')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -489,20 +494,14 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
try {
|
try {
|
||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
let bookTaskDetail =
|
let bookTaskDetail =
|
||||||
await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId)
|
await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId, true)
|
||||||
if (bookTaskDetail == null) {
|
|
||||||
throw new Error('获取到的数据分镜为空,无法执行操作')
|
let book = await this.bookService.GetBookDataById(bookTaskDetail.bookId as string, true)
|
||||||
}
|
|
||||||
let book = await this.bookService.GetBookDataById(bookTaskDetail.bookId as string)
|
|
||||||
if (book == null) {
|
|
||||||
throw new Error('获取到的小说为空,无法执行操作')
|
|
||||||
}
|
|
||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
||||||
bookTaskDetail.bookTaskId as string
|
bookTaskDetail.bookTaskId as string, true
|
||||||
)
|
)
|
||||||
if (bookTask == null) {
|
|
||||||
throw new Error('获取到的任务为空,无法执行操作')
|
|
||||||
}
|
|
||||||
let imagePath = path.join(
|
let imagePath = path.join(
|
||||||
book.bookFolderPath as string,
|
book.bookFolderPath as string,
|
||||||
`data\\MJOriginalImage\\${bookTaskDetail.id}_${new Date().getTime()}.png`
|
`data\\MJOriginalImage\\${bookTaskDetail.id}_${new Date().getTime()}.png`
|
||||||
@ -530,7 +529,7 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
path.join(book.bookFolderPath as string, 'data\\MJOriginalImage')
|
path.join(book.bookFolderPath as string, 'data\\MJOriginalImage')
|
||||||
)
|
)
|
||||||
if (imageRes && imageRes.length < 4) {
|
if (imageRes && imageRes.length < 4) {
|
||||||
throw new Error('图片裁剪失败')
|
throw new Error(t('图片裁剪失败'))
|
||||||
}
|
}
|
||||||
// 修改数据
|
// 修改数据
|
||||||
// 修改数据库数据,将图片保存到对应的文件夹中
|
// 修改数据库数据,将图片保存到对应的文件夹中
|
||||||
@ -583,14 +582,17 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
subImagePath: imageRes.map((item) => item + '?time=' + new Date().getTime()),
|
subImagePath: imageRes.map((item) => item + '?time=' + new Date().getTime()),
|
||||||
mjMessage: mjMessage
|
mjMessage: mjMessage
|
||||||
},
|
},
|
||||||
'下载指定的图片地址并且分割成功',
|
t('下载指定的图片地址并且分割成功'),
|
||||||
'BookImage_DownloadImageUrlAndSplit'
|
'BookImage_DownloadImageUrlAndSplit'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return errorMessage(
|
||||||
code: 0,
|
t("下载指定的图片地址并且分割失败,{error}", {
|
||||||
message: '下载指定的图片地址并且分割错误,错误信息如下:' + error.message
|
error: error.message
|
||||||
}
|
}),
|
||||||
|
'BookImage_DownloadImageUrlAndSplit'
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -602,35 +604,25 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
* 获取MJ图片链接、下载图片、分割处理、保存到项目目录和更新数据库信息。
|
* 获取MJ图片链接、下载图片、分割处理、保存到项目目录和更新数据库信息。
|
||||||
*
|
*
|
||||||
* 注意:此方法仅在MJ API模式下有效,其他图片生成模式不支持此操作。
|
* 注意:此方法仅在MJ API模式下有效,其他图片生成模式不支持此操作。
|
||||||
*
|
|
||||||
* @param {string} id - 目标ID,可以是书籍任务ID或分镜ID,取决于operateBookType参数
|
|
||||||
* @param {OperateBookType} operateBookType - 操作类型,指定id参数代表的是书籍任务还是单个分镜
|
|
||||||
* - BOOKTASK: 处理整个书籍任务下的所有分镜
|
|
||||||
* - BOOKTASKDETAIL: 仅处理单个指定分镜
|
|
||||||
* @param {boolean} coverData - 是否覆盖现有图片数据
|
|
||||||
* - true: 覆盖所有分镜的图片数据,包括已有图片
|
|
||||||
* - false: 仅处理尚未有图片的分镜
|
|
||||||
*
|
|
||||||
* @returns {Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem>}
|
* @returns {Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem>}
|
||||||
* 成功时返回包含下载和处理后的图片数据的对象数组,失败时返回错误信息
|
成功时返回包含下载和处理后的图片数据的对象数组,失败时返回错误信息
|
||||||
*
|
*
|
||||||
* @throws {Error} 当找不到分镜数据、不支持的操作类型、非MJ模式、非MJ API模式、
|
* @throws {Error} 当找不到分镜数据、不支持的操作类型、非MJ模式、非MJ API模式、
|
||||||
* 图片链接为空或下载失败时抛出错误
|
图片链接为空或下载失败时抛出错误
|
||||||
*
|
* @example // 下载并应用整个书籍任务的所有分镜图片(不覆盖已有图片)
|
||||||
* @example
|
// 下载并应用整个书籍任务的所有分镜图片(不覆盖已有图片)
|
||||||
* // 下载并应用整个书籍任务的所有分镜图片(不覆盖已有图片)
|
const result = await bookImageHandle.GetImageUrlAndDownload(
|
||||||
* const result = await bookImageHandle.GetImageUrlAndDownload(
|
"task-123",
|
||||||
* "task-123",
|
OperateBookType.BOOKTASK,
|
||||||
* OperateBookType.BOOKTASK,
|
false
|
||||||
* false
|
);
|
||||||
* );
|
|
||||||
*
|
// 下载并应用单个分镜图片(覆盖已有图片)
|
||||||
* // 下载并应用单个分镜图片(覆盖已有图片)
|
const result = await bookImageHandle.GetImageUrlAndDownload(
|
||||||
* const result = await bookImageHandle.GetImageUrlAndDownload(
|
"detail-456",
|
||||||
* "detail-456",
|
OperateBookType.BOOKTASKDETAIL,
|
||||||
* OperateBookType.BOOKTASKDETAIL,
|
true
|
||||||
* true
|
);
|
||||||
* );
|
|
||||||
*/
|
*/
|
||||||
async GetImageUrlAndDownload(
|
async GetImageUrlAndDownload(
|
||||||
bookTaskDetailId: string
|
bookTaskDetailId: string
|
||||||
@ -639,43 +631,32 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
|
|
||||||
let bookTaskDetail =
|
let bookTaskDetail =
|
||||||
await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId)
|
await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId, true)
|
||||||
if (bookTaskDetail == null) {
|
|
||||||
throw new Error('没有找到要采集的分镜数据,请检查ID是否正确')
|
|
||||||
}
|
|
||||||
|
|
||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
||||||
bookTaskDetail.bookTaskId as string
|
bookTaskDetail.bookTaskId as string, true
|
||||||
)
|
)
|
||||||
if (bookTask == null) {
|
let book = await this.bookService.GetBookDataById(bookTask.bookId as string, true)
|
||||||
throw new Error('没有找到要采集的小说批次任务数据,请检查ID是否正确')
|
|
||||||
}
|
|
||||||
|
|
||||||
let book = await this.bookService.GetBookDataById(bookTask.bookId as string)
|
|
||||||
if (book == null) {
|
|
||||||
throw new Error('没有找到要采集的小说数据,请检查ID是否正确')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bookTask.imageCategory != ImageCategory.Midjourney) {
|
if (bookTask.imageCategory != ImageCategory.Midjourney) {
|
||||||
throw new Error('只有MJ模式下才能使用这个功能')
|
throw new Error(t('只有MJ模式下才能使用这个功能'))
|
||||||
}
|
}
|
||||||
let mjGeneralSettingOption = this.optionRealmService.GetOptionByKey(
|
let mjGeneralSettingOption = this.optionRealmService.GetOptionByKey(
|
||||||
OptionKeyName.Midjourney.GeneralSetting
|
OptionKeyName.Midjourney.GeneralSetting
|
||||||
)
|
)
|
||||||
let mjGeneralSetting = optionSerialization<SettingModal.MJGeneralSettings>(
|
let mjGeneralSetting = optionSerialization<SettingModal.MJGeneralSettings>(
|
||||||
mjGeneralSettingOption,
|
mjGeneralSettingOption,
|
||||||
'‘设置 -> MJ设置’'
|
t("设置 -> MJ设置")
|
||||||
)
|
)
|
||||||
if (mjGeneralSetting.outputMode != ImageGenerateMode.MJ_API) {
|
if (mjGeneralSetting.outputMode != ImageGenerateMode.MJ_API) {
|
||||||
throw new Error('只有MJ API模式下才能使用这个功能')
|
throw new Error(t('只有MJ API模式下才能使用这个功能'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bookTaskDetail.mjMessage) {
|
if (!bookTaskDetail.mjMessage) {
|
||||||
throw new Error('没有找到对应的分镜数据,请检查ID是否正确')
|
throw new Error(t('分镜中没有MJ的消息数据,请检查分镜数据'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEmpty(bookTaskDetail.mjMessage.messageId)) {
|
if (isEmpty(bookTaskDetail.mjMessage.messageId)) {
|
||||||
throw new Error('没有找到对应分镜的MJ Task ID,请检查分镜数据')
|
throw new Error(t('没有找到对应分镜的MJ Task ID,请检查分镜数据'))
|
||||||
}
|
}
|
||||||
// 这边开始采集
|
// 这边开始采集
|
||||||
let task_res = await this.mjApiService.GetMJAPITaskById(
|
let task_res = await this.mjApiService.GetMJAPITaskById(
|
||||||
@ -691,7 +672,7 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
`data\\MJOriginalImage\\${task_res.messageId}.png`
|
`data\\MJOriginalImage\\${task_res.messageId}.png`
|
||||||
)
|
)
|
||||||
if (isEmpty(task_res.imageClick)) {
|
if (isEmpty(task_res.imageClick)) {
|
||||||
throw new Error('没有找到对应的分镜的MJ图片链接,请检查分镜数据')
|
throw new Error(t('没有找到对应的分镜的MJ图片链接,请检查分镜数据'))
|
||||||
}
|
}
|
||||||
|
|
||||||
await CheckFolderExistsOrCreate(path.dirname(imagePath))
|
await CheckFolderExistsOrCreate(path.dirname(imagePath))
|
||||||
@ -706,7 +687,7 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (imageArray && imageArray.length < 4) {
|
if (imageArray && imageArray.length < 4) {
|
||||||
throw new Error('图片裁剪失败')
|
throw new Error(t('图片裁剪失败'))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < task_res.imageUrls.length; i++) {
|
for (let i = 0; i < task_res.imageUrls.length; i++) {
|
||||||
@ -750,10 +731,12 @@ export class BookImageHandle extends BookBasicHandle {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let result = await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId)
|
let result = await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId)
|
||||||
return successMessage(result, '获取图片链接并且下载成功', 'BookImage_GetImageUrlAndDownload')
|
return successMessage(result, t('获取图片链接并且下载成功'), 'BookImage_GetImageUrlAndDownload')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'获取图片链接并且下载失败,错误信息如下:' + error.message,
|
t("获取图片链接并且下载失败,{error}", {
|
||||||
|
error: (error as Error).message
|
||||||
|
}),
|
||||||
'BookImage_GetImageUrlAndDownload'
|
'BookImage_GetImageUrlAndDownload'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { SDServiceHandle } from '../../sd/sdServiceHandle'
|
|||||||
import { aiHandle } from '../../ai'
|
import { aiHandle } from '../../ai'
|
||||||
import { AICharacterAnalyseRequestData } from '@/define/data/aiData/aiPrompt/CharacterAndScene/aiCharacterAnalyseRequestData'
|
import { AICharacterAnalyseRequestData } from '@/define/data/aiData/aiPrompt/CharacterAndScene/aiCharacterAnalyseRequestData'
|
||||||
import { AISceneAnalyseRequestData } from '@/define/data/aiData/aiPrompt/CharacterAndScene/aiSceneAnalyseRequestData'
|
import { AISceneAnalyseRequestData } from '@/define/data/aiData/aiPrompt/CharacterAndScene/aiSceneAnalyseRequestData'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class BookPromptHandle extends BookBasicHandle {
|
export class BookPromptHandle extends BookBasicHandle {
|
||||||
aiReasonCommon: AiReasonCommon
|
aiReasonCommon: AiReasonCommon
|
||||||
@ -71,7 +72,7 @@ export class BookPromptHandle extends BookBasicHandle {
|
|||||||
|
|
||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
if (operateBookType == OperateBookType.BOOKTASK) {
|
if (operateBookType == OperateBookType.BOOKTASK) {
|
||||||
bookTask = await this.bookTaskService.GetBookTaskDataById(id)
|
bookTask = await this.bookTaskService.GetBookTaskDataById(id, true)
|
||||||
allBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
allBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
||||||
bookTaskId: id
|
bookTaskId: id
|
||||||
})
|
})
|
||||||
@ -83,21 +84,19 @@ export class BookPromptHandle extends BookBasicHandle {
|
|||||||
bookTaskDetails = allBookTaskDetails
|
bookTaskDetails = allBookTaskDetails
|
||||||
}
|
}
|
||||||
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
|
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
|
||||||
let singleBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id)
|
let singleBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id, true)
|
||||||
if (singleBookTaskDetail == null) {
|
|
||||||
throw new Error('没有找到要推理的分镜数据,请检查ID是否正确')
|
|
||||||
}
|
|
||||||
bookTask = await this.bookTaskService.GetBookTaskDataById(
|
bookTask = await this.bookTaskService.GetBookTaskDataById(
|
||||||
singleBookTaskDetail.bookTaskId as string
|
singleBookTaskDetail.bookTaskId as string,
|
||||||
|
true
|
||||||
)
|
)
|
||||||
bookTaskDetails = [singleBookTaskDetail]
|
bookTaskDetails = [singleBookTaskDetail]
|
||||||
} else if (operateBookType == OperateBookType.UNDERBOOKTASK) {
|
} else if (operateBookType == OperateBookType.UNDERBOOKTASK) {
|
||||||
let singleBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id)
|
let singleBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id, true)
|
||||||
if (singleBookTaskDetail == null) {
|
|
||||||
throw new Error('没有找到要推理的分镜数据,请检查ID是否正确')
|
|
||||||
}
|
|
||||||
bookTask = await this.bookTaskService.GetBookTaskDataById(
|
bookTask = await this.bookTaskService.GetBookTaskDataById(
|
||||||
singleBookTaskDetail.bookTaskId as string
|
singleBookTaskDetail.bookTaskId as string,
|
||||||
|
true
|
||||||
)
|
)
|
||||||
// 获取全部的分镜数据
|
// 获取全部的分镜数据
|
||||||
allBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
allBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
||||||
@ -115,7 +114,7 @@ export class BookPromptHandle extends BookBasicHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('未知的操作类型')
|
throw new Error(t('未知操作'))
|
||||||
}
|
}
|
||||||
if (!allBookTaskDetails || allBookTaskDetails.length <= 0) {
|
if (!allBookTaskDetails || allBookTaskDetails.length <= 0) {
|
||||||
allBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
allBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
||||||
@ -124,14 +123,14 @@ export class BookPromptHandle extends BookBasicHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (bookTaskDetails.length <= 0) {
|
if (bookTaskDetails.length <= 0) {
|
||||||
throw new Error('没有找到要推理的分镜数据')
|
throw new Error(t('没有找到要推理的分镜数据'))
|
||||||
}
|
}
|
||||||
let generalSettingOption = this.optionRealmService.GetOptionByKey(
|
let generalSettingOption = this.optionRealmService.GetOptionByKey(
|
||||||
OptionKeyName.Software.GeneralSetting
|
OptionKeyName.Software.GeneralSetting
|
||||||
)
|
)
|
||||||
let generalSetting = optionSerialization<SettingModal.GeneralSettings>(
|
let generalSetting = optionSerialization<SettingModal.GeneralSettings>(
|
||||||
generalSettingOption,
|
generalSettingOption,
|
||||||
'‘设置 -> 通用设置’'
|
t('设置 -> 通用设置')
|
||||||
)
|
)
|
||||||
let tasks = [] as Array<() => Promise<any>>
|
let tasks = [] as Array<() => Promise<any>>
|
||||||
|
|
||||||
@ -189,23 +188,41 @@ export class BookPromptHandle extends BookBasicHandle {
|
|||||||
// 分批次执行异步任务
|
// 分批次执行异步任务
|
||||||
await ExecuteConcurrently(tasks, global.am.isPro ? (generalSetting.concurrency ?? 1) : 1)
|
await ExecuteConcurrently(tasks, global.am.isPro ? (generalSetting.concurrency ?? 1) : 1)
|
||||||
// 执行完毕
|
// 执行完毕
|
||||||
return successMessage(null, '推理所有数据完成', 'BookPrompt_OriginalGetPrompt')
|
return successMessage(null, t("推理所有数据完成"), 'BookPrompt_OriginalGetPrompt')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`获取小说子任务详细数据失败,失败原因如下:${error.message}`,
|
t("推理所有数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookPromptHandle_OriginalGetAiPrompt'
|
'BookPromptHandle_OriginalGetAiPrompt'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI分镜头合并
|
||||||
|
* @param bookTaskId 小说任务ID
|
||||||
|
* @param type 合并类型
|
||||||
|
* @returns 操作结果
|
||||||
|
* @throws 操作失败时抛出异常
|
||||||
|
* @example
|
||||||
|
* const result = await bookPromptHandle.AIStoryboardMerge("task-123", BookTask.StoryboardMergeType.MJ);
|
||||||
|
* if (result.code === 1) {
|
||||||
|
* // 合并成功
|
||||||
|
* } else {
|
||||||
|
* // 合并失败
|
||||||
|
* }
|
||||||
|
*/
|
||||||
AIStoryboardMerge = async (bookTaskId: string, type: BookTask.StoryboardMergeType) => {
|
AIStoryboardMerge = async (bookTaskId: string, type: BookTask.StoryboardMergeType) => {
|
||||||
try {
|
try {
|
||||||
let res = await aiHandle.AIStoryboardMerge(bookTaskId, type)
|
let res = await aiHandle.AIStoryboardMerge(bookTaskId, type)
|
||||||
return successMessage(res, 'AI分镜头合并成功', 'BookPromptHandle_AIStoryboardMerge')
|
return successMessage(res, t('AI分镜头合并成功'), 'BookPromptHandle_AIStoryboardMerge')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`AI分镜头合并失败,失败原因如下:${error.message}`,
|
t('AI分镜头合并成功', {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookPromptHandle_AIStoryboardMerge'
|
'BookPromptHandle_AIStoryboardMerge'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -250,11 +267,13 @@ export class BookPromptHandle extends BookBasicHandle {
|
|||||||
} else if (type == PromptMergeType.SD_MERGE) {
|
} else if (type == PromptMergeType.SD_MERGE) {
|
||||||
return await this.sdServiceHandle.MergeSDPrompt(id, operateBookType)
|
return await this.sdServiceHandle.MergeSDPrompt(id, operateBookType)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('未知的合并模式,请检查')
|
throw new Error(t('未知的合并模式,请检查'))
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'合并提示词失败,错误信息如下:' + error.message,
|
t("合并提示词失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'ReverseBook_MergePrompt'
|
'ReverseBook_MergePrompt'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -288,19 +307,19 @@ export class BookPromptHandle extends BookBasicHandle {
|
|||||||
* PresetCategory.Scene
|
* PresetCategory.Scene
|
||||||
* );
|
* );
|
||||||
*/
|
*/
|
||||||
AutoAnalyzeCharacterOrScene = async (bookTaskId: string, type: PresetCategory) => {
|
AutoAnalyzeCharacterOrScene = async (bookTaskId: string, type: PresetCategory): Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> => {
|
||||||
try {
|
try {
|
||||||
if (type != PresetCategory.Character && type != PresetCategory.Scene) {
|
if (type != PresetCategory.Character && type != PresetCategory.Scene) {
|
||||||
throw new Error('分析的类型只能是角色或场景,请检查')
|
throw new Error(t('分析的类型只能是角色或场景,请检查'))
|
||||||
}
|
}
|
||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
|
|
||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId)
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId, true)
|
||||||
let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
||||||
bookTaskId: bookTaskId
|
bookTaskId: bookTaskId
|
||||||
})
|
})
|
||||||
if (bookTaskDetails.length <= 0) {
|
if (bookTaskDetails.length <= 0) {
|
||||||
throw new Error('没有找到要分析的分镜数据,请先导入文案或者时srt!')
|
throw new Error(t('没有找到要分析的分镜数据,请先导入文案或者时srt!'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let words = bookTaskDetails
|
let words = bookTaskDetails
|
||||||
@ -315,7 +334,7 @@ export class BookPromptHandle extends BookBasicHandle {
|
|||||||
} else if (type == PresetCategory.Scene) {
|
} else if (type == PresetCategory.Scene) {
|
||||||
requestData = AISceneAnalyseRequestData
|
requestData = AISceneAnalyseRequestData
|
||||||
} else {
|
} else {
|
||||||
throw new Error('未知的分析类型,请检查')
|
throw new Error(t('未知的分析类型,请检查'))
|
||||||
}
|
}
|
||||||
|
|
||||||
requestData.messages = this.aiReasonCommon.replaceMessageObject(requestData.messages, {
|
requestData.messages = this.aiReasonCommon.replaceMessageObject(requestData.messages, {
|
||||||
@ -359,12 +378,14 @@ export class BookPromptHandle extends BookBasicHandle {
|
|||||||
|
|
||||||
return successMessage(
|
return successMessage(
|
||||||
autoAnalyzeCharacterData,
|
autoAnalyzeCharacterData,
|
||||||
'自动分析角色或场景成功',
|
t('自动分析角色或场景成功'),
|
||||||
'ReverseBook_AutoAnalyzeCharacterOrScene'
|
'ReverseBook_AutoAnalyzeCharacterOrScene'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'自动分析角色或场景失败,错误信息如下:' + error.message,
|
t("自动分析角色或场景失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'ReverseBook_AutoAnalyzeCharacterOrScene'
|
'ReverseBook_AutoAnalyzeCharacterOrScene'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { BookBasicHandle } from './bookBasicHandle'
|
|||||||
import { CheckFileOrDirExist, DeleteFolderAllFile } from '@/define/Tools/file'
|
import { CheckFileOrDirExist, DeleteFolderAllFile } from '@/define/Tools/file'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 书籍服务处理器类
|
* 书籍服务处理器类
|
||||||
@ -34,11 +35,13 @@ export class BookServiceHandle extends BookBasicHandle {
|
|||||||
try {
|
try {
|
||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
let res = await this.bookService.AddOrModifyBook(book)
|
let res = await this.bookService.AddOrModifyBook(book)
|
||||||
return successMessage(res, '添加/修改小说信息成功!', 'BookServiceHandle_AddOrModifyBook')
|
return successMessage(res, t('添加/修改小说信息成功!'), 'BookServiceHandle_AddOrModifyBook')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`添加/修改小说信息失败,失败原因如下:${error.message}`,
|
t("添加/修改小说信息失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookServiceHandle_AddOrModifyBook'
|
'BookServiceHandle_AddOrModifyBook'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -59,11 +62,13 @@ export class BookServiceHandle extends BookBasicHandle {
|
|||||||
try {
|
try {
|
||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
let res = await this.bookService.GetBookDataCondition(queryCondition)
|
let res = await this.bookService.GetBookDataCondition(queryCondition)
|
||||||
return successMessage(res, '获取小说数据成功!', 'BookServiceHandle_GetBookDataCondition')
|
return successMessage(res, t('获取小说数据成功!'), 'BookServiceHandle_GetBookDataCondition')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`获取小说数据失败,失败原因如下:${error.message}`,
|
t("获取小说数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookServiceHandle_GetBookDataCondition'
|
'BookServiceHandle_GetBookDataCondition'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -82,11 +87,13 @@ export class BookServiceHandle extends BookBasicHandle {
|
|||||||
try {
|
try {
|
||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
let res = await this.bookService.GetBookDataById(id)
|
let res = await this.bookService.GetBookDataById(id)
|
||||||
return successMessage(res, '获取小说数据成功!', 'BookServiceHandle_GetBookDataById')
|
return successMessage(res, t('获取小说数据成功!'), 'BookServiceHandle_GetBookDataById')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`获取小说数据失败,失败原因如下:${error.message}`,
|
t("获取小说数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookServiceHandle_GetBookDataById'
|
'BookServiceHandle_GetBookDataById'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -109,11 +116,13 @@ export class BookServiceHandle extends BookBasicHandle {
|
|||||||
try {
|
try {
|
||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
let res = await this.bookService.ModifyBookDataById(id, bookData)
|
let res = await this.bookService.ModifyBookDataById(id, bookData)
|
||||||
return successMessage(res, '修改小说数据成功!', 'BookServiceHandle_ModifyBookDataById')
|
return successMessage(res, t('修改小说数据成功!'), 'BookServiceHandle_ModifyBookDataById')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`修改小说数据失败,失败原因如下:${error.message}`,
|
t("修改小说数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookServiceHandle_ModifyBookDataById'
|
'BookServiceHandle_ModifyBookDataById'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -123,13 +132,7 @@ export class BookServiceHandle extends BookBasicHandle {
|
|||||||
async ResetBookDataInfo(bookId: string) {
|
async ResetBookDataInfo(bookId: string) {
|
||||||
try {
|
try {
|
||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
let book = await this.bookService.GetBookDataById(bookId)
|
let book = await this.bookService.GetBookDataById(bookId, true)
|
||||||
if (book == null) {
|
|
||||||
return errorMessage(
|
|
||||||
'未找到小说数据,重置小说数据失败!',
|
|
||||||
'BookServiceHandle_ResetBookDataInfo'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let bookDataPath = path.resolve(book.bookFolderPath as string, 'data')
|
let bookDataPath = path.resolve(book.bookFolderPath as string, 'data')
|
||||||
let bookScriptPath = path.resolve(book.bookFolderPath as string, 'script')
|
let bookScriptPath = path.resolve(book.bookFolderPath as string, 'script')
|
||||||
@ -159,10 +162,12 @@ export class BookServiceHandle extends BookBasicHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 删除完毕 返回
|
// 删除完毕 返回
|
||||||
return successMessage(null, '重置小说数据成功!', 'BookServiceHandle_ResetBookDataInfo')
|
return successMessage(null, t('重置小说数据成功!'), 'BookServiceHandle_ResetBookDataInfo')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`重置小说数据失败,失败原因如下:${error.message}`,
|
t("重置小说数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookServiceHandle_ResetBookDataInfo'
|
'BookServiceHandle_ResetBookDataInfo'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -185,19 +190,13 @@ export class BookServiceHandle extends BookBasicHandle {
|
|||||||
let resetRes = await this.ResetBookDataInfo(id)
|
let resetRes = await this.ResetBookDataInfo(id)
|
||||||
if (resetRes.code != 1) {
|
if (resetRes.code != 1) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`删除小说失败,失败原因如下:${resetRes.message}`,
|
t("删除小说数据失败,{error}", {
|
||||||
|
error: resetRes.message
|
||||||
|
}),
|
||||||
'BookServiceHandle_DeleteBookDataInfoById'
|
'BookServiceHandle_DeleteBookDataInfoById'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
let book = await this.bookService.GetBookDataById(id, true)
|
||||||
let book = await this.bookService.GetBookDataById(id)
|
|
||||||
if (book == null) {
|
|
||||||
return errorMessage(
|
|
||||||
'未找到小说数据,删除小说失败!',
|
|
||||||
'BookServiceHandle_DeleteBookDataInfoById'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let projectPath = book.bookFolderPath as string
|
let projectPath = book.bookFolderPath as string
|
||||||
|
|
||||||
this.bookService.DeleteBookDataById(id)
|
this.bookService.DeleteBookDataById(id)
|
||||||
@ -206,11 +205,13 @@ export class BookServiceHandle extends BookBasicHandle {
|
|||||||
if (!isEmpty(projectPath) && (await CheckFileOrDirExist(projectPath))) {
|
if (!isEmpty(projectPath) && (await CheckFileOrDirExist(projectPath))) {
|
||||||
await DeleteFolderAllFile(projectPath, true)
|
await DeleteFolderAllFile(projectPath, true)
|
||||||
}
|
}
|
||||||
return successMessage(null, '删除小说数据成功!', 'BookServiceHandle_DeleteBookDataById')
|
return successMessage(null, t('删除小说数据成功!'), 'BookServiceHandle_DeleteBookDataById')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`删除小说数据失败,失败原因如下:${error.message}`,
|
t("删除小说数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookServiceHandle_DeleteBookDataById'
|
'BookServiceHandle_DeleteBookDataById'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,19 @@
|
|||||||
import { BookTaskDetailService } from '@/define/db/service/book/bookTaskDetailService'
|
|
||||||
import { BookTaskStatus, OperateBookType } from '@/define/enum/bookEnum'
|
import { BookTaskStatus, OperateBookType } from '@/define/enum/bookEnum'
|
||||||
import { Book } from '@/define/model/book/book'
|
import { Book } from '@/define/model/book/book'
|
||||||
import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
||||||
import { errorMessage, successMessage } from '@/public/generalTools'
|
import { errorMessage, successMessage } from '@/public/generalTools'
|
||||||
import { BookTaskServiceHandle } from './bookTaskServiceHandle'
|
import { BookTaskServiceHandle } from './bookTaskServiceHandle'
|
||||||
import { ErrorItem, SuccessItem } from '@/define/model/generalResponse'
|
import { ErrorItem, SuccessItem } from '@/define/model/generalResponse'
|
||||||
|
import { BookBasicHandle } from './bookBasicHandle'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class BookTaskDetailServiceHandle {
|
export class BookTaskDetailServiceHandle extends BookBasicHandle {
|
||||||
private bookTaskDetailService!: BookTaskDetailService
|
|
||||||
private bookTaskServiceHandle: BookTaskServiceHandle
|
private bookTaskServiceHandle: BookTaskServiceHandle
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
super()
|
||||||
this.bookTaskServiceHandle = new BookTaskServiceHandle()
|
this.bookTaskServiceHandle = new BookTaskServiceHandle()
|
||||||
}
|
}
|
||||||
private async InitBookTaskDetailServiceHandle() {
|
|
||||||
// 如果 bookTaskDetailService 已经初始化,则直接返回
|
|
||||||
if (this.bookTaskDetailService) return
|
|
||||||
this.bookTaskDetailService = await BookTaskDetailService.getInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取小说子任务详细数据
|
* 获取小说子任务详细数据
|
||||||
@ -32,18 +28,20 @@ export class BookTaskDetailServiceHandle {
|
|||||||
bookTaskDetailCondition: Book.QueryBookTaskDetailCondition
|
bookTaskDetailCondition: Book.QueryBookTaskDetailCondition
|
||||||
): Promise<SuccessItem | ErrorItem> {
|
): Promise<SuccessItem | ErrorItem> {
|
||||||
try {
|
try {
|
||||||
await this.InitBookTaskDetailServiceHandle()
|
await this.InitBookBasicHandle()
|
||||||
let res =
|
let res =
|
||||||
await this.bookTaskDetailService.GetBookTaskDetailDataByCondition(bookTaskDetailCondition)
|
await this.bookTaskDetailService.GetBookTaskDetailDataByCondition(bookTaskDetailCondition)
|
||||||
return successMessage(
|
return successMessage(
|
||||||
res,
|
res,
|
||||||
'获取小说子任务详细数据成功!',
|
t('获取小说分镜数据成功!'),
|
||||||
'BookTaskDetailServiceHandle_GetBookTaskDetailData'
|
'BookTaskDetailServiceHandle_GetBookTaskDetailData'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`获取小说子任务详细数据失败,失败原因如下:${error.message}`,
|
t("获取小说分镜数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskDetailServiceHandle_GetBookTaskDetailData'
|
'BookTaskDetailServiceHandle_GetBookTaskDetailData'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -60,17 +58,19 @@ export class BookTaskDetailServiceHandle {
|
|||||||
*/
|
*/
|
||||||
async GetBookTaskDetailDataById(id: string): Promise<SuccessItem | ErrorItem> {
|
async GetBookTaskDetailDataById(id: string): Promise<SuccessItem | ErrorItem> {
|
||||||
try {
|
try {
|
||||||
await this.InitBookTaskDetailServiceHandle()
|
await this.InitBookBasicHandle()
|
||||||
let res = await this.bookTaskDetailService.GetBookTaskDetailDataById(id)
|
let res = await this.bookTaskDetailService.GetBookTaskDetailDataById(id)
|
||||||
return successMessage(
|
return successMessage(
|
||||||
res,
|
res,
|
||||||
'获取小说子任务详细数据成功!',
|
t('获取小说分镜数据成功!'),
|
||||||
'BookTaskDetailServiceHandle_GetBookTaskDetailDataById'
|
'BookTaskDetailServiceHandle_GetBookTaskDetailDataById'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`获取小说子任务详细数据失败,失败原因如下:${error.message}`,
|
t("获取小说分镜数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskDetailServiceHandle_GetBookTaskDetailDataById'
|
'BookTaskDetailServiceHandle_GetBookTaskDetailDataById'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -91,20 +91,22 @@ export class BookTaskDetailServiceHandle {
|
|||||||
updateData: Book.SelectBookTaskDetail
|
updateData: Book.SelectBookTaskDetail
|
||||||
): Promise<SuccessItem | ErrorItem> {
|
): Promise<SuccessItem | ErrorItem> {
|
||||||
try {
|
try {
|
||||||
await this.InitBookTaskDetailServiceHandle()
|
await this.InitBookBasicHandle()
|
||||||
let res = await this.bookTaskDetailService.ModifyBookTaskDetailById(
|
let res = await this.bookTaskDetailService.ModifyBookTaskDetailById(
|
||||||
bookTaskDetailId,
|
bookTaskDetailId,
|
||||||
updateData
|
updateData
|
||||||
)
|
)
|
||||||
return successMessage(
|
return successMessage(
|
||||||
res,
|
res,
|
||||||
'修改小说子任务详细数据成功!',
|
t('修改小说分镜数据成功!'),
|
||||||
'BookTaskDetailServiceHandle_ModifyBookTaskDetailById'
|
'BookTaskDetailServiceHandle_ModifyBookTaskDetailById'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`修改小说子任务详细数据失败,失败原因如下:${error.message}`,
|
t("修改小说分镜数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskDetailServiceHandle_ModifyBookTaskDetailById'
|
'BookTaskDetailServiceHandle_ModifyBookTaskDetailById'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -128,14 +130,16 @@ export class BookTaskDetailServiceHandle {
|
|||||||
): Promise<SuccessItem | ErrorItem> {
|
): Promise<SuccessItem | ErrorItem> {
|
||||||
try {
|
try {
|
||||||
if (operateBookType != OperateBookType.BOOKTASK) {
|
if (operateBookType != OperateBookType.BOOKTASK) {
|
||||||
throw new Error('目前只支持对小说任务的文案保存')
|
throw new Error(t("目前只支持对小说批次任务的文案保存"))
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.bookTaskServiceHandle.InitBookBasicHandle()
|
await this.bookTaskServiceHandle.InitBookBasicHandle()
|
||||||
let bookTask =
|
let bookTask = await this.bookTaskServiceHandle.bookTaskService.GetBookTaskDataById(
|
||||||
await this.bookTaskServiceHandle.bookTaskService.GetBookTaskDataById(bookTaskId)
|
bookTaskId,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
await this.InitBookTaskDetailServiceHandle()
|
await this.InitBookBasicHandle()
|
||||||
let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
||||||
bookTaskId: bookTaskId
|
bookTaskId: bookTaskId
|
||||||
})
|
})
|
||||||
@ -153,7 +157,7 @@ export class BookTaskDetailServiceHandle {
|
|||||||
bookTaskDetails[i].id
|
bookTaskDetails[i].id
|
||||||
)
|
)
|
||||||
if (btd == null) {
|
if (btd == null) {
|
||||||
throw new Error('未找到对应的分镜数据,请检查')
|
throw new Error(t('未找到对应的小说分镜信息'))
|
||||||
}
|
}
|
||||||
// 开始修改
|
// 开始修改
|
||||||
btd.startTime = element.startTime
|
btd.startTime = element.startTime
|
||||||
@ -195,7 +199,7 @@ export class BookTaskDetailServiceHandle {
|
|||||||
bookTaskDetails[i].id
|
bookTaskDetails[i].id
|
||||||
)
|
)
|
||||||
if (btd == null) {
|
if (btd == null) {
|
||||||
throw new Error('未找到对应的分镜数据,请检查')
|
throw new Error(t('未找到对应的小说分镜信息'))
|
||||||
}
|
}
|
||||||
// 开始修改
|
// 开始修改
|
||||||
btd.startTime = element.startTime
|
btd.startTime = element.startTime
|
||||||
@ -230,17 +234,19 @@ export class BookTaskDetailServiceHandle {
|
|||||||
bookTaskId: bookTaskId
|
bookTaskId: bookTaskId
|
||||||
})
|
})
|
||||||
if (newData.length == 0) {
|
if (newData.length == 0) {
|
||||||
throw new Error('没有找到对应的分镜数据,请检查')
|
throw new Error(t('未找到对应的小说分镜信息'))
|
||||||
}
|
}
|
||||||
return successMessage(
|
return successMessage(
|
||||||
newData,
|
newData,
|
||||||
'保存小说批次数据分镜信息成功!',
|
t('保存小说分镜数据成功!'),
|
||||||
'BookTaskDetailServiceHandle_SaveCopywritingInfo'
|
'BookTaskDetailServiceHandle_SaveCopywritingInfo'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`保存小说批次数据分镜信息失败,失败原因如下:${error.message}`,
|
t("保存小说分镜数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskDetailServiceHandle_SaveCopywritingInfo'
|
'BookTaskDetailServiceHandle_SaveCopywritingInfo'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import { ValidateJson } from '@/define/Tools/validate'
|
|||||||
import { TimeStringToMilliseconds } from '@/define/Tools/time'
|
import { TimeStringToMilliseconds } from '@/define/Tools/time'
|
||||||
import { SrtHandle } from '../../common/srtHandle'
|
import { SrtHandle } from '../../common/srtHandle'
|
||||||
import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class BookTaskServiceHandle extends BookBasicHandle {
|
export class BookTaskServiceHandle extends BookBasicHandle {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -49,13 +50,15 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
let res = await this.bookTaskService.GetBookTaskDataByCondition(bookTaskCondition)
|
let res = await this.bookTaskService.GetBookTaskDataByCondition(bookTaskCondition)
|
||||||
return successMessage(
|
return successMessage(
|
||||||
res,
|
res,
|
||||||
'获取小说子任务数据成功!',
|
t("获取小说批次任务数据成功!"),
|
||||||
'BookTaskServiceHandle_GetBookTaskData'
|
'BookTaskServiceHandle_GetBookTaskData'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`获取小说子任务数据失败,失败原因如下:${error.message}`,
|
t("获取小说批次任务数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskServiceHandle_GetBookTaskData'
|
'BookTaskServiceHandle_GetBookTaskData'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -76,13 +79,15 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
let res = await this.bookTaskService.GetBookTaskDataById(id)
|
let res = await this.bookTaskService.GetBookTaskDataById(id)
|
||||||
return successMessage(
|
return successMessage(
|
||||||
res,
|
res,
|
||||||
'获取小说子任务数据成功!',
|
t("获取小说批次任务数据成功!"),
|
||||||
'BookTaskServiceHandle_GetBookTaskDataById'
|
'BookTaskServiceHandle_GetBookTaskDataById'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`获取小说子任务数据失败,失败原因如下:${error.message}`,
|
t("获取小说批次任务数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskServiceHandle_GetBookTaskDataById'
|
'BookTaskServiceHandle_GetBookTaskDataById'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -131,12 +136,14 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
let res = await this.bookTaskService.ModifyBookTaskDataById(bookTaskId, data)
|
let res = await this.bookTaskService.ModifyBookTaskDataById(bookTaskId, data)
|
||||||
return successMessage(
|
return successMessage(
|
||||||
res,
|
res,
|
||||||
'修改小说子任务数据成功!',
|
t("修改小说批次任务数据成功!"),
|
||||||
'BookTaskServiceHandle_ModifyBookTaskData'
|
'BookTaskServiceHandle_ModifyBookTaskData'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`修改小说子任务数据失败,失败原因如下:${error.message}`,
|
t("修改小说批次任务数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskServiceHandle_ModifyBookTaskData'
|
'BookTaskServiceHandle_ModifyBookTaskData'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -159,12 +166,12 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
for (let i = 0; i < bookTaskIds.length; i++) {
|
for (let i = 0; i < bookTaskIds.length; i++) {
|
||||||
const element = bookTaskIds[i]
|
const element = bookTaskIds[i]
|
||||||
let tempBookTask = await this.bookTaskService.GetBookTaskDataById(element)
|
let tempBookTask = await this.bookTaskService.GetBookTaskDataById(element, true)
|
||||||
bookTasks.push(tempBookTask)
|
bookTasks.push(tempBookTask)
|
||||||
}
|
}
|
||||||
if (bookTasks.length === 0) {
|
if (bookTasks.length === 0) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'没有找到要删除的小说子任务数据',
|
t("没有找到要删除的小说批次任务数据"),
|
||||||
'BookTaskServiceHandle_DeleteBookTask'
|
'BookTaskServiceHandle_DeleteBookTask'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -190,12 +197,14 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
bookId: bookTasks[0].bookId,
|
bookId: bookTasks[0].bookId,
|
||||||
bookTasks: newBookTasks
|
bookTasks: newBookTasks
|
||||||
},
|
},
|
||||||
'删除小说子任务数据成功!',
|
t("删除小说批次任务数据成功!"),
|
||||||
'BookTaskServiceHandle_DeleteBookTask'
|
'BookTaskServiceHandle_DeleteBookTask'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`删除小说子任务数据失败,失败原因如下:${error.message}`,
|
t("删除小说批次任务数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskServiceHandle_DeleteBookTask'
|
'BookTaskServiceHandle_DeleteBookTask'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -214,10 +223,8 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
no: number,
|
no: number,
|
||||||
projectPath: string
|
projectPath: string
|
||||||
): Promise<Book.SelectBookTask> {
|
): Promise<Book.SelectBookTask> {
|
||||||
let book = await this.bookService.GetBookDataById(bookTask.bookId as string)
|
let book = await this.bookService.GetBookDataById(bookTask.bookId as string, true)
|
||||||
if (book == null) {
|
|
||||||
throw new Error('没有找到小说,请检查')
|
|
||||||
}
|
|
||||||
let name = book.name + '_' + no.toString().padStart(5, '0')
|
let name = book.name + '_' + no.toString().padStart(5, '0')
|
||||||
|
|
||||||
let imageFolder = path.join(projectPath, `${bookTask.bookId}/tmp/${name}`)
|
let imageFolder = path.join(projectPath, `${bookTask.bookId}/tmp/${name}`)
|
||||||
@ -288,10 +295,8 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
projectPath: string
|
projectPath: string
|
||||||
): Promise<Book.SelectBookTaskDetail[]> {
|
): Promise<Book.SelectBookTaskDetail[]> {
|
||||||
let bookTaskDetail = [] as Book.SelectBookTaskDetail[]
|
let bookTaskDetail = [] as Book.SelectBookTaskDetail[]
|
||||||
let book = await this.bookService.GetBookDataById(bookTask.bookId as string)
|
let book = await this.bookService.GetBookDataById(bookTask.bookId as string, true)
|
||||||
if (book == null) {
|
|
||||||
throw new Error('没有找到小说,请检查')
|
|
||||||
}
|
|
||||||
let originalTimePath = path.join(book.bookFolderPath as string, `data/${book.id}.mp4.json`)
|
let originalTimePath = path.join(book.bookFolderPath as string, `data/${book.id}.mp4.json`)
|
||||||
let originalTime = [] as any[]
|
let originalTime = [] as any[]
|
||||||
if (await CheckFileOrDirExist(originalTimePath)) {
|
if (await CheckFileOrDirExist(originalTimePath)) {
|
||||||
@ -423,13 +428,13 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
try {
|
try {
|
||||||
if (!global.am.isPro && addData.count > 1) {
|
if (!global.am.isPro && addData.count > 1) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'批量添加小说子任务数据失败,免费版只能添加一条数据',
|
t("批量添加小说批次任务数据失败,免费版只能添加一条数据"),
|
||||||
'BookTaskServiceHandle_AddNewBookTask'
|
'BookTaskServiceHandle_AddNewBookTask'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (isEmpty(addData.selectBookId)) {
|
if (isEmpty(addData.selectBookId)) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'批量添加小说子任务数据失败,小说ID不能为空',
|
t('批量添加小说批次任务数据失败,小说ID不能为空'),
|
||||||
'BookTaskServiceHandle_AddNewBookTask'
|
'BookTaskServiceHandle_AddNewBookTask'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -442,7 +447,7 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
for (let i = 0; i < addData.count; i++) {
|
for (let i = 0; i < addData.count; i++) {
|
||||||
if (addData.copyBookTask && !isEmpty(addData.selectBookTask)) {
|
if (addData.copyBookTask && !isEmpty(addData.selectBookTask)) {
|
||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
||||||
addData.selectBookTask as string
|
addData.selectBookTask as string, true
|
||||||
)
|
)
|
||||||
let oldBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition(
|
let oldBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition(
|
||||||
{
|
{
|
||||||
@ -497,12 +502,14 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
bookId: addData.selectBookId,
|
bookId: addData.selectBookId,
|
||||||
bookTasks: bookTasks
|
bookTasks: bookTasks
|
||||||
},
|
},
|
||||||
'添加小说子任务数据成功!',
|
t("添加小说批次任务数据成功!"),
|
||||||
'BookTaskServiceHandle_AddNewBookTask'
|
'BookTaskServiceHandle_AddNewBookTask'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`添加小说子任务数据失败,失败原因如下:${error.message}`,
|
t("添加小说批次任务数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskServiceHandle_AddNewBookTask'
|
'BookTaskServiceHandle_AddNewBookTask'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -520,13 +527,7 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
*/
|
*/
|
||||||
async GetBookTaskImageGenerateProgress(bookId: string): Promise<SuccessItem | ErrorItem> {
|
async GetBookTaskImageGenerateProgress(bookId: string): Promise<SuccessItem | ErrorItem> {
|
||||||
try {
|
try {
|
||||||
let book = await this.bookService.GetBookDataById(bookId)
|
await this.InitBookBasicHandle()
|
||||||
if (book == null) {
|
|
||||||
return errorMessage(
|
|
||||||
'获取小说任务生成进度失败,小说不存在',
|
|
||||||
'BookTaskServiceHandle_GetBookTaskImageGenerateProgress'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取小说批次数据
|
// 获取小说批次数据
|
||||||
let bookTasks = await this.bookTaskService.GetBookTaskDataByCondition({
|
let bookTasks = await this.bookTaskService.GetBookTaskDataByCondition({
|
||||||
@ -535,7 +536,7 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
if (bookTasks.bookTasks.length === 0) {
|
if (bookTasks.bookTasks.length === 0) {
|
||||||
return successMessage(
|
return successMessage(
|
||||||
{},
|
{},
|
||||||
'获取小说任务生成进度成功!',
|
t("获取小说批次任务生成进度成功!"),
|
||||||
'BookTaskServiceHandle_GetBookTaskImageGenerateProgress'
|
'BookTaskServiceHandle_GetBookTaskImageGenerateProgress'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -595,12 +596,14 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
}
|
}
|
||||||
return successMessage(
|
return successMessage(
|
||||||
resData,
|
resData,
|
||||||
'获取小说任务生成进度成功!',
|
t("获取小说批次任务生成进度成功!"),
|
||||||
'BookTaskServiceHandle_GetBookTaskImageGenerateProgress'
|
'BookTaskServiceHandle_GetBookTaskImageGenerateProgress'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`获取小说任务生成进度失败,失败原因如下:${error.message}`,
|
t("获取小说批次任务生成进度失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskServiceHandle_GetBookTaskImageGenerateProgress'
|
'BookTaskServiceHandle_GetBookTaskImageGenerateProgress'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -632,10 +635,8 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (bookTasks.bookTasks.length === 0) {
|
if (bookTasks.bookTasks.length === 0) {
|
||||||
return errorMessage(
|
return successMessage({}, t('获取小说批次任务的第一张图片路径成功!'), 'BookTaskServiceHandle_GetBookTaskFirstImagePath')
|
||||||
'获取小说批次任务的第一张图片路径失败,小说批次任务不存在',
|
|
||||||
'BookTaskServiceHandle_GetBookTaskFirstImagePath'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
let resData: Record<string, string | undefined> = {}
|
let resData: Record<string, string | undefined> = {}
|
||||||
// 开始处理所有的批次
|
// 开始处理所有的批次
|
||||||
@ -666,12 +667,14 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
}
|
}
|
||||||
return successMessage(
|
return successMessage(
|
||||||
resData,
|
resData,
|
||||||
'获取小说批次任务的第一张图片路径成功!',
|
t('获取小说批次任务的第一张图片路径成功!'),
|
||||||
'BookTaskServiceHandle_GetBookTaskFirstImagePath'
|
'BookTaskServiceHandle_GetBookTaskFirstImagePath'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`获取小说批次任务的第一张图片路径失败,失败原因如下:${error.message}`,
|
t("获取小说批次任务的第一张图片路径失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskServiceHandle_GetBookTaskFirstImagePath'
|
'BookTaskServiceHandle_GetBookTaskFirstImagePath'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -711,10 +714,8 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
|
|
||||||
// 初始化复制数量为100(后续会根据实际子图数量调整)
|
// 初始化复制数量为100(后续会根据实际子图数量调整)
|
||||||
let copyCount = 100
|
let copyCount = 100
|
||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId)
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId, true)
|
||||||
if (bookTask == null) {
|
|
||||||
throw new Error('没有找到对应的数小说任务,请检查数据')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取该批次下所有的分镜详情,用于检查子图情况
|
// 获取该批次下所有的分镜详情,用于检查子图情况
|
||||||
let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
||||||
@ -722,7 +723,7 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (bookTaskDetails == null || bookTaskDetails.length <= 0) {
|
if (bookTaskDetails == null || bookTaskDetails.length <= 0) {
|
||||||
throw new Error('没有对应的小说分镜任务,请先添加分镜任务')
|
throw new Error(t('没有对应的小说分镜任务,请先添加分镜任务'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 遍历所有分镜,找出子图数量最少的分镜,以此作为复制批次的数量基准
|
// 遍历所有分镜,找出子图数量最少的分镜,以此作为复制批次的数量基准
|
||||||
@ -730,10 +731,10 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
const element = bookTaskDetails[i]
|
const element = bookTaskDetails[i]
|
||||||
// 检查分镜是否有子图路径
|
// 检查分镜是否有子图路径
|
||||||
if (!element.subImagePath) {
|
if (!element.subImagePath) {
|
||||||
throw new Error('检测到图片没有出完,请先检查出图')
|
throw new Error(t('检测到图片没有出完,请先检查出图'))
|
||||||
}
|
}
|
||||||
if (element.subImagePath == null || element.subImagePath.length <= 0) {
|
if (element.subImagePath == null || element.subImagePath.length <= 0) {
|
||||||
throw new Error('检测到图片没有出完,请先检查出图')
|
throw new Error(t('检测到图片没有出完,请先检查出图'))
|
||||||
}
|
}
|
||||||
// 更新最小子图数量(取所有分镜中子图数量的最小值)
|
// 更新最小子图数量(取所有分镜中子图数量的最小值)
|
||||||
if (element.subImagePath.length < copyCount) {
|
if (element.subImagePath.length < copyCount) {
|
||||||
@ -742,7 +743,7 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
}
|
}
|
||||||
// 检查是否有足够的子图进行一拆四操作(至少需要2张子图才能拆分)
|
// 检查是否有足够的子图进行一拆四操作(至少需要2张子图才能拆分)
|
||||||
if (copyCount - 1 <= 0) {
|
if (copyCount - 1 <= 0) {
|
||||||
throw new Error('有分镜子图数量不足,无法进行一拆四')
|
throw new Error(t("有分镜子图数量不足,无法进行一拆四"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始执行复制操作:创建 (copyCount-1) 个新批次
|
// 开始执行复制操作:创建 (copyCount-1) 个新批次
|
||||||
@ -755,11 +756,13 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 返回成功结果
|
// 返回成功结果
|
||||||
return successMessage(null, '一拆四成功', 'BookTaskServiceHandle_OneToFourBookTask')
|
return successMessage(null, t("一拆四成功!"), 'BookTaskServiceHandle_OneToFourBookTask')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 捕获并返回错误信息
|
// 捕获并返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`小说批次任务 一拆四 失败,失败原因如下:${error.message}`,
|
t("一拆四失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskServiceHandle_OneToFourBookTask'
|
'BookTaskServiceHandle_OneToFourBookTask'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -778,10 +781,12 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
try {
|
try {
|
||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
let res = await this.bookTaskService.AddBookTask(bookTask)
|
let res = await this.bookTaskService.AddBookTask(bookTask)
|
||||||
return successMessage(res, '添加小说子任务数据成功!', 'BookTaskServiceHandle_AddBookTask')
|
return successMessage(res, t("添加小说批次任务数据成功!"), 'BookTaskServiceHandle_AddBookTask')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`添加小说子任务数据失败,失败原因如下:${error.message}`,
|
t("添加小说批次任务数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskServiceHandle_AddBookTask'
|
'BookTaskServiceHandle_AddBookTask'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -803,17 +808,24 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
let res = await this.bookTaskService.ResetBookTaskDataById(bookTaskId)
|
let res = await this.bookTaskService.ResetBookTaskDataById(bookTaskId)
|
||||||
return successMessage(
|
return successMessage(
|
||||||
res,
|
res,
|
||||||
'重置小说子任务数据成功!',
|
t("重置小说批次任务数据成功!"),
|
||||||
'BookTaskServiceHandle_ResetBookTaskData'
|
'BookTaskServiceHandle_ResetBookTaskData'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`重置小说子任务数据失败,失败原因如下:${error.message}`,
|
t("重置小说批次任务数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskServiceHandle_ResetBookTaskData'
|
'BookTaskServiceHandle_ResetBookTaskData'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化小说分镜数据
|
||||||
|
* @param bookTaskId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
async ResetAndInitializeBookTask(bookTaskId: string) {
|
async ResetAndInitializeBookTask(bookTaskId: string) {
|
||||||
try {
|
try {
|
||||||
// 先调用重置
|
// 先调用重置
|
||||||
@ -857,18 +869,20 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
|
|
||||||
if (newBookTaskDetails == null || newBookTaskDetails.length <= 0) {
|
if (newBookTaskDetails == null || newBookTaskDetails.length <= 0) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'重置小说分镜数据(不包含bootask本身数据),并且初始化全新的分镜信息失败,小说分镜数据不存在',
|
t("重置小说分镜数据(不包含批次任务本身数据),并且初始化全新的分镜信息失败,小说分镜数据不存在"),
|
||||||
'BookTaskServiceHandle_ResetAndInitializeBookTask'
|
'BookTaskServiceHandle_ResetAndInitializeBookTask'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return successMessage(
|
return successMessage(
|
||||||
newBookTaskDetails,
|
newBookTaskDetails,
|
||||||
'重置小说分镜数据成功!',
|
t("重置小说分镜数据成功!"),
|
||||||
'BookTaskServiceHandle_ResetAndInitializeBookTask'
|
'BookTaskServiceHandle_ResetAndInitializeBookTask'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`重置小说分镜数据(不包含bootask本身数据),并且初始化全新的分镜信息失败,失败原因如下:${error.message}`,
|
t("重置小说分镜数据(不包含批次任务本身数据),并且初始化全新的分镜信息失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskServiceHandle_ResetAndInitializeBookTask'
|
'BookTaskServiceHandle_ResetAndInitializeBookTask'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -887,13 +901,8 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
try {
|
try {
|
||||||
await this.InitBookBasicHandle()
|
await this.InitBookBasicHandle()
|
||||||
|
|
||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId)
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId, true)
|
||||||
if (bookTask == null) {
|
|
||||||
return errorMessage(
|
|
||||||
'重置小说子任务数据失败,小说子任务不存在',
|
|
||||||
'BookTaskServiceHandle_ResetBookTaskDataById'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
let imageFolder = bookTask.imageFolder
|
let imageFolder = bookTask.imageFolder
|
||||||
|
|
||||||
let res = this.bookTaskService.DeleteBookTaskDataById(bookTaskId)
|
let res = this.bookTaskService.DeleteBookTaskDataById(bookTaskId)
|
||||||
@ -902,10 +911,12 @@ export class BookTaskServiceHandle extends BookBasicHandle {
|
|||||||
if (!isEmpty(imageFolder) && (await CheckFileOrDirExist(imageFolder))) {
|
if (!isEmpty(imageFolder) && (await CheckFileOrDirExist(imageFolder))) {
|
||||||
await DeleteFolderAllFile(imageFolder as string, true)
|
await DeleteFolderAllFile(imageFolder as string, true)
|
||||||
}
|
}
|
||||||
return successMessage(res, '删除小说子任务数据成功!', 'BookTaskServiceHandle_DeleteBookTask')
|
return successMessage(res, t("删除小说批次任务数据成功!"), 'BookTaskServiceHandle_DeleteBookTask')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`删除小说子任务数据失败,失败原因如下:${error.message}`,
|
t("删除小说批次任务数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'BookTaskServiceHandle_DeleteBookTask'
|
'BookTaskServiceHandle_DeleteBookTask'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
356
src/main/service/book/subBookHandle/bookVideoServiceHandle.ts
Normal file
356
src/main/service/book/subBookHandle/bookVideoServiceHandle.ts
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
import { errorMessage, SendReturnMessage, successMessage } from '@/public/generalTools'
|
||||||
|
import { BookBasicHandle } from './bookBasicHandle'
|
||||||
|
import { Book } from '@/define/model/book/book'
|
||||||
|
import {
|
||||||
|
ImageToVideoModels,
|
||||||
|
KlingMode,
|
||||||
|
MJVideoBatchSize,
|
||||||
|
MJVideoMotion,
|
||||||
|
MJVideoType,
|
||||||
|
RunawayModel,
|
||||||
|
RunwaySeconds,
|
||||||
|
VideoModel,
|
||||||
|
VideoStatus
|
||||||
|
} from '@/define/enum/video'
|
||||||
|
import { SettingModal } from '@/define/model/setting'
|
||||||
|
import { OptionKeyName } from '@/define/enum/option'
|
||||||
|
import { GetApiDefineDataById } from '@/define/data/apiData'
|
||||||
|
import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
||||||
|
import { GetBaseUrl } from '@/define/Tools/common'
|
||||||
|
import { isEmpty } from 'lodash'
|
||||||
|
import { t } from '@/i18n/main'
|
||||||
|
import { getProjectPath } from '../../option/optionCommonService'
|
||||||
|
import { TaskModal } from '@/define/model/task'
|
||||||
|
import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus } from '@/define/enum/bookEnum'
|
||||||
|
import { VideoHandle } from '@/main/service/video/index'
|
||||||
|
|
||||||
|
export class BookVideoServiceHandle extends BookBasicHandle {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region GetVideoBookInfoList
|
||||||
|
/**
|
||||||
|
* 获取用于视频生成的小说信息列表
|
||||||
|
* 根据查询条件返回小说数据,如果指定了bookTaskId,则返回对应小说的单个任务数据
|
||||||
|
* 否则返回所有启用了视频生成功能的小说及其任务数据
|
||||||
|
* @param condition 查询条件,包含可选的bookTaskId等参数
|
||||||
|
* @returns 处理结果,成功时返回小说数据,失败时返回错误信息
|
||||||
|
*/
|
||||||
|
GetVideoBookInfoList = async (condition: BookVideo.BookVideoInfoListQuertCondition) => {
|
||||||
|
try {
|
||||||
|
await this.InitBookBasicHandle()
|
||||||
|
|
||||||
|
// 获取小说的所有的数据
|
||||||
|
let bookRes = await this.bookService.GetBookDataCondition({
|
||||||
|
id: condition.bookId
|
||||||
|
})
|
||||||
|
|
||||||
|
if (bookRes.resultBooks == null || bookRes.resultBooks.length <= 0) {
|
||||||
|
return errorMessage(
|
||||||
|
t("获取小说数据错误,未找到指定条件的小说数据"),
|
||||||
|
'BookVideoServiceHandle_GetVideoBookInfoList'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let bookList = bookRes.resultBooks ?? []
|
||||||
|
|
||||||
|
// 有指定的小说批次任务的ID情况下 只返回当前的小说数据
|
||||||
|
if (condition.bookTaskId) {
|
||||||
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(condition.bookTaskId, true)
|
||||||
|
|
||||||
|
// 再上面的小说数据里面获取数据 然后直接返回就行
|
||||||
|
let bookInfo = bookList.find((book) => book.id === bookTask.bookId)
|
||||||
|
if (!bookInfo) {
|
||||||
|
return errorMessage(
|
||||||
|
t("没有找到对应的小说数据,请先添加小说"),
|
||||||
|
'BookImageTextToVideoInfo_GetVideoBookInfoList'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
bookInfo.children = [bookTask]
|
||||||
|
// 返回
|
||||||
|
return successMessage(
|
||||||
|
bookInfo,
|
||||||
|
t('获取小说批次任务数据成功'),
|
||||||
|
'BookImageTextToVideoInfo_GetVideoBookInfoList'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有那个数据 将所有的数据进行处理
|
||||||
|
let res: Book.SelectBook[] = []
|
||||||
|
for (let i = 0; i < bookList.length; i++) {
|
||||||
|
const element = bookList[i]
|
||||||
|
// 获取小说批次任务数据
|
||||||
|
let bookTaskRes = await this.bookTaskService.GetBookTaskDataByCondition({
|
||||||
|
bookId: element.id
|
||||||
|
})
|
||||||
|
|
||||||
|
if (bookTaskRes.bookTasks == null || bookTaskRes.bookTasks.length <= 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查所有的 bookTasks 里面是不是开启了图转视频功能
|
||||||
|
let videoBookTasks = bookTaskRes.bookTasks.filter((task) => task.openVideoGenerate)
|
||||||
|
if (videoBookTasks.length <= 0) {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
element.children = videoBookTasks
|
||||||
|
res.push(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return successMessage(
|
||||||
|
res,
|
||||||
|
t('获取小说批次任务数据成功'),
|
||||||
|
'BookImageTextToVideoInfo_GetVideoBookInfoList'
|
||||||
|
)
|
||||||
|
} catch (error: any) {
|
||||||
|
return errorMessage(
|
||||||
|
t("获取小说批次任务数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
|
'BookImageTextToVideoInfo_GetVideoBookInfoList'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region InitVideoMessage
|
||||||
|
/**
|
||||||
|
* 初始化视频消息数据
|
||||||
|
* @param params 初始化参数
|
||||||
|
* @returns 处理结果
|
||||||
|
*/
|
||||||
|
InitVideoMessage = async (bookTaskId: string) => {
|
||||||
|
try {
|
||||||
|
await this.InitBookBasicHandle()
|
||||||
|
|
||||||
|
let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
||||||
|
bookTaskId: bookTaskId
|
||||||
|
})
|
||||||
|
if (bookTaskDetails == null || bookTaskDetails.length <= 0) {
|
||||||
|
let d = t("初始视频消息失败,未找到指定小说批次任务的分镜数据")
|
||||||
|
return errorMessage(d, 'BookVideoServiceHandle_InitVideoMessage')
|
||||||
|
}
|
||||||
|
|
||||||
|
let project_path = await getProjectPath()
|
||||||
|
|
||||||
|
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||||
|
const element = bookTaskDetails[i]
|
||||||
|
if (element.videoMessage) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let { videoMessage } = await this.InitVideoMessageData(element, project_path)
|
||||||
|
|
||||||
|
// 修改数据报错
|
||||||
|
await this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage(
|
||||||
|
element.id as string,
|
||||||
|
videoMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return successMessage(
|
||||||
|
null,
|
||||||
|
t("初始视频消息成功!"),
|
||||||
|
'BookVideoServiceHandle_InitVideoMessage'
|
||||||
|
)
|
||||||
|
} catch (error: any) {
|
||||||
|
return errorMessage(
|
||||||
|
t("初始化分镜视频消息失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
|
'BookVideoServiceHandle_InitVideoMessage'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region InitVideoMessageData
|
||||||
|
|
||||||
|
async InitVideoMessageData(bookTaskDetail: Book.SelectBookTaskDetail, project_path: string) {
|
||||||
|
try {
|
||||||
|
console.log(project_path)
|
||||||
|
let s = t("设置 -> 通用设置")
|
||||||
|
console.log('翻译输出', s)
|
||||||
|
// 获取基础要的数据
|
||||||
|
let generalSettingString =
|
||||||
|
this.optionRealmService.GetOptionDataByKey<SettingModal.GeneralSettings>(
|
||||||
|
OptionKeyName.Software.GeneralSetting,
|
||||||
|
t("设置 -> 通用设置")
|
||||||
|
)
|
||||||
|
let defaultVideoMode = generalSettingString.defaultImg2Video ?? ImageToVideoModels.MJ_VIDEO
|
||||||
|
|
||||||
|
let inferenceSetting =
|
||||||
|
this.optionRealmService.GetOptionDataByKey<SettingModal.InferenceAISettings>(
|
||||||
|
OptionKeyName.InferenceAI.InferenceSetting,
|
||||||
|
t("设置 -> 推理设置")
|
||||||
|
)
|
||||||
|
let gptUrl = GetApiDefineDataById(inferenceSetting.apiProvider)?.gpt_url
|
||||||
|
if (gptUrl == null || isEmpty(gptUrl)) {
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
// 开始设置默认设置
|
||||||
|
|
||||||
|
let outImage =
|
||||||
|
bookTaskDetail.outImagePath != null && !isEmpty(bookTaskDetail.outImagePath)
|
||||||
|
? bookTaskDetail.outImagePath
|
||||||
|
: ''
|
||||||
|
|
||||||
|
// 设置为runway
|
||||||
|
let optionObject: BookTaskDetail.RunwayOption = {
|
||||||
|
callback_url: GetBaseUrl(gptUrl),
|
||||||
|
image: outImage,
|
||||||
|
model: RunawayModel.GNE3,
|
||||||
|
prompt: '',
|
||||||
|
options: {
|
||||||
|
seconds: RunwaySeconds.FIVE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let lumaOptions: BookTaskDetail.lumaOptions = {
|
||||||
|
user_prompt: '',
|
||||||
|
aspect_ratio: '4:3',
|
||||||
|
expand_prompt: true,
|
||||||
|
loop: false,
|
||||||
|
image_url: outImage
|
||||||
|
}
|
||||||
|
|
||||||
|
let klingOptions: BookTaskDetail.klingOptions = {
|
||||||
|
model: 'kling-v1',
|
||||||
|
image: outImage,
|
||||||
|
image_tail: '',
|
||||||
|
prompt: '',
|
||||||
|
negative_prompt: '',
|
||||||
|
mode: KlingMode.STD,
|
||||||
|
duration: RunwaySeconds.FIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
let mjVideoOptions: BookTaskDetail.MjVideoOptions = {
|
||||||
|
action: undefined,
|
||||||
|
image: outImage, // 或者根据 Image 类型的定义提供默认值
|
||||||
|
index: undefined,
|
||||||
|
motion: MJVideoMotion.High, // 根据 Motion 类型的定义提供默认值
|
||||||
|
noStorage: false,
|
||||||
|
notifyHook: undefined,
|
||||||
|
prompt: null,
|
||||||
|
state: undefined,
|
||||||
|
taskId: undefined,
|
||||||
|
raw: false,
|
||||||
|
batchSize: MJVideoBatchSize.ONE,
|
||||||
|
videoType: MJVideoType.HD
|
||||||
|
}
|
||||||
|
|
||||||
|
let videoMessage: BookTaskDetail.VideoMessage = {
|
||||||
|
id: bookTaskDetail.id,
|
||||||
|
msg: '',
|
||||||
|
prompt: '',
|
||||||
|
videoType: defaultVideoMode,
|
||||||
|
style: '',
|
||||||
|
imageUrl: outImage,
|
||||||
|
bookTaskDetailId: bookTaskDetail.id,
|
||||||
|
runwayOptions: JSON.stringify(optionObject),
|
||||||
|
lumaOptions: JSON.stringify(lumaOptions),
|
||||||
|
klingOptions: JSON.stringify(klingOptions),
|
||||||
|
mjVideoOptions: JSON.stringify(mjVideoOptions),
|
||||||
|
status: VideoStatus.WAIT,
|
||||||
|
model: VideoModel.IMAGE_TO_VIDEO
|
||||||
|
}
|
||||||
|
|
||||||
|
return { optionObject, lumaOptions, klingOptions, mjVideoOptions, videoMessage }
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region UpdateBookTaskDetailVideoMessage
|
||||||
|
/**
|
||||||
|
* 更新小说分镜的视频消息
|
||||||
|
* @param bookTaskDetailId 小说分镜的ID
|
||||||
|
* @param videoMessage 要更新的视频消息数据
|
||||||
|
* @returns 处理结果
|
||||||
|
*/
|
||||||
|
async UpdateBookTaskDetailVideoMessage(
|
||||||
|
bookTaskDetailId: string,
|
||||||
|
videoMessage: Partial<BookTaskDetail.VideoMessage>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
await this.InitBookBasicHandle()
|
||||||
|
|
||||||
|
await this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage(
|
||||||
|
bookTaskDetailId,
|
||||||
|
videoMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
return successMessage(
|
||||||
|
t("修改小说分镜的VideoMessage成功!"),
|
||||||
|
'BookVideoServiceHandle_UpdateBookTaskDetailVideoMessage'
|
||||||
|
)
|
||||||
|
} catch (error: any) {
|
||||||
|
return errorMessage(
|
||||||
|
t("修改小说分镜的VideoMessage失败,{error}", { error: error.message }),
|
||||||
|
'BookVideoServiceHandle_UpdateBookTaskDetailVideoMessage'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region MediaToVideo
|
||||||
|
|
||||||
|
async MediaToVideo(task: TaskModal.Task) {
|
||||||
|
try {
|
||||||
|
// 更具不同的方式调用不同的处理方法
|
||||||
|
const videoHandle = new VideoHandle()
|
||||||
|
switch (task.type) {
|
||||||
|
case BookBackTaskType.MJ_VIDEO:
|
||||||
|
return await videoHandle.MJImageToVideo(task)
|
||||||
|
default:
|
||||||
|
throw new Error('未知的视频生成方式,请检查')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 统一处理 报错信息
|
||||||
|
let message = t("图转视频失败,失败信息如下:{error}", { error: (error as Error).message })
|
||||||
|
|
||||||
|
// 修改批次状态
|
||||||
|
await this.bookTaskService.ModifyBookTaskDataById(task.bookTaskId as string, {
|
||||||
|
status: BookTaskStatus.IMAGE_TO_VIDEO_ERROR,
|
||||||
|
errorMsg: message
|
||||||
|
})
|
||||||
|
|
||||||
|
// 修改任务状态
|
||||||
|
this.taskListService.UpdateTaskStatus({
|
||||||
|
id: task.id as string,
|
||||||
|
status: BookBackTaskStatus.FAIL,
|
||||||
|
errorMessage: message
|
||||||
|
})
|
||||||
|
|
||||||
|
// 修改对应的小说分镜的 videoMessage 的状态
|
||||||
|
await this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage(
|
||||||
|
task.bookTaskDetailId as string,
|
||||||
|
{
|
||||||
|
status: VideoStatus.FAIL,
|
||||||
|
taskId: '',
|
||||||
|
msg: message
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
SendReturnMessage(
|
||||||
|
{
|
||||||
|
code: 0,
|
||||||
|
id: task.bookTaskDetailId as string,
|
||||||
|
data: BookTaskStatus.IMAGE_TO_VIDEO_ERROR,
|
||||||
|
message: message
|
||||||
|
},
|
||||||
|
task.messageName as string
|
||||||
|
)
|
||||||
|
return errorMessage(message, 'BookVideoServiceHandle_ImageToVideo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { CheckFileOrDirExist } from '@/define/Tools/file'
|
import { CheckFileOrDirExist } from '@/define/Tools/file'
|
||||||
|
import { t } from '@/i18n'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ interface SrtDataModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SrtHandle {
|
export class SrtHandle {
|
||||||
constructor() {}
|
constructor() { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取srt文件数据
|
* 获取srt文件数据
|
||||||
@ -23,13 +24,15 @@ export class SrtHandle {
|
|||||||
async GetSrtDataByPath(srtPath: string): Promise<SrtDataModel[]> {
|
async GetSrtDataByPath(srtPath: string): Promise<SrtDataModel[]> {
|
||||||
try {
|
try {
|
||||||
if (isEmpty(srtPath)) {
|
if (isEmpty(srtPath)) {
|
||||||
throw new Error('srt文件路径不能为空!')
|
throw new Error(t("srt文件路径不能为空!"))
|
||||||
}
|
}
|
||||||
if (!srtPath.toLowerCase().endsWith('.srt')) {
|
if (!srtPath.toLowerCase().endsWith('.srt')) {
|
||||||
throw new Error('srt文件后缀不正确,请检查!')
|
throw new Error(t("srt文件后缀不正确,请检查!"))
|
||||||
}
|
}
|
||||||
if (!(await CheckFileOrDirExist(srtPath))) {
|
if (!(await CheckFileOrDirExist(srtPath))) {
|
||||||
throw new Error(`srt文件不存在,路径为:${srtPath}`)
|
throw new Error(t("目的文件/文件夹不存在,{data}", {
|
||||||
|
data: srtPath
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
let srt_data = (await fs.promises.readFile(srtPath)).toString('utf-8')
|
let srt_data = (await fs.promises.readFile(srtPath)).toString('utf-8')
|
||||||
const entries = srt_data.replace(/\r\n/g, '\n').split('\n\n')
|
const entries = srt_data.replace(/\r\n/g, '\n').split('\n\n')
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { OptionRealmService } from '@/define/db/service/optionService'
|
|||||||
import { OptionKeyName } from '@/define/enum/option'
|
import { OptionKeyName } from '@/define/enum/option'
|
||||||
import { optionSerialization } from '../option/optionSerialization'
|
import { optionSerialization } from '../option/optionSerialization'
|
||||||
import { SettingModal } from '@/define/model/setting'
|
import { SettingModal } from '@/define/model/setting'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 剪映的一些服务
|
* 剪映的一些服务
|
||||||
@ -51,7 +52,7 @@ class JianyingService {
|
|||||||
// 反序列化设置数据为具体的设置对象
|
// 反序列化设置数据为具体的设置对象
|
||||||
let generalSetting = optionSerialization<SettingModal.GeneralSettings>(
|
let generalSetting = optionSerialization<SettingModal.GeneralSettings>(
|
||||||
res,
|
res,
|
||||||
'设置 -> 通用设置'
|
t('设置 -> 通用设置')
|
||||||
)
|
)
|
||||||
|
|
||||||
// 从设置中获取剪映草稿文件夹路径
|
// 从设置中获取剪映草稿文件夹路径
|
||||||
@ -66,13 +67,15 @@ class JianyingService {
|
|||||||
// 返回成功结果
|
// 返回成功结果
|
||||||
return successMessage(
|
return successMessage(
|
||||||
result,
|
result,
|
||||||
'获取剪映草稿文件列表成功',
|
t("获取剪映草稿文件列表成功!"),
|
||||||
'JianyingService_GetJianyingDraftFileList'
|
'JianyingService_GetJianyingDraftFileList'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 捕获异常并返回错误信息
|
// 捕获异常并返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'获取剪映草稿文件列表失败,失败信息:' + error.message,
|
t("获取剪映草稿文件列表失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'JianyingService_GetJianyingDraftFileList'
|
'JianyingService_GetJianyingDraftFileList'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -179,7 +182,7 @@ class JianyingService {
|
|||||||
|
|
||||||
let dependDraftPath = path.resolve(generalSetting.draftPath, dependDraftName)
|
let dependDraftPath = path.resolve(generalSetting.draftPath, dependDraftName)
|
||||||
if (!(await CheckFileOrDirExist(dependDraftPath))) {
|
if (!(await CheckFileOrDirExist(dependDraftPath))) {
|
||||||
throw new Error('依赖的草稿文件不存在,请检查')
|
throw new Error(t("依赖的草稿文件不存在,请检查"))
|
||||||
}
|
}
|
||||||
let draftPath = path.resolve(generalSetting.draftPath, draftName)
|
let draftPath = path.resolve(generalSetting.draftPath, draftName)
|
||||||
if (await CheckFileOrDirExist(draftPath)) {
|
if (await CheckFileOrDirExist(draftPath)) {
|
||||||
@ -194,26 +197,26 @@ class JianyingService {
|
|||||||
// 开始处理数据
|
// 开始处理数据
|
||||||
let draftJsonPath = path.resolve(draftPath, 'draft_content.json')
|
let draftJsonPath = path.resolve(draftPath, 'draft_content.json')
|
||||||
if (!(await CheckFileOrDirExist(draftJsonPath))) {
|
if (!(await CheckFileOrDirExist(draftJsonPath))) {
|
||||||
throw new Error('剪映草稿文件数据文件不存在,请先检查')
|
throw new Error(t('剪映草稿文件数据文件不存在,请先检查'))
|
||||||
}
|
}
|
||||||
// 读取草稿文件内容
|
// 读取草稿文件内容
|
||||||
let draftJsonString = await fs.promises.readFile(draftJsonPath, 'utf-8')
|
let draftJsonString = await fs.promises.readFile(draftJsonPath, 'utf-8')
|
||||||
if (!ValidateJson(draftJsonString)) {
|
if (!ValidateJson(draftJsonString)) {
|
||||||
throw new Error('剪映草稿文件格式错误,请检查')
|
throw new Error(t('剪映草稿文件格式错误,请检查'))
|
||||||
}
|
}
|
||||||
let draftJson = JSON.parse(draftJsonString)
|
let draftJson = JSON.parse(draftJsonString)
|
||||||
let draftTracks = draftJson.tracks
|
let draftTracks = draftJson.tracks
|
||||||
if (draftTracks.length <= 0) {
|
if (draftTracks.length <= 0) {
|
||||||
throw new Error('剪映草稿文件格式错误,没有轨道,请检查')
|
throw new Error(t('剪映草稿文件格式错误,没有轨道,请检查'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let videoTrack = draftTracks[0] // 只处理主轨道
|
let videoTrack = draftTracks[0] // 只处理主轨道
|
||||||
if (videoTrack.type !== 'video') {
|
if (videoTrack.type !== 'video') {
|
||||||
throw new Error('剪映草稿文件格式错误,主轨道不是Video,请检查')
|
throw new Error(t('剪映草稿文件格式错误,主轨道不是Video,请检查'))
|
||||||
}
|
}
|
||||||
let videoTrackSegments = videoTrack.segments
|
let videoTrackSegments = videoTrack.segments
|
||||||
if (videoTrackSegments.length <= 0) {
|
if (videoTrackSegments.length <= 0) {
|
||||||
throw new Error('剪映草稿文件格式错误,主轨道没有对应的图片节点,请检查')
|
throw new Error(t("剪映草稿文件格式错误,主轨道没有对应的图片节点,请检查"))
|
||||||
}
|
}
|
||||||
let segmentMaterialNodes: any = []
|
let segmentMaterialNodes: any = []
|
||||||
for (let i = 0; i < videoTrackSegments.length; i++) {
|
for (let i = 0; i < videoTrackSegments.length; i++) {
|
||||||
@ -224,14 +227,14 @@ class JianyingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (images.length !== segmentMaterialNodes.length) {
|
if (images.length !== segmentMaterialNodes.length) {
|
||||||
throw new Error('当前新增的任务对应的图片数量和依赖的草稿的主轨道的图片数量不一致,请检查')
|
throw new Error(t('当前新增的任务对应的图片数量和依赖的草稿的主轨道的图片数量不一致,请检查'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接修改
|
// 直接修改
|
||||||
for (let i = 0; i < segmentMaterialNodes.length; i++) {
|
for (let i = 0; i < segmentMaterialNodes.length; i++) {
|
||||||
const element = segmentMaterialNodes[i]
|
const element = segmentMaterialNodes[i]
|
||||||
if (!images[i]) {
|
if (!images[i]) {
|
||||||
throw new Error('图片数量不对应,请检查')
|
throw new Error(t('图片数量不对应,请检查'))
|
||||||
}
|
}
|
||||||
element.path = images[i]
|
element.path = images[i]
|
||||||
}
|
}
|
||||||
@ -248,16 +251,16 @@ class JianyingService {
|
|||||||
public async ReplaceDraftMaterialImageToVideo(draftName: string, replaceOnject: ReplaceOnject[]) {
|
public async ReplaceDraftMaterialImageToVideo(draftName: string, replaceOnject: ReplaceOnject[]) {
|
||||||
let draftPath = path.resolve(global.config.draft_path, draftName)
|
let draftPath = path.resolve(global.config.draft_path, draftName)
|
||||||
if (!(await CheckFileOrDirExist(draftPath))) {
|
if (!(await CheckFileOrDirExist(draftPath))) {
|
||||||
throw new Error('草稿文件不存在,请检查')
|
throw new Error(t('草稿文件不存在,请检查'))
|
||||||
}
|
}
|
||||||
let draftJsonPath = path.resolve(draftPath, 'draft_content.json')
|
let draftJsonPath = path.resolve(draftPath, 'draft_content.json')
|
||||||
if (!(await CheckFileOrDirExist(draftJsonPath))) {
|
if (!(await CheckFileOrDirExist(draftJsonPath))) {
|
||||||
throw new Error('剪映草稿文件数据文件不存在,请先检查')
|
throw new Error(t('剪映草稿文件数据文件不存在,请先检查'))
|
||||||
}
|
}
|
||||||
// 读取草稿文件内容
|
// 读取草稿文件内容
|
||||||
let draftJsonString = await fs.promises.readFile(draftJsonPath, 'utf-8')
|
let draftJsonString = await fs.promises.readFile(draftJsonPath, 'utf-8')
|
||||||
if (!ValidateJson(draftJsonString)) {
|
if (!ValidateJson(draftJsonString)) {
|
||||||
throw new Error('剪映草稿文件格式错误,请检查')
|
throw new Error(t('剪映草稿文件格式错误,请检查'))
|
||||||
}
|
}
|
||||||
let draftJson = JSON.parse(draftJsonString)
|
let draftJson = JSON.parse(draftJsonString)
|
||||||
let materialNodes = draftJson.materials.videos
|
let materialNodes = draftJson.materials.videos
|
||||||
@ -291,7 +294,7 @@ class JianyingService {
|
|||||||
private FindNode(nodes: any[], type: string, value: any) {
|
private FindNode(nodes: any[], type: string, value: any) {
|
||||||
let res = nodes.filter((node: any) => node[type] === value)
|
let res = nodes.filter((node: any) => node[type] === value)
|
||||||
if (res.length === 0) {
|
if (res.length === 0) {
|
||||||
throw new Error('没有找到对应的节点')
|
throw new Error(t('没有找到对应的节点'))
|
||||||
}
|
}
|
||||||
return res[0]
|
return res[0]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { isEmpty } from 'lodash'
|
|||||||
import { BookBackTaskStatus } from '@/define/enum/bookEnum'
|
import { BookBackTaskStatus } from '@/define/enum/bookEnum'
|
||||||
import { MJ } from '@/define/model/mj'
|
import { MJ } from '@/define/model/mj'
|
||||||
import { define } from '@/define/define'
|
import { define } from '@/define/define'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MidJourney API 账户过滤器接口
|
* MidJourney API 账户过滤器接口
|
||||||
@ -129,6 +130,7 @@ export class MJApiService extends MJBasic {
|
|||||||
imagineUrl!: string
|
imagineUrl!: string
|
||||||
fetchTaskUrl!: string
|
fetchTaskUrl!: string
|
||||||
describeUrl!: string
|
describeUrl!: string
|
||||||
|
videoUrl!: string
|
||||||
token!: string
|
token!: string
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -140,13 +142,17 @@ export class MJApiService extends MJBasic {
|
|||||||
/**
|
/**
|
||||||
* 初始化MJ设置
|
* 初始化MJ设置
|
||||||
*/
|
*/
|
||||||
async InitMJSetting(): Promise<void> {
|
async InitMJSetting(outputMode?: ImageGenerateMode): Promise<void> {
|
||||||
await this.GetMJGeneralSetting()
|
await this.GetMJGeneralSetting()
|
||||||
|
|
||||||
// 获取当前机器人类型
|
// 获取当前机器人类型
|
||||||
this.bootType =
|
this.bootType =
|
||||||
this.mjGeneralSetting?.robot == MJRobotType.NIJI ? 'NIJI_JOURNEY' : 'MID_JOURNEY'
|
this.mjGeneralSetting?.robot == MJRobotType.NIJI ? 'NIJI_JOURNEY' : 'MID_JOURNEY'
|
||||||
|
|
||||||
|
if (outputMode) {
|
||||||
|
this.mjGeneralSetting!.outputMode = outputMode
|
||||||
|
}
|
||||||
|
|
||||||
// 再 MJ API 模式下 获取对应的数据
|
// 再 MJ API 模式下 获取对应的数据
|
||||||
if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.MJ_API) {
|
if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.MJ_API) {
|
||||||
await this.GetApiSetting()
|
await this.GetApiSetting()
|
||||||
@ -155,14 +161,19 @@ export class MJApiService extends MJBasic {
|
|||||||
isEmpty(this.mjApiSetting.apiUrl) ||
|
isEmpty(this.mjApiSetting.apiUrl) ||
|
||||||
isEmpty(this.mjApiSetting.apiKey)
|
isEmpty(this.mjApiSetting.apiKey)
|
||||||
) {
|
) {
|
||||||
throw new Error('没有找到对应的API的配置,请检查 ‘设置 -> MJ设置’ 配置!')
|
throw new Error(t("没有找到对应的API的配置,请检查 ‘{data}’ 配置!", {
|
||||||
|
data: t('设置 -> MJ设置')
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
let apiProvider = GetApiDefineDataById(this.mjApiSetting.apiUrl as string)
|
let apiProvider = GetApiDefineDataById(this.mjApiSetting.apiUrl as string)
|
||||||
if (apiProvider.mj_url == null) {
|
if (apiProvider.mj_url == null) {
|
||||||
throw new Error('当前API不支持MJ出图,请检查 ‘设置 -> MJ设置’ 配置!')
|
throw new Error(t("当前API不支持MJ出图,请检查 ‘{data}’ 配置!", {
|
||||||
|
data: t('设置 -> MJ设置')
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
this.imagineUrl = apiProvider.mj_url.imagine
|
this.imagineUrl = apiProvider.mj_url.imagine
|
||||||
this.describeUrl = apiProvider.mj_url.describe
|
this.describeUrl = apiProvider.mj_url.describe
|
||||||
|
this.videoUrl = apiProvider.mj_url.video ?? ''
|
||||||
this.fetchTaskUrl = apiProvider.mj_url.once_get_task
|
this.fetchTaskUrl = apiProvider.mj_url.once_get_task
|
||||||
this.token = this.mjApiSetting.apiKey
|
this.token = this.mjApiSetting.apiKey
|
||||||
} else if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.MJ_PACKAGE) {
|
} else if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.MJ_PACKAGE) {
|
||||||
@ -173,20 +184,27 @@ export class MJApiService extends MJBasic {
|
|||||||
isEmpty(this.mjPackageSetting.packageToken)
|
isEmpty(this.mjPackageSetting.packageToken)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'没有找到对应的生图包的配置或配置有误,请检查 ‘设置 -> MJ设置 -> 生图包模式’ 配置!'
|
t("没有找到对应的生图包的配置或配置有误,请检查 ‘{data}’ 配置!", {
|
||||||
|
data: t('设置 -> MJ设置 -> 生图包模式')
|
||||||
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let mjProvider = GetApiDefineDataById(this.mjPackageSetting.selectPackage)
|
let mjProvider = GetApiDefineDataById(this.mjPackageSetting.selectPackage)
|
||||||
if (!mjProvider.isPackage) {
|
if (!mjProvider.isPackage) {
|
||||||
throw new Error('当前选择的包不支持,请检查 ‘设置 -> MJ设置 -> 生图包模式’ 配置!')
|
throw new Error(t("当前选择的包不支持,请检查 ‘{data}’ 配置!", {
|
||||||
|
data: t('设置 -> MJ设置 -> 生图包模式')
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mjProvider.mj_url == null) {
|
if (mjProvider.mj_url == null) {
|
||||||
throw new Error('当前选择的包不支持,请检查 ‘设置 -> MJ设置 -> 生图包模式’ 配置!')
|
throw new Error(t("当前选择的包不支持,请检查 ‘{data}’ 配置!", {
|
||||||
|
data: t('设置 -> MJ设置 -> 生图包模式')
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
this.imagineUrl = mjProvider.mj_url.imagine
|
this.imagineUrl = mjProvider.mj_url.imagine
|
||||||
this.describeUrl = mjProvider.mj_url.describe
|
this.describeUrl = mjProvider.mj_url.describe
|
||||||
|
this.videoUrl = mjProvider.mj_url.video ?? ''
|
||||||
this.fetchTaskUrl = mjProvider.mj_url.once_get_task
|
this.fetchTaskUrl = mjProvider.mj_url.once_get_task
|
||||||
this.token = this.mjPackageSetting.packageToken
|
this.token = this.mjPackageSetting.packageToken
|
||||||
} else if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.LOCAL_MJ) {
|
} else if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.LOCAL_MJ) {
|
||||||
@ -197,7 +215,9 @@ export class MJApiService extends MJBasic {
|
|||||||
isEmpty(this.mjLocalSetting.token)
|
isEmpty(this.mjLocalSetting.token)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'本地代理模式的设置不完善或配置错误,请检查 ‘设置 -> MJ设置 -> 本地代理模式’ 配置!'
|
t("本地代理模式的设置不完善或配置错误,请检查 ‘{data}’ 配置!", {
|
||||||
|
data: t('设置 -> MJ设置 -> 本地代理模式')
|
||||||
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
this.mjLocalSetting.requestUrl.endsWith('/')
|
this.mjLocalSetting.requestUrl.endsWith('/')
|
||||||
@ -206,6 +226,7 @@ export class MJApiService extends MJBasic {
|
|||||||
|
|
||||||
this.imagineUrl = this.mjLocalSetting.requestUrl + '/mj/submit/imagine'
|
this.imagineUrl = this.mjLocalSetting.requestUrl + '/mj/submit/imagine'
|
||||||
this.describeUrl = this.mjLocalSetting.requestUrl + '/mj/submit/describe'
|
this.describeUrl = this.mjLocalSetting.requestUrl + '/mj/submit/describe'
|
||||||
|
this.videoUrl = this.mjLocalSetting.requestUrl + '/mj/submit/video'
|
||||||
this.fetchTaskUrl = this.mjLocalSetting.requestUrl + '/mj/task/${id}/fetch'
|
this.fetchTaskUrl = this.mjLocalSetting.requestUrl + '/mj/task/${id}/fetch'
|
||||||
this.token = this.mjLocalSetting.token
|
this.token = this.mjLocalSetting.token
|
||||||
} else if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.REMOTE_MJ) {
|
} else if (this.mjGeneralSetting?.outputMode == ImageGenerateMode.REMOTE_MJ) {
|
||||||
@ -213,10 +234,13 @@ export class MJApiService extends MJBasic {
|
|||||||
|
|
||||||
this.imagineUrl = define.remotemj_api + 'mj/submit/imagine'
|
this.imagineUrl = define.remotemj_api + 'mj/submit/imagine'
|
||||||
this.describeUrl = define.remotemj_api + 'mj/submit/describe'
|
this.describeUrl = define.remotemj_api + 'mj/submit/describe'
|
||||||
|
this.videoUrl = ""
|
||||||
this.fetchTaskUrl = define.remotemj_api + 'mj/task/${id}/fetch'
|
this.fetchTaskUrl = define.remotemj_api + 'mj/task/${id}/fetch'
|
||||||
this.token = define.remote_token
|
this.token = define.remote_token
|
||||||
} else {
|
} else {
|
||||||
throw new Error('当前的MJ出图模式不支持,请检查 ‘设置 -> MJ设置’ 配置!')
|
throw new Error(t("当前的MJ出图模式不支持,请检查 ‘{data}’ 配置!", {
|
||||||
|
data: t('设置 -> MJ设置')
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +360,7 @@ export class MJApiService extends MJBasic {
|
|||||||
res = await this.SubmitMJDescribeAPI(param)
|
res = await this.SubmitMJDescribeAPI(param)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error('MJ反推的类型不支持,反推只支持,API和代理模式')
|
throw new Error(t('MJ反推的类型不支持,反推只支持API和代理模式'))
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@ -485,7 +509,7 @@ export class MJApiService extends MJBasic {
|
|||||||
res = await this.SubmitMJImagineAPI(taskId, prompt)
|
res = await this.SubmitMJImagineAPI(taskId, prompt)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error('MJ出图的类型不支持')
|
throw new Error(t('MJ出图的类型不支持'))
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@ -551,7 +575,7 @@ export class MJApiService extends MJBasic {
|
|||||||
delete data.accountFilter.instanceId
|
delete data.accountFilter.instanceId
|
||||||
useTransfer = this.mjRemoteSetting?.isForward ?? false
|
useTransfer = this.mjRemoteSetting?.isForward ?? false
|
||||||
} else {
|
} else {
|
||||||
throw new Error('不支持的MJ出图类型')
|
throw new Error(t('不支持的MJ出图类型'))
|
||||||
}
|
}
|
||||||
console.log('useTransfer', useTransfer)
|
console.log('useTransfer', useTransfer)
|
||||||
return {
|
return {
|
||||||
@ -591,7 +615,7 @@ export class MJApiService extends MJBasic {
|
|||||||
async SubmitMJImagineAPI(taskId: string, prompt: string): Promise<string> {
|
async SubmitMJImagineAPI(taskId: string, prompt: string): Promise<string> {
|
||||||
// 这边校验是不是在提示词包含不正确的链接
|
// 这边校验是不是在提示词包含不正确的链接
|
||||||
if (prompt.includes('feishu.cn')) {
|
if (prompt.includes('feishu.cn')) {
|
||||||
throw new Error('提示词里面出现了 feishu.cn 飞书的链接,请检查并复制正确的链接')
|
throw new Error(t("提示词里面出现了 feishu.cn 飞书的链接,请检查并复制正确的链接"))
|
||||||
}
|
}
|
||||||
|
|
||||||
let { body, config } = this.GenerateImagineRequestBody(prompt)
|
let { body, config } = this.GenerateImagineRequestBody(prompt)
|
||||||
@ -607,7 +631,7 @@ export class MJApiService extends MJBasic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resData == null) {
|
if (resData == null) {
|
||||||
throw new Error('返回的数据为空')
|
throw new Error(t('返回的数据为空'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 某些API的返回的code为23,表示队列已满,需要重新请求
|
// 某些API的返回的code为23,表示队列已满,需要重新请求
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { BookTaskService } from '@/define/db/service/book/bookTaskService'
|
|||||||
import { BookService } from '@/define/db/service/book/bookService'
|
import { BookService } from '@/define/db/service/book/bookService'
|
||||||
import { PresetRealmService } from '@/define/db/service/presetService'
|
import { PresetRealmService } from '@/define/db/service/presetService'
|
||||||
import { TaskListService } from '@/define/db/service/book/taskListService'
|
import { TaskListService } from '@/define/db/service/book/taskListService'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class MJBasic {
|
export class MJBasic {
|
||||||
optionRealmService!: OptionRealmService
|
optionRealmService!: OptionRealmService
|
||||||
@ -73,7 +74,7 @@ export class MJBasic {
|
|||||||
)
|
)
|
||||||
this.mjGeneralSetting = optionSerialization<SettingModal.MJGeneralSettings>(
|
this.mjGeneralSetting = optionSerialization<SettingModal.MJGeneralSettings>(
|
||||||
generalSetting,
|
generalSetting,
|
||||||
'‘设置 -> MJ设置’'
|
t('设置 -> MJ设置')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +94,7 @@ export class MJBasic {
|
|||||||
let apiSetting = this.optionRealmService.GetOptionByKey(OptionKeyName.Midjourney.ApiSetting)
|
let apiSetting = this.optionRealmService.GetOptionByKey(OptionKeyName.Midjourney.ApiSetting)
|
||||||
this.mjApiSetting = optionSerialization<SettingModal.MJApiSettings>(
|
this.mjApiSetting = optionSerialization<SettingModal.MJApiSettings>(
|
||||||
apiSetting,
|
apiSetting,
|
||||||
'‘设置 -> MJ设置 -> API设置’'
|
t('设置 -> MJ设置 -> API模式')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +117,7 @@ export class MJBasic {
|
|||||||
)
|
)
|
||||||
this.mjPackageSetting = optionSerialization<SettingModal.MJPackageSetting>(
|
this.mjPackageSetting = optionSerialization<SettingModal.MJPackageSetting>(
|
||||||
packageSetting,
|
packageSetting,
|
||||||
'‘设置 -> MJ设置 -> 生图包设置’'
|
t("设置 -> MJ设置 -> 生图包模式")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +141,7 @@ export class MJBasic {
|
|||||||
)
|
)
|
||||||
this.mjRemoteSetting = optionSerialization<SettingModal.MJRemoteSetting>(
|
this.mjRemoteSetting = optionSerialization<SettingModal.MJRemoteSetting>(
|
||||||
remoteSetting,
|
remoteSetting,
|
||||||
'‘设置 -> MJ设置 -> 代理模式设置’'
|
t("设置 -> MJ设置 -> 代理模式")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +163,7 @@ export class MJBasic {
|
|||||||
let localSetting = this.optionRealmService.GetOptionByKey(OptionKeyName.Midjourney.LocalSetting)
|
let localSetting = this.optionRealmService.GetOptionByKey(OptionKeyName.Midjourney.LocalSetting)
|
||||||
this.mjLocalSetting = optionSerialization<SettingModal.MJLocalSetting>(
|
this.mjLocalSetting = optionSerialization<SettingModal.MJLocalSetting>(
|
||||||
localSetting,
|
localSetting,
|
||||||
'‘设置 -> MJ设置 -> 本地代理设置’'
|
t("设置 -> MJ设置 -> 本地代理模式")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import {
|
|||||||
import { ImageSplit } from '@/define/Tools/image'
|
import { ImageSplit } from '@/define/Tools/image'
|
||||||
import { getProjectPath } from '../option/optionCommonService'
|
import { getProjectPath } from '../option/optionCommonService'
|
||||||
import { PresetBasicService } from '../preset/presetBasicService'
|
import { PresetBasicService } from '../preset/presetBasicService'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class MJServiceHandle extends MJBasic {
|
export class MJServiceHandle extends MJBasic {
|
||||||
mjApiService: MJApiService
|
mjApiService: MJApiService
|
||||||
@ -81,7 +82,7 @@ export class MJServiceHandle extends MJBasic {
|
|||||||
bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
||||||
bookTaskId: id
|
bookTaskId: id
|
||||||
})
|
})
|
||||||
bookTask = await this.bookTaskService.GetBookTaskDataById(id)
|
bookTask = (await this.bookTaskService.GetBookTaskDataById(id, true)) as Book.SelectBookTask
|
||||||
// 判断是不是有为空的
|
// 判断是不是有为空的
|
||||||
let emptyName = [] as string[]
|
let emptyName = [] as string[]
|
||||||
for (let i = 0; i < bookTaskDetail.length; i++) {
|
for (let i = 0; i < bookTaskDetail.length; i++) {
|
||||||
@ -91,32 +92,31 @@ export class MJServiceHandle extends MJBasic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (emptyName.length > 0) {
|
if (emptyName.length > 0) {
|
||||||
throw new Error(`${emptyName.join(',')} 的提示词为空,请先推理`)
|
throw new Error(t("{emptyName} 的提示词为空,请先推理提示词", {
|
||||||
|
emptyName: emptyName.join(', ')
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
|
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
|
||||||
let tempBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id)
|
let tempBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id, true)
|
||||||
if (tempBookTaskDetail == null) {
|
|
||||||
throw new Error('没有找到要合并的分镜数据,请检查ID是否正确')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEmpty(tempBookTaskDetail.gptPrompt)) {
|
if (isEmpty(tempBookTaskDetail.gptPrompt)) {
|
||||||
throw new Error('当前分镜没有推理提示词,请先生成')
|
throw new Error(t("{emptyName} 的提示词为空,请先推理提示词", {
|
||||||
|
emptyName: tempBookTaskDetail.name as string
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
bookTaskDetail = [tempBookTaskDetail]
|
bookTaskDetail = [tempBookTaskDetail]
|
||||||
bookTask = await this.bookTaskService.GetBookTaskDataById(
|
bookTask = (await this.bookTaskService.GetBookTaskDataById(
|
||||||
bookTaskDetail[0].bookTaskId as string
|
bookTaskDetail[0].bookTaskId as string,
|
||||||
)
|
true
|
||||||
|
)) as Book.SelectBookTask
|
||||||
} else {
|
} else {
|
||||||
throw new Error('未知的合并类型')
|
throw new Error(t('未知类型'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bookTaskDetail.length <= 0) {
|
if (bookTaskDetail.length <= 0) {
|
||||||
throw new Error('没有找到对应的需要合并的分镜数据')
|
throw new Error(t('未找到对应的小说分镜信息'))
|
||||||
}
|
|
||||||
let book = await this.bookService.GetBookDataById(bookTask.bookId as string)
|
|
||||||
if (book == null) {
|
|
||||||
throw new Error('没有找到对应的小说数据,请检查小说ID是否正确')
|
|
||||||
}
|
}
|
||||||
|
let book = await this.bookService.GetBookDataById(bookTask.bookId as string, true)
|
||||||
|
|
||||||
// 获取通用的后缀
|
// 获取通用的后缀
|
||||||
let suffixParam = this.mjGeneralSetting?.commandSuffix
|
let suffixParam = this.mjGeneralSetting?.commandSuffix
|
||||||
@ -209,10 +209,12 @@ export class MJServiceHandle extends MJBasic {
|
|||||||
prompt: promptStr
|
prompt: promptStr
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return successMessage(result, 'MJ模式合并数据成功', 'MJOpt_MergePrompt')
|
return successMessage(result, t("MJ模式合并数据成功!"), 'MJOpt_MergePrompt')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'MJ合并提示词失败,错误信息如下:' + error.message,
|
t("MJ模式合并数据失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'MJServiceHandle_MergeMJPrompt'
|
'MJServiceHandle_MergeMJPrompt'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -248,25 +250,19 @@ export class MJServiceHandle extends MJBasic {
|
|||||||
): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
||||||
try {
|
try {
|
||||||
if (isEmpty(task.bookTaskDetailId)) {
|
if (isEmpty(task.bookTaskDetailId)) {
|
||||||
throw new Error('MJ出图,没有找到对应的分镜信息')
|
throw new Error(t('小说分镜ID不能为空'))
|
||||||
}
|
}
|
||||||
await this.GetMJGeneralSetting()
|
await this.GetMJGeneralSetting()
|
||||||
let bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(
|
let bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(
|
||||||
task.bookTaskDetailId as string
|
task.bookTaskDetailId as string, true
|
||||||
)
|
)
|
||||||
if (bookTaskDetail == null) {
|
|
||||||
throw new Error('没有找到对应的分镜信息')
|
let book = await this.bookService.GetBookDataById(bookTaskDetail.bookId as string, true)
|
||||||
}
|
|
||||||
let book = await this.bookService.GetBookDataById(bookTaskDetail.bookId as string)
|
|
||||||
if (book == null) {
|
|
||||||
throw new Error('没有找到对应的小说信息')
|
|
||||||
}
|
|
||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
||||||
bookTaskDetail.bookTaskId as string
|
bookTaskDetail.bookTaskId as string, true
|
||||||
)
|
)
|
||||||
if (bookTask == null) {
|
|
||||||
throw new Error('没有找到对应的任务信息')
|
|
||||||
}
|
|
||||||
// 调用方法合并提示词
|
// 调用方法合并提示词
|
||||||
let mergeRes = await this.MergeMJPrompt(
|
let mergeRes = await this.MergeMJPrompt(
|
||||||
task.bookTaskDetailId as string,
|
task.bookTaskDetailId as string,
|
||||||
@ -280,7 +276,9 @@ export class MJServiceHandle extends MJBasic {
|
|||||||
|
|
||||||
let prompt = bookTaskDetail.prompt
|
let prompt = bookTaskDetail.prompt
|
||||||
if (isEmpty(prompt) || prompt == undefined) {
|
if (isEmpty(prompt) || prompt == undefined) {
|
||||||
throw new Error(`${bookTaskDetail.name} 没有找到对应的提示词`)
|
throw new Error(t("{emptyName} 的提示词为空,请先推理提示词", {
|
||||||
|
emptyName: bookTaskDetail.name
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
// 这个就是任务ID
|
// 这个就是任务ID
|
||||||
let reqRes = await this.mjApiService.SubmitMJImagine(task.id as string, prompt)
|
let reqRes = await this.mjApiService.SubmitMJImagine(task.id as string, prompt)
|
||||||
@ -321,7 +319,9 @@ export class MJServiceHandle extends MJBasic {
|
|||||||
// throw new Error(`任务队列过多,${task.bookTaskDetailId} 重新提交排队`);
|
// throw new Error(`任务队列过多,${task.bookTaskDetailId} 重新提交排队`);
|
||||||
return successMessage(
|
return successMessage(
|
||||||
null,
|
null,
|
||||||
`任务队列过多,${task.bookTaskDetailId} 重新提交排队`,
|
t("任务队列过多,{id} 重新提交排队", {
|
||||||
|
id: task.bookTaskDetailId
|
||||||
|
}),
|
||||||
'MJServiceHandle_MJImagine'
|
'MJServiceHandle_MJImagine'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -355,12 +355,17 @@ export class MJServiceHandle extends MJBasic {
|
|||||||
await this.FetchImageTask(task, reqRes, book, bookTask, bookTaskDetail)
|
await this.FetchImageTask(task, reqRes, book, bookTask, bookTaskDetail)
|
||||||
return successMessage(
|
return successMessage(
|
||||||
null,
|
null,
|
||||||
`MJ生图成功,分镜ID:${task.bookTaskDetailId},任务ID:${task.id}`,
|
t("MJ生图成功,分镜ID:{bookTaskDetailId},任务ID:${taskId}", {
|
||||||
|
bookTaskDetailId: task.bookTaskDetailId,
|
||||||
|
taskId: task.id
|
||||||
|
}),
|
||||||
'MJServiceHandle_MJImagine'
|
'MJServiceHandle_MJImagine'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log(error.toString())
|
console.log(error.toString())
|
||||||
let errorMsg = 'MJ生图失败,失败信息如下:' + error.toString()
|
let errorMsg = t("MJ生图失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
})
|
||||||
this.taskListService.UpdateTaskStatus({
|
this.taskListService.UpdateTaskStatus({
|
||||||
id: task.id as string,
|
id: task.id as string,
|
||||||
status: BookBackTaskStatus.FAIL,
|
status: BookBackTaskStatus.FAIL,
|
||||||
@ -448,7 +453,9 @@ export class MJServiceHandle extends MJBasic {
|
|||||||
status: BookTaskStatus.IMAGE_FAIL
|
status: BookTaskStatus.IMAGE_FAIL
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
let errorMsg = `MJ生成图片失败,失败信息如下:${task_res.message}`
|
let errorMsg = t("MJ生图失败,{error}", {
|
||||||
|
error: task_res.message
|
||||||
|
})
|
||||||
this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(
|
this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(
|
||||||
task.bookTaskDetailId as string,
|
task.bookTaskDetailId as string,
|
||||||
{
|
{
|
||||||
@ -514,7 +521,7 @@ export class MJServiceHandle extends MJBasic {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (imageArray && imageArray.length < 4) {
|
if (imageArray && imageArray.length < 4) {
|
||||||
throw new Error('图片裁剪失败')
|
throw new Error(t('图片裁剪失败'))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < task_res.imageUrls.length; i++) {
|
for (let i = 0; i < task_res.imageUrls.length; i++) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { OptionRealmService } from '@/define/db/service/optionService'
|
|||||||
import { OptionKeyName } from '@/define/enum/option'
|
import { OptionKeyName } from '@/define/enum/option'
|
||||||
import { optionSerialization } from './optionSerialization'
|
import { optionSerialization } from './optionSerialization'
|
||||||
import { SettingModal } from '@/define/model/setting'
|
import { SettingModal } from '@/define/model/setting'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前项目的路径
|
* 获取当前项目的路径
|
||||||
@ -12,7 +13,7 @@ export async function getProjectPath() {
|
|||||||
let projectOption = optionRealmService.GetOptionByKey(OptionKeyName.Software.ProjectPath)
|
let projectOption = optionRealmService.GetOptionByKey(OptionKeyName.Software.ProjectPath)
|
||||||
let projectPath: string = optionSerialization(
|
let projectPath: string = optionSerialization(
|
||||||
projectOption,
|
projectOption,
|
||||||
'‘设置-> 通用设置 -> 项目地址’'
|
t('设置-> 通用设置 -> 项目地址')
|
||||||
) as string
|
) as string
|
||||||
return projectPath
|
return projectPath
|
||||||
}
|
}
|
||||||
@ -28,7 +29,7 @@ export async function getGeneralSetting() {
|
|||||||
)
|
)
|
||||||
let generalSetting: SettingModal.GeneralSettings = optionSerialization(
|
let generalSetting: SettingModal.GeneralSettings = optionSerialization(
|
||||||
generalSettingOption,
|
generalSettingOption,
|
||||||
'‘设置-> 通用设置’'
|
t('设置 -> 通用设置')
|
||||||
) as SettingModal.GeneralSettings
|
) as SettingModal.GeneralSettings
|
||||||
return generalSetting
|
return generalSetting
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { OptionRealmService } from '@/define/db/service/optionService'
|
import { OptionRealmService } from '@/define/db/service/optionService'
|
||||||
import { OptionType } from '@/define/enum/option'
|
import { OptionType } from '@/define/enum/option'
|
||||||
import { ErrorItem, SuccessItem } from '@/define/model/generalResponse'
|
import { ErrorItem, SuccessItem } from '@/define/model/generalResponse'
|
||||||
|
import { t } from '@/i18n'
|
||||||
import { errorMessage, successMessage } from '@/public/generalTools'
|
import { errorMessage, successMessage } from '@/public/generalTools'
|
||||||
export class OptionOptions {
|
export class OptionOptions {
|
||||||
optionRealmService!: OptionRealmService
|
optionRealmService!: OptionRealmService
|
||||||
constructor() {}
|
constructor() { }
|
||||||
|
|
||||||
/** 初始化数据库服务 */
|
/** 初始化数据库服务 */
|
||||||
async InitService() {
|
async InitService() {
|
||||||
@ -22,10 +23,15 @@ export class OptionOptions {
|
|||||||
try {
|
try {
|
||||||
await this.InitService()
|
await this.InitService()
|
||||||
let res = this.optionRealmService.GetOptionByKey(key)
|
let res = this.optionRealmService.GetOptionByKey(key)
|
||||||
return successMessage(res, '获取成功 OptionKey: ' + key, 'OptionOptions.GetOptionByKey')
|
return successMessage(res, t("获取成功 OptionKey: {key}", {
|
||||||
|
key: key
|
||||||
|
}), 'OptionOptions.GetOptionByKey')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'获取失败 OptionKey: ' + key + ',失败信息如下 : ' + error.message,
|
t("获取失败 OptionKey: {key},失败原因:{error}", {
|
||||||
|
key: key,
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'OptionOptions.GetOptionByKey'
|
'OptionOptions.GetOptionByKey'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -47,10 +53,15 @@ export class OptionOptions {
|
|||||||
value = value.toString()
|
value = value.toString()
|
||||||
}
|
}
|
||||||
let res = this.optionRealmService.ModifyOptionByKey(key, value, type)
|
let res = this.optionRealmService.ModifyOptionByKey(key, value, type)
|
||||||
return successMessage(res, '修改成功 OptionKey: ' + key, 'OptionOptions.ModifyOptionByKey')
|
return successMessage(res, t("修改成功 OptionKey: {key}", {
|
||||||
|
key: key
|
||||||
|
}), 'OptionOptions.ModifyOptionByKey')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`修改失败 OptionKey: ${key} , 失败信息如下: ${error.message}`,
|
t("修改失败 OptionKey: {key},失败原因:{error}", {
|
||||||
|
key: key,
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'OptionOptions.ModifyOptionByKey'
|
'OptionOptions.ModifyOptionByKey'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { getOptionType, OptionType } from '@/define/enum/option'
|
import { getOptionType, OptionType } from '@/define/enum/option'
|
||||||
|
import { t } from '@/i18n'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -8,10 +9,12 @@ import { isEmpty } from 'lodash'
|
|||||||
* @returns 转换后的值
|
* @returns 转换后的值
|
||||||
*/
|
*/
|
||||||
export function convertStringToType<T>(value: string, type: OptionType, checkString?: string): T {
|
export function convertStringToType<T>(value: string, type: OptionType, checkString?: string): T {
|
||||||
let checkErrorString = '请到 ' + checkString + ' 检查设置!'
|
let checkErrorString = t("请到 “{checkString}” 检查设置!", {
|
||||||
|
checkString: checkString
|
||||||
|
})
|
||||||
// 如果值为空,直接报错
|
// 如果值为空,直接报错
|
||||||
if (value === undefined || value === null || value === '') {
|
if (value === undefined || value === null || value === '') {
|
||||||
throw new Error('当前值为空!' + checkString ? checkErrorString : '')
|
throw new Error(t('当前值为空!') + checkString ? checkErrorString : '')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -54,13 +57,13 @@ export const optionSerialization = <T>(
|
|||||||
defaultValue?: T
|
defaultValue?: T
|
||||||
): T => {
|
): T => {
|
||||||
if (option == null) {
|
if (option == null) {
|
||||||
if (defaultValue) {
|
if (defaultValue != null) {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
throw new Error('未找到选项对象,请检查所有的选项设置是否存在!')
|
throw new Error(t("未找到选项对象,请检查所有的选项设置是否存在!"))
|
||||||
}
|
}
|
||||||
if (option.value == null || option.value == undefined || isEmpty(option.value)) {
|
if (option.value == null || option.value == undefined || isEmpty(option.value)) {
|
||||||
if (defaultValue) {
|
if (defaultValue != null) {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
throw new Error('option value is null')
|
throw new Error('option value is null')
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { PresetCategory } from '@/define/data/presetData'
|
|||||||
import { PresetBasic } from './presetBasic'
|
import { PresetBasic } from './presetBasic'
|
||||||
import { PromptMergeType } from '@/define/enum/bookEnum'
|
import { PromptMergeType } from '@/define/enum/bookEnum'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class PresetBasicService extends PresetBasic {
|
export class PresetBasicService extends PresetBasic {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -72,7 +73,7 @@ export class PresetBasicService extends PresetBasic {
|
|||||||
}
|
}
|
||||||
return { characterString: result, characterUrl: '' }
|
return { characterString: result, characterUrl: '' }
|
||||||
} else {
|
} else {
|
||||||
throw new Error('不支持的合并类型')
|
throw new Error(t('不支持的合并类型'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { Base64ToFile, GetImageTypeFromBase64 } from '@/define/Tools/image'
|
|||||||
import { errorMessage, successMessage } from '@/public/generalTools'
|
import { errorMessage, successMessage } from '@/public/generalTools'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { PresetBasic } from './presetBasic'
|
import { PresetBasic } from './presetBasic'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 预设服务处理器类
|
* 预设服务处理器类
|
||||||
@ -37,11 +38,13 @@ export class PresetServiceHandle extends PresetBasic {
|
|||||||
try {
|
try {
|
||||||
await this.InitPresetBasic()
|
await this.InitPresetBasic()
|
||||||
let res = this.presetRealmService.GetPresetByCondition(queryCondition)
|
let res = this.presetRealmService.GetPresetByCondition(queryCondition)
|
||||||
return successMessage(res, '获取预设成功!', 'PresetServiceHandle_GetPresetByCondition')
|
return successMessage(res, t("获取预设列表成功!"), 'PresetServiceHandle_GetPresetByCondition')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`获取预设失败,失败原因如下:${error.message}`,
|
t("获取预设列表失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'PresetServiceHandle_GetPresetByCondition'
|
'PresetServiceHandle_GetPresetByCondition'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -60,11 +63,13 @@ export class PresetServiceHandle extends PresetBasic {
|
|||||||
try {
|
try {
|
||||||
await this.InitPresetBasic()
|
await this.InitPresetBasic()
|
||||||
let res = this.presetRealmService.GetPresetById(id)
|
let res = this.presetRealmService.GetPresetById(id)
|
||||||
return successMessage(res, '获取预设成功!', 'PresetServiceHandle_GetPresetById')
|
return successMessage(res, t('获取预设成功!'), 'PresetServiceHandle_GetPresetById')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`获取预设失败,失败原因如下:${error.message}`,
|
t("获取预设失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'PresetServiceHandle_GetPresetById'
|
'PresetServiceHandle_GetPresetById'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -91,7 +96,7 @@ export class PresetServiceHandle extends PresetBasic {
|
|||||||
// 判断base64是什么图片类型
|
// 判断base64是什么图片类型
|
||||||
let ext = GetImageTypeFromBase64(element)
|
let ext = GetImageTypeFromBase64(element)
|
||||||
if (ext != '.png' && ext != '.jpg' && ext != '.webp') {
|
if (ext != '.png' && ext != '.jpg' && ext != '.webp') {
|
||||||
throw new Error('图片格式不合法!')
|
throw new Error(t("图片格式不合法!只支持 png、jpg、webp 格式的图片!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
let imagePath = JoinPath(
|
let imagePath = JoinPath(
|
||||||
@ -106,11 +111,13 @@ export class PresetServiceHandle extends PresetBasic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let res = this.presetRealmService.AddPreset(preset)
|
let res = this.presetRealmService.AddPreset(preset)
|
||||||
return successMessage(res, '添加预设成功!', 'PresetServiceHandle_AddPreset')
|
return successMessage(res, t('添加预设成功!'), 'PresetServiceHandle_AddPreset')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`添加预设失败,失败原因如下:${error.message}`,
|
t("添加预设失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'PresetServiceHandle_AddPreset'
|
'PresetServiceHandle_AddPreset'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -142,7 +149,7 @@ export class PresetServiceHandle extends PresetBasic {
|
|||||||
// 判断base64是什么图片类型
|
// 判断base64是什么图片类型
|
||||||
let ext = GetImageTypeFromBase64(element)
|
let ext = GetImageTypeFromBase64(element)
|
||||||
if (ext != '.png' && ext != '.jpg' && ext != '.webp') {
|
if (ext != '.png' && ext != '.jpg' && ext != '.webp') {
|
||||||
throw new Error('图片格式不合法!')
|
throw new Error(t("图片格式不合法!只支持 png、jpg、webp 格式的图片!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
let imagePath = JoinPath(
|
let imagePath = JoinPath(
|
||||||
@ -166,11 +173,13 @@ export class PresetServiceHandle extends PresetBasic {
|
|||||||
preset.showImage = iamges
|
preset.showImage = iamges
|
||||||
}
|
}
|
||||||
let res = this.presetRealmService.ModifyPreset(id, preset)
|
let res = this.presetRealmService.ModifyPreset(id, preset)
|
||||||
return successMessage(res, '修改预设成功!', 'PresetServiceHandle_ModifyPreset')
|
return successMessage(res, t("修改预设成功!"), 'PresetServiceHandle_ModifyPreset')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`修改预设失败,失败原因如下:${error.message}`,
|
t("修改预设失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'PresetServiceHandle_ModifyPreset'
|
'PresetServiceHandle_ModifyPreset'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -189,11 +198,13 @@ export class PresetServiceHandle extends PresetBasic {
|
|||||||
try {
|
try {
|
||||||
await this.InitPresetBasic()
|
await this.InitPresetBasic()
|
||||||
this.presetRealmService.DeletePreset(id)
|
this.presetRealmService.DeletePreset(id)
|
||||||
return successMessage(null, '删除预设成功!', 'PresetServiceHandle_DeletePreset')
|
return successMessage(null, t("删除预设成功!"), 'PresetServiceHandle_DeletePreset')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 处理错误,返回错误信息
|
// 处理错误,返回错误信息
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`删除预设失败,失败原因如下:${error.message}`,
|
t('删除预设失败,{error}', {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'PresetServiceHandle_DeletePreset'
|
'PresetServiceHandle_DeletePreset'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { ImageGenerateMode } from '@/define/data/mjData'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { getProjectPath } from '../option/optionCommonService'
|
import { getProjectPath } from '../option/optionCommonService'
|
||||||
import { SDServiceHandle } from './sdServiceHandle'
|
import { SDServiceHandle } from './sdServiceHandle'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class ComfyUIServiceHandle extends SDServiceHandle {
|
export class ComfyUIServiceHandle extends SDServiceHandle {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -31,21 +32,15 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
await this.InitSDBasic()
|
await this.InitSDBasic()
|
||||||
let comfyUISettingCollection = await this.GetComfyUISetting()
|
let comfyUISettingCollection = await this.GetComfyUISetting()
|
||||||
let bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(
|
let bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(
|
||||||
task.bookTaskDetailId as string
|
task.bookTaskDetailId as string, true
|
||||||
)
|
)
|
||||||
if (bookTaskDetail == null) {
|
|
||||||
throw new Error('未找到对应的小说分镜')
|
let book = await this.bookService.GetBookDataById(bookTaskDetail.bookId as string, true)
|
||||||
}
|
|
||||||
let book = await this.bookService.GetBookDataById(bookTaskDetail.bookId as string)
|
|
||||||
if (book == null) {
|
|
||||||
throw new Error('未找到对应的小说')
|
|
||||||
}
|
|
||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
||||||
bookTaskDetail.bookTaskId as string
|
bookTaskDetail.bookTaskId as string, true
|
||||||
)
|
)
|
||||||
if (bookTask == null) {
|
|
||||||
throw new Error('未找到对应的小说任务')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用方法合并提示词
|
// 调用方法合并提示词
|
||||||
let mergeRes = await this.MergeSDPrompt(
|
let mergeRes = await this.MergeSDPrompt(
|
||||||
@ -82,11 +77,11 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
SendReturnMessage(
|
SendReturnMessage(
|
||||||
{
|
{
|
||||||
code: 1,
|
code: 1,
|
||||||
message: '任务已提交',
|
message: t('任务已提交'),
|
||||||
id: task.bookTaskDetailId as string,
|
id: task.bookTaskDetailId as string,
|
||||||
data: {
|
data: {
|
||||||
status: 'submited',
|
status: 'submited',
|
||||||
message: '任务已提交',
|
message: t('任务已提交'),
|
||||||
id: task.bookTaskDetailId as string
|
id: task.bookTaskDetailId as string
|
||||||
} as any
|
} as any
|
||||||
},
|
},
|
||||||
@ -102,7 +97,9 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
comfyUISettingCollection
|
comfyUISettingCollection
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
let errorMsg = 'ComfyUI 生图失败,失败信息如下:' + error.toString()
|
let errorMsg = t("ComfyUI生图失败,{error}", {
|
||||||
|
error: error.message()
|
||||||
|
})
|
||||||
this.taskListService.UpdateTaskStatus({
|
this.taskListService.UpdateTaskStatus({
|
||||||
id: task.id as string,
|
id: task.id as string,
|
||||||
status: BookBackTaskStatus.FAIL,
|
status: BookBackTaskStatus.FAIL,
|
||||||
@ -153,7 +150,7 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
)
|
)
|
||||||
result['comfyuiSimpleSetting'] = optionSerialization<SettingModal.ComfyUISimpleSettingModel>(
|
result['comfyuiSimpleSetting'] = optionSerialization<SettingModal.ComfyUISimpleSettingModel>(
|
||||||
comfyuiSimpleSettingOption,
|
comfyuiSimpleSettingOption,
|
||||||
'设置 -> ComfyUI 设置'
|
t("设置 -> ComfyUI 设置")
|
||||||
)
|
)
|
||||||
|
|
||||||
let comfyuiWorkFlowSettingOption = optionRealmService.GetOptionByKey(
|
let comfyuiWorkFlowSettingOption = optionRealmService.GetOptionByKey(
|
||||||
@ -162,12 +159,12 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
|
|
||||||
let comfyuiWorkFlowList = optionSerialization<SettingModal.ComfyUIWorkFlowSettingModel[]>(
|
let comfyuiWorkFlowList = optionSerialization<SettingModal.ComfyUIWorkFlowSettingModel[]>(
|
||||||
comfyuiWorkFlowSettingOption,
|
comfyuiWorkFlowSettingOption,
|
||||||
'设置 -> ComfyUI 设置'
|
t("设置 -> ComfyUI 设置")
|
||||||
)
|
)
|
||||||
result['comfyuiWorkFlowSetting'] = comfyuiWorkFlowList
|
result['comfyuiWorkFlowSetting'] = comfyuiWorkFlowList
|
||||||
|
|
||||||
if (comfyuiWorkFlowList.length <= 0) {
|
if (comfyuiWorkFlowList.length <= 0) {
|
||||||
throw new Error('ComfyUI的工作流设置为空,请检查是否正确设置!!')
|
throw new Error(t('ComfyUI的工作流设置为空,请检查是否正确设置!!'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取选中的工作流
|
// 获取选中的工作流
|
||||||
@ -175,12 +172,12 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
(item) => item.id == result.comfyuiSimpleSetting.selectedWorkflow
|
(item) => item.id == result.comfyuiSimpleSetting.selectedWorkflow
|
||||||
)
|
)
|
||||||
if (selectedWorkflow == null) {
|
if (selectedWorkflow == null) {
|
||||||
throw new Error('未找到选中的工作流,请检查是否正确设置!!')
|
throw new Error(t('未找到选中的工作流,请检查是否正确设置!!'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断工作流对应的文件是不是存在
|
// 判断工作流对应的文件是不是存在
|
||||||
if (!(await CheckFileOrDirExist(selectedWorkflow.workflowPath))) {
|
if (!(await CheckFileOrDirExist(selectedWorkflow.workflowPath))) {
|
||||||
throw new Error('本地未找到选中的工作流文件地址,请检查是否正确设置!!')
|
throw new Error(t('本地未找到选中的工作流文件地址,请检查是否正确设置!!'))
|
||||||
}
|
}
|
||||||
result['comfyuiSelectedWorkflow'] = selectedWorkflow
|
result['comfyuiSelectedWorkflow'] = selectedWorkflow
|
||||||
|
|
||||||
@ -204,7 +201,7 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
let jsonContentString = await fs.promises.readFile(workflowPath, 'utf-8')
|
let jsonContentString = await fs.promises.readFile(workflowPath, 'utf-8')
|
||||||
|
|
||||||
if (!ValidateJson(jsonContentString)) {
|
if (!ValidateJson(jsonContentString)) {
|
||||||
throw new Error('工作流文件内容不是有效的JSON格式,请检查是否正确设置!!')
|
throw new Error(t('工作流文件内容不是有效的JSON格式,请检查是否正确设置!!'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let jsonContent = JSON.parse(jsonContentString)
|
let jsonContent = JSON.parse(jsonContentString)
|
||||||
@ -214,10 +211,10 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
for (const key in jsonContent) {
|
for (const key in jsonContent) {
|
||||||
let element = jsonContent[key]
|
let element = jsonContent[key]
|
||||||
if (element && element.class_type === 'CLIPTextEncode') {
|
if (element && element.class_type === 'CLIPTextEncode') {
|
||||||
if (element._meta?.title === '正向提示词') {
|
if (element._meta?.title === '正向提示词' || element._meta?.title === 'Positive Prompt') {
|
||||||
jsonContent[key].inputs.text = prompt
|
jsonContent[key].inputs.text = prompt
|
||||||
}
|
}
|
||||||
if (element._meta?.title === '反向提示词') {
|
if (element._meta?.title === '反向提示词' || element._meta?.title === 'Negative Prompt') {
|
||||||
jsonContent[key].inputs.text = negativePrompt
|
jsonContent[key].inputs.text = negativePrompt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -236,7 +233,7 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('工作流文件内容不是有效的JSON对象格式,请检查是否正确设置!!')
|
throw new Error(t('工作流文件内容不是有效的JSON对象格式,请检查是否正确设置!!'))
|
||||||
}
|
}
|
||||||
let result = JSON.stringify({
|
let result = JSON.stringify({
|
||||||
prompt: jsonContent
|
prompt: jsonContent
|
||||||
@ -281,7 +278,10 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
errorNode += key + ', '
|
errorNode += key + ', '
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let msg = '错误信息:' + resData.error.message + '错误节点:' + errorNode
|
let msg = t("错误信息:{error},错误节点:{node}", {
|
||||||
|
error: resData.error.message,
|
||||||
|
node: errorNode
|
||||||
|
})
|
||||||
throw new Error(msg)
|
throw new Error(msg)
|
||||||
}
|
}
|
||||||
// 没有错误 判断是不是成功
|
// 没有错误 判断是不是成功
|
||||||
@ -289,7 +289,7 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
// 成功
|
// 成功
|
||||||
return resData
|
return resData
|
||||||
} else {
|
} else {
|
||||||
throw new Error('未知错误,未获取到请求ID,请检查是否正确设置!!')
|
throw new Error(t('未知错误,未获取到请求ID,请检查是否正确设置!!'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,7 +318,9 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
status: BookTaskStatus.IMAGE_FAIL
|
status: BookTaskStatus.IMAGE_FAIL
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
let errorMsg = `MJ生成图片失败,失败信息如下:${resData.message}`
|
let errorMsg = t("ComfyUI生图失败,{error}", {
|
||||||
|
error: resData.message
|
||||||
|
})
|
||||||
this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(
|
this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(
|
||||||
task.bookTaskDetailId as string,
|
task.bookTaskDetailId as string,
|
||||||
{
|
{
|
||||||
@ -365,7 +367,7 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
messageId: promptId,
|
messageId: promptId,
|
||||||
action: MJAction.IMAGINE,
|
action: MJAction.IMAGINE,
|
||||||
status: 'running',
|
status: 'running',
|
||||||
message: '任务正在执行中'
|
message: t('任务正在执行中')
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -376,7 +378,7 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
id: task.bookTaskDetailId as string,
|
id: task.bookTaskDetailId as string,
|
||||||
data: {
|
data: {
|
||||||
status: 'running',
|
status: 'running',
|
||||||
message: '任务正在执行中',
|
message: t('任务正在执行中'),
|
||||||
id: task.bookTaskDetailId
|
id: task.bookTaskDetailId
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -414,18 +416,18 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
messageId: promptId,
|
messageId: promptId,
|
||||||
action: MJAction.IMAGINE,
|
action: MJAction.IMAGINE,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
message: 'ComfyUI 生成图片成功'
|
message: t("ComfyUI 生成图片成功!")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
SendReturnMessage(
|
SendReturnMessage(
|
||||||
{
|
{
|
||||||
code: 1,
|
code: 1,
|
||||||
message: 'ComfyUI 生成图片成功',
|
message: t("ComfyUI 生成图片成功!"),
|
||||||
id: task.bookTaskDetailId as string,
|
id: task.bookTaskDetailId as string,
|
||||||
data: {
|
data: {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
message: 'ComfyUI 生成图片成功',
|
message: t("ComfyUI 生成图片成功!"),
|
||||||
id: task.bookTaskDetailId,
|
id: task.bookTaskDetailId,
|
||||||
outImagePath: res.outImagePath + '?t=' + new Date().getTime(),
|
outImagePath: res.outImagePath + '?t=' + new Date().getTime(),
|
||||||
subImagePath: res.subImagePath.map((item) => item + '?t=' + new Date().getTime())
|
subImagePath: res.subImagePath.map((item) => item + '?t=' + new Date().getTime())
|
||||||
@ -456,10 +458,10 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
comfyUISettingCollection: SettingModal.ComfyUISettingCollection
|
comfyUISettingCollection: SettingModal.ComfyUISettingCollection
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
if (isEmpty(promptId)) {
|
if (isEmpty(promptId)) {
|
||||||
throw new Error('未获取到请求ID,请检查是否正确设置!!')
|
throw new Error(t("ComfyUI生图失败,未获取到请求ID,请检查是否正确设置!!"))
|
||||||
}
|
}
|
||||||
if (isEmpty(comfyUISettingCollection.comfyuiSimpleSetting.requestUrl)) {
|
if (isEmpty(comfyUISettingCollection.comfyuiSimpleSetting.requestUrl)) {
|
||||||
throw new Error('未获取到ComfyUI的请求地址,请检查是否正确设置!!')
|
throw new Error(t('未获取到ComfyUI的请求地址,请检查是否正确设置!!'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = comfyUISettingCollection.comfyuiSimpleSetting.requestUrl?.replace(
|
let url = comfyUISettingCollection.comfyuiSimpleSetting.requestUrl?.replace(
|
||||||
@ -489,7 +491,7 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
return {
|
return {
|
||||||
progress: 0,
|
progress: 0,
|
||||||
status: 'in_progress',
|
status: 'in_progress',
|
||||||
message: '任务正在执行中'
|
message: t('任务正在执行中')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let completed = data.status?.completed
|
let completed = data.status?.completed
|
||||||
@ -514,7 +516,7 @@ export class ComfyUIServiceHandle extends SDServiceHandle {
|
|||||||
return {
|
return {
|
||||||
progress: 0,
|
progress: 0,
|
||||||
status: 'error',
|
status: 'error',
|
||||||
message: '生图失败,详细失败信息看启动器控制台'
|
message: t('ComfyUI 生图失败,详细失败信息看启动器控制台')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { MJAction, MJRespoonseType } from '@/define/enum/mjEnum'
|
|||||||
import { MJ } from '@/define/model/mj'
|
import { MJ } from '@/define/model/mj'
|
||||||
import { ImageGenerateMode } from '@/define/data/mjData'
|
import { ImageGenerateMode } from '@/define/data/mjData'
|
||||||
import { errorMessage, SendReturnMessage, successMessage } from '@/public/generalTools'
|
import { errorMessage, SendReturnMessage, successMessage } from '@/public/generalTools'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class FluxServiceHandle extends SDServiceHandle {
|
export class FluxServiceHandle extends SDServiceHandle {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -28,18 +29,14 @@ export class FluxServiceHandle extends SDServiceHandle {
|
|||||||
// 开始生图
|
// 开始生图
|
||||||
await this.GetSDImageSetting()
|
await this.GetSDImageSetting()
|
||||||
let bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(
|
let bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(
|
||||||
task.bookTaskDetailId as string
|
task.bookTaskDetailId as string, true
|
||||||
)
|
)
|
||||||
if (bookTaskDetail == null) {
|
|
||||||
throw new Error('未找到对应的分镜')
|
|
||||||
}
|
|
||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
||||||
bookTaskDetail.bookTaskId as string
|
bookTaskDetail.bookTaskId as string, true
|
||||||
)
|
)
|
||||||
let book = await this.bookService.GetBookDataById(bookTask.bookId as string)
|
let book = await this.bookService.GetBookDataById(bookTask.bookId as string, true)
|
||||||
if (book == null) {
|
|
||||||
throw new Error('未找到对应的小说')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用方法合并提示词
|
// 调用方法合并提示词
|
||||||
let mergeRes = await this.MergeSDPrompt(
|
let mergeRes = await this.MergeSDPrompt(
|
||||||
@ -159,7 +156,7 @@ export class FluxServiceHandle extends SDServiceHandle {
|
|||||||
status: 'success',
|
status: 'success',
|
||||||
outImagePath: outImagePath + '?t=' + new Date().getTime(),
|
outImagePath: outImagePath + '?t=' + new Date().getTime(),
|
||||||
subImagePath: subImagePath.map((item) => item + '?t=' + new Date().getTime()),
|
subImagePath: subImagePath.map((item) => item + '?t=' + new Date().getTime()),
|
||||||
message: 'FLUX FORGE 生成图片成功'
|
message: t("FLUX FORGE 生成图片成功!")
|
||||||
} as MJ.MJResponseToFront
|
} as MJ.MJResponseToFront
|
||||||
this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(
|
this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(
|
||||||
task.bookTaskDetailId as string,
|
task.bookTaskDetailId as string,
|
||||||
@ -168,7 +165,7 @@ export class FluxServiceHandle extends SDServiceHandle {
|
|||||||
SendReturnMessage(
|
SendReturnMessage(
|
||||||
{
|
{
|
||||||
code: 1,
|
code: 1,
|
||||||
message: 'FLUX FORGE 生成图片成功',
|
message: t("FLUX FORGE 生成图片成功!"),
|
||||||
id: bookTaskDetail.id as string,
|
id: bookTaskDetail.id as string,
|
||||||
data: {
|
data: {
|
||||||
...resp
|
...resp
|
||||||
@ -178,11 +175,13 @@ export class FluxServiceHandle extends SDServiceHandle {
|
|||||||
)
|
)
|
||||||
return successMessage(
|
return successMessage(
|
||||||
resp,
|
resp,
|
||||||
'FLUX FORGE 生成图片成功',
|
t("FLUX FORGE 生成图片成功!"),
|
||||||
'FluxServiceHandle_FluxForgeImageGenerate'
|
'FluxServiceHandle_FluxForgeImageGenerate'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
let errorMsg = 'FLUX FORGE 生成图片失败,错误信息如下:' + error.toString()
|
let errorMsg = t("FLUX FORGE 生成图片失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
})
|
||||||
this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId as string, {
|
this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId as string, {
|
||||||
mjApiUrl: this.sdImageSetting.requestUrl,
|
mjApiUrl: this.sdImageSetting.requestUrl,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import { getProjectPath } from '../option/optionCommonService'
|
|||||||
import { MJRespoonseType } from '@/define/enum/mjEnum'
|
import { MJRespoonseType } from '@/define/enum/mjEnum'
|
||||||
import { ImageGenerateMode } from '@/define/data/mjData'
|
import { ImageGenerateMode } from '@/define/data/mjData'
|
||||||
import { MJ } from '@/define/model/mj'
|
import { MJ } from '@/define/model/mj'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class SDServiceHandle extends SDBasic {
|
export class SDServiceHandle extends SDBasic {
|
||||||
presetBasicService!: PresetBasicService
|
presetBasicService!: PresetBasicService
|
||||||
@ -91,7 +92,7 @@ export class SDServiceHandle extends SDBasic {
|
|||||||
bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
|
||||||
bookTaskId: id
|
bookTaskId: id
|
||||||
})
|
})
|
||||||
bookTask = await this.bookTaskService.GetBookTaskDataById(id)
|
bookTask = await this.bookTaskService.GetBookTaskDataById(id, true)
|
||||||
// 判断是不是有为空的
|
// 判断是不是有为空的
|
||||||
let emptyName = [] as string[]
|
let emptyName = [] as string[]
|
||||||
for (let i = 0; i < bookTaskDetail.length; i++) {
|
for (let i = 0; i < bookTaskDetail.length; i++) {
|
||||||
@ -101,30 +102,28 @@ export class SDServiceHandle extends SDBasic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (emptyName.length > 0) {
|
if (emptyName.length > 0) {
|
||||||
throw new Error(`${emptyName.join(',')} 的提示词为空,请先推理`)
|
throw new Error(t("{emptyName} 的提示词为空,请先推理提示词", {
|
||||||
|
emptyName: emptyName.join(',')
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
|
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
|
||||||
let tempBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id)
|
let tempBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id, true)
|
||||||
if (tempBookTaskDetail == null) {
|
|
||||||
throw new Error('未找到对应的分镜')
|
|
||||||
}
|
|
||||||
if (isEmpty(tempBookTaskDetail.gptPrompt)) {
|
if (isEmpty(tempBookTaskDetail.gptPrompt)) {
|
||||||
throw new Error('当前分镜没有推理提示词,请先生成')
|
throw new Error(t("{emptyName} 的提示词为空,请先推理提示词", {
|
||||||
|
emptyName: tempBookTaskDetail.name
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
bookTaskDetail = [tempBookTaskDetail]
|
bookTaskDetail = [tempBookTaskDetail]
|
||||||
bookTask = await this.bookTaskService.GetBookTaskDataById(
|
bookTask = await this.bookTaskService.GetBookTaskDataById(
|
||||||
bookTaskDetail[0].bookTaskId as string
|
bookTaskDetail[0].bookTaskId as string, true
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('未知的合并类型')
|
throw new Error(t('未知的合并类型'))
|
||||||
}
|
|
||||||
let book = await this.bookService.GetBookDataById(bookTask.bookId as string)
|
|
||||||
if (book == null) {
|
|
||||||
throw new Error('未找到对应的小说')
|
|
||||||
}
|
}
|
||||||
|
let book = await this.bookService.GetBookDataById(bookTask.bookId as string, true)
|
||||||
|
|
||||||
// 获取SD的通用前缀
|
// 获取SD的通用前缀
|
||||||
|
|
||||||
let sdGlobalPrompt = this.sdImageSetting.positivePrompt
|
let sdGlobalPrompt = this.sdImageSetting.positivePrompt
|
||||||
let result: any[] = [] // 返回前端的数据数组
|
let result: any[] = [] // 返回前端的数据数组
|
||||||
for (let i = 0; i < bookTaskDetail.length; i++) {
|
for (let i = 0; i < bookTaskDetail.length; i++) {
|
||||||
@ -213,9 +212,11 @@ export class SDServiceHandle extends SDBasic {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return successMessage(result, 'SD和并提示词数据成功', 'SDOpt_MergePrompt')
|
return successMessage(result, t("SD合并提示词成功!"), 'SDOpt_MergePrompt')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage('SD合并提示词,错误信息如下:' + error.toString(), 'SDOpt_MergePrompt')
|
return errorMessage(t("SD合并提示词失败,{error}", {
|
||||||
|
error: (error as Error).message
|
||||||
|
}), 'SDOpt_MergePrompt')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,18 +257,14 @@ export class SDServiceHandle extends SDBasic {
|
|||||||
// 开始生图
|
// 开始生图
|
||||||
await this.GetSDImageSetting()
|
await this.GetSDImageSetting()
|
||||||
let bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(
|
let bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(
|
||||||
task.bookTaskDetailId as string
|
task.bookTaskDetailId as string, true
|
||||||
)
|
)
|
||||||
if (bookTaskDetail == null) {
|
|
||||||
throw new Error('未找到对应的分镜')
|
|
||||||
}
|
|
||||||
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
||||||
bookTaskDetail.bookTaskId as string
|
bookTaskDetail.bookTaskId as string, true
|
||||||
)
|
)
|
||||||
let book = await this.bookService.GetBookDataById(bookTask.bookId as string)
|
let book = await this.bookService.GetBookDataById(bookTask.bookId as string, true)
|
||||||
if (book == null) {
|
|
||||||
throw new Error('未找到对应的小说')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用方法合并提示词
|
// 调用方法合并提示词
|
||||||
let mergeRes = await this.MergeSDPrompt(
|
let mergeRes = await this.MergeSDPrompt(
|
||||||
@ -383,7 +380,7 @@ export class SDServiceHandle extends SDBasic {
|
|||||||
status: 'success',
|
status: 'success',
|
||||||
outImagePath: outImagePath + '?t=' + new Date().getTime(),
|
outImagePath: outImagePath + '?t=' + new Date().getTime(),
|
||||||
subImagePath: subImagePath.map((item) => item + '?t=' + new Date().getTime()),
|
subImagePath: subImagePath.map((item) => item + '?t=' + new Date().getTime()),
|
||||||
message: 'SD生成图片成功'
|
message: t('SD生成图片成功!')
|
||||||
} as MJ.MJResponseToFront
|
} as MJ.MJResponseToFront
|
||||||
this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(
|
this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(
|
||||||
task.bookTaskDetailId as string,
|
task.bookTaskDetailId as string,
|
||||||
@ -392,7 +389,7 @@ export class SDServiceHandle extends SDBasic {
|
|||||||
SendReturnMessage(
|
SendReturnMessage(
|
||||||
{
|
{
|
||||||
code: 1,
|
code: 1,
|
||||||
message: 'SD生成图片成功',
|
message: t('SD生成图片成功!'),
|
||||||
id: bookTaskDetail.id as string,
|
id: bookTaskDetail.id as string,
|
||||||
data: {
|
data: {
|
||||||
...resp
|
...resp
|
||||||
@ -400,9 +397,11 @@ export class SDServiceHandle extends SDBasic {
|
|||||||
},
|
},
|
||||||
task.messageName as string
|
task.messageName as string
|
||||||
)
|
)
|
||||||
return successMessage(resp, 'SD生成图片成功', 'SDServiceHandle_SDImageGenerate')
|
return successMessage(resp, t('SD生成图片成功!'), 'SDServiceHandle_SDImageGenerate')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
let errorMsg = 'SD生成图片失败,错误信息如下:' + error.toString()
|
let errorMsg = t("SD生成图片失败,{error}", {
|
||||||
|
error: (error as Error).message
|
||||||
|
})
|
||||||
this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId as string, {
|
this.bookTaskDetailService.UpdateBookTaskDetailMjMessage(task.bookTaskDetailId as string, {
|
||||||
mjApiUrl: sdSetting ? this.sdImageSetting.requestUrl : '',
|
mjApiUrl: sdSetting ? this.sdImageSetting.requestUrl : '',
|
||||||
progress: 0,
|
progress: 0,
|
||||||
|
|||||||
@ -7,12 +7,13 @@ import fs from 'fs'
|
|||||||
import { ValidateJson } from '@/define/Tools/validate'
|
import { ValidateJson } from '@/define/Tools/validate'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
import { ErrorItem, SuccessItem } from '@/define/model/generalResponse'
|
import { ErrorItem, SuccessItem } from '@/define/model/generalResponse'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class SettingService {
|
export class SettingService {
|
||||||
constructor() {}
|
constructor() { }
|
||||||
|
|
||||||
/** 初始化数据库服务 */
|
/** 初始化数据库服务 */
|
||||||
async InitService() {}
|
async InitService() { }
|
||||||
|
|
||||||
//#region 剪映设置
|
//#region 剪映设置
|
||||||
|
|
||||||
@ -34,12 +35,12 @@ export class SettingService {
|
|||||||
)
|
)
|
||||||
let rootMetaInfoPath = path.resolve(defaultJianyingDraftPath, 'root_meta_info.json')
|
let rootMetaInfoPath = path.resolve(defaultJianyingDraftPath, 'root_meta_info.json')
|
||||||
if (!(await CheckFileOrDirExist(rootMetaInfoPath))) {
|
if (!(await CheckFileOrDirExist(rootMetaInfoPath))) {
|
||||||
throw new Error('未找到剪映相关数据,请手动填写或选择')
|
throw new Error(t("未找到剪映相关数据,请手动填写或选择"))
|
||||||
}
|
}
|
||||||
// 读取文件内容,判断是否是剪映的草稿地址
|
// 读取文件内容,判断是否是剪映的草稿地址
|
||||||
let fileContent = await fs.promises.readFile(rootMetaInfoPath, 'utf-8')
|
let fileContent = await fs.promises.readFile(rootMetaInfoPath, 'utf-8')
|
||||||
if (!ValidateJson(fileContent)) {
|
if (!ValidateJson(fileContent)) {
|
||||||
throw new Error('剪映草稿地址数据错误,请手动填写或选择')
|
throw new Error(t('剪映草稿地址数据错误,请手动填写或选择'))
|
||||||
}
|
}
|
||||||
let jsonContent = JSON.parse(fileContent)
|
let jsonContent = JSON.parse(fileContent)
|
||||||
let all_draft_store = jsonContent.all_draft_store
|
let all_draft_store = jsonContent.all_draft_store
|
||||||
@ -49,18 +50,20 @@ export class SettingService {
|
|||||||
if (draft_root_path && !isEmpty(draft_root_path)) {
|
if (draft_root_path && !isEmpty(draft_root_path)) {
|
||||||
return successMessage(
|
return successMessage(
|
||||||
draft_root_path,
|
draft_root_path,
|
||||||
'成功',
|
t('成功'),
|
||||||
'SettingService_GetDefaultJianyingDraftPath'
|
'SettingService_GetDefaultJianyingDraftPath'
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('剪映草稿地址数据错误,请手动填写或选择')
|
throw new Error(t('剪映草稿地址数据错误,请手动填写或选择'))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('剪映草稿地址数据错误,请手动填写或选择')
|
throw new Error(t('剪映草稿地址数据错误,请手动填写或选择'))
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'获取默认剪映草稿地址失败, ' + error.message,
|
t("获取默认剪映草稿地址失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'SettingService_GetDefaultJianyingDraftPath'
|
'SettingService_GetDefaultJianyingDraftPath'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,24 @@
|
|||||||
import { dialog, nativeTheme, shell } from 'electron'
|
import { dialog, nativeTheme, shell } from 'electron'
|
||||||
import { CheckFileOrDirExist, CopyFileOrFolder } from '../../../define/Tools/file'
|
import { CheckFileOrDirExist, CopyFileOrFolder } from '../../../define/Tools/file'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import fs from 'fs/promises'
|
||||||
import { errorMessage, successMessage } from '../../../public/generalTools'
|
import { errorMessage, successMessage } from '../../../public/generalTools'
|
||||||
import { ErrorItem, SuccessItem } from '@/define/model/generalResponse'
|
import { ErrorItem, SuccessItem } from '@/define/model/generalResponse'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
/** 打开指定的文件夹的方法 */
|
/** 打开指定的文件夹的方法 */
|
||||||
export type OpenFolderParams = {
|
export type OpenFolderParams = {
|
||||||
/** 是不是基于项目文件,是的话,会在项目文件夹的基础上进行拼接 */
|
/** 是不是基于项目文件,是的话,会在项目文件夹的基础上进行拼接 */
|
||||||
baseProject: boolean
|
baseProject: boolean
|
||||||
/** 判断是不是打开父文件夹 */
|
/** 判断是不是打开父文件夹 */
|
||||||
dirFloder: boolean
|
dirFolder: boolean
|
||||||
/** 文件路径,baseProject 为false,需要设置完整的文件路径 */
|
/** 文件路径,baseProject 为false,需要设置完整的文件路径 */
|
||||||
folderPath: string
|
folderPath: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 一些对electron接口的封装,配合业务逻辑 */
|
/** 一些对electron接口的封装,配合业务逻辑 */
|
||||||
export default class ElectronInterface {
|
export default class ElectronInterface {
|
||||||
constructor() {}
|
constructor() { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开指定的文件,试用默认的打开方式
|
* 打开指定的文件,试用默认的打开方式
|
||||||
@ -25,10 +27,14 @@ export default class ElectronInterface {
|
|||||||
*/
|
*/
|
||||||
public async OpenFile(value: string): Promise<ErrorItem | SuccessItem> {
|
public async OpenFile(value: string): Promise<ErrorItem | SuccessItem> {
|
||||||
if (!(await CheckFileOrDirExist(value))) {
|
if (!(await CheckFileOrDirExist(value))) {
|
||||||
return errorMessage('文件/文件夹 不存在', 'SystemIpc_OPEN_FILE')
|
return errorMessage(t("目的文件/文件夹不存在,{data}", {
|
||||||
|
data: value
|
||||||
|
}), 'SystemIpc_OPEN_FILE')
|
||||||
}
|
}
|
||||||
await shell.openPath(value)
|
await shell.openPath(value)
|
||||||
return successMessage(null, '打开指定的文件成功', 'SystemIpc_OPEN_FILE')
|
return successMessage(null, t("打开文件/文件夹成功", {
|
||||||
|
data: value
|
||||||
|
}), 'SystemIpc_OPEN_FILE')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,10 +51,12 @@ export default class ElectronInterface {
|
|||||||
// 使用更完善的复制方法
|
// 使用更完善的复制方法
|
||||||
await CopyFileOrFolder(source, destination, false)
|
await CopyFileOrFolder(source, destination, false)
|
||||||
|
|
||||||
return successMessage(null, '复制文件夹内容成功', 'SystemIpc_COPY_FOLDER_CONTENTS')
|
return successMessage(null, t('复制文件夹成功'), 'SystemIpc_COPY_FOLDER_CONTENTS')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'复制文件夹内容错误,错误信息如下:' + error.message,
|
t("复制文件夹失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'SystemIpc_COPY_FOLDER_CONTENTS'
|
'SystemIpc_COPY_FOLDER_CONTENTS'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -65,7 +73,7 @@ export default class ElectronInterface {
|
|||||||
if (params.baseProject) {
|
if (params.baseProject) {
|
||||||
openFolder = path.join(global.config.project_path, params.folderPath)
|
openFolder = path.join(global.config.project_path, params.folderPath)
|
||||||
}
|
}
|
||||||
if (params.dirFloder) {
|
if (params.dirFolder) {
|
||||||
openFolder = path.dirname(params.folderPath)
|
openFolder = path.dirname(params.folderPath)
|
||||||
}
|
}
|
||||||
if (!openFolder) {
|
if (!openFolder) {
|
||||||
@ -74,12 +82,16 @@ export default class ElectronInterface {
|
|||||||
// 判断文件夹是不是存在
|
// 判断文件夹是不是存在
|
||||||
let isExist = await CheckFileOrDirExist(openFolder)
|
let isExist = await CheckFileOrDirExist(openFolder)
|
||||||
if (!isExist) {
|
if (!isExist) {
|
||||||
throw new Error('文件夹不存在,请检查')
|
throw new Error(t("目的文件/文件夹不存在,{data}", {
|
||||||
|
data: openFolder
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
shell.openPath(openFolder)
|
shell.openPath(openFolder)
|
||||||
return successMessage(null, '打开成功')
|
return successMessage(null, t('打开文件/文件夹成功'))
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage('打开文件夹错误,错误信息如下:' + error.message, 'SystemIpc_OPEN_FOLDER')
|
return errorMessage(t("打卡开文件/文件夹失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}), 'SystemIpc_OPEN_FOLDER')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,12 +106,14 @@ export default class ElectronInterface {
|
|||||||
filters: [{ name: 'fileName', extensions: value }]
|
filters: [{ name: 'fileName', extensions: value }]
|
||||||
})
|
})
|
||||||
if (filePaths.length === 0) {
|
if (filePaths.length === 0) {
|
||||||
throw new Error('没有选择的文件')
|
throw new Error(t('没有选择的文件或文件夹'))
|
||||||
}
|
}
|
||||||
return successMessage(filePaths[0], '选择文件成功', 'SystemIpc_SelectSingleFile')
|
return successMessage(filePaths[0], t("选择文件/文件夹成功"), 'SystemIpc_SelectSingleFile')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'选择文件错误,错误信息如下:' + error.message,
|
t("选择文件/文件夹失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'SystemIpc_SelectSingleFile'
|
'SystemIpc_SelectSingleFile'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -118,14 +132,16 @@ export default class ElectronInterface {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (filePaths.length === 0) {
|
if (filePaths.length === 0) {
|
||||||
throw new Error('没有选择的文件')
|
throw new Error(t('没有选择的文件或文件夹'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return successMessage(filePaths, '选择文件成功', 'SystemIpc_SelectMultipleFile')
|
return successMessage(filePaths, t("选择文件/文件夹成功"), 'SystemIpc_SelectMultipleFile')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('选择文件错误:', error) // 记录错误日志
|
console.error('选择文件错误:', error) // 记录错误日志
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'选择文件错误,错误信息如下:' + error.message,
|
t("选择文件/文件夹失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'SystemIpc_SelectMultipleFile'
|
'SystemIpc_SelectMultipleFile'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -156,18 +172,20 @@ export default class ElectronInterface {
|
|||||||
const { filePaths } = await dialog.showOpenDialog({
|
const { filePaths } = await dialog.showOpenDialog({
|
||||||
properties: ['openDirectory'],
|
properties: ['openDirectory'],
|
||||||
defaultPath: defaultPath,
|
defaultPath: defaultPath,
|
||||||
title: '选择文件夹',
|
title: t('选择文件夹'),
|
||||||
buttonLabel: '选择文件夹'
|
buttonLabel: t('选择文件夹')
|
||||||
})
|
})
|
||||||
|
|
||||||
if (filePaths.length === 0) {
|
if (filePaths.length === 0) {
|
||||||
throw new Error('没有选择任何文件夹')
|
throw new Error(t('没有选择的文件或文件夹'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return successMessage(filePaths[0], '选择文件夹成功', 'SystemIpc_SelectSingleFolder')
|
return successMessage(filePaths[0], t("选择文件/文件夹成功"), 'SystemIpc_SelectSingleFolder')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'选择文件夹错误,错误信息如下:' + error.message,
|
t("选择文件/文件夹失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'SystemIpc_SelectSingleFolder'
|
'SystemIpc_SelectSingleFolder'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -183,15 +201,15 @@ export default class ElectronInterface {
|
|||||||
// 使用消息框让用户选择类型
|
// 使用消息框让用户选择类型
|
||||||
const choice = await dialog.showMessageBox({
|
const choice = await dialog.showMessageBox({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
title: '选择类型',
|
title: t('选择类型'),
|
||||||
message: '请选择要选择的类型:',
|
message: t('请选择要选择的类型:'),
|
||||||
buttons: ['选择文件', '选择文件夹', '取消'],
|
buttons: [t('选择文件'), t('选择文件夹'), t('取消')],
|
||||||
defaultId: 0,
|
defaultId: 0,
|
||||||
cancelId: 2
|
cancelId: 2
|
||||||
})
|
})
|
||||||
|
|
||||||
if (choice.response === 2) {
|
if (choice.response === 2) {
|
||||||
throw new Error('用户取消选择')
|
throw new Error(t("取消操作"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (choice.response === 0) {
|
if (choice.response === 0) {
|
||||||
@ -205,31 +223,33 @@ export default class ElectronInterface {
|
|||||||
{ name: 'All Files', extensions: ['*'] }
|
{ name: 'All Files', extensions: ['*'] }
|
||||||
]
|
]
|
||||||
: [{ name: 'All Files', extensions: ['*'] }],
|
: [{ name: 'All Files', extensions: ['*'] }],
|
||||||
title: '选择文件'
|
title: t('选择文件')
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.filePaths.length === 0) {
|
if (result.filePaths.length === 0) {
|
||||||
throw new Error('没有选择文件')
|
throw new Error(t('没有选择的文件或文件夹'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return successMessage(result.filePaths[0], '选择文件成功', 'SystemIpc_SelectFolderOrFile')
|
return successMessage(result.filePaths[0], t("选择文件/文件夹成功"), 'SystemIpc_SelectFolderOrFile')
|
||||||
} else {
|
} else {
|
||||||
// 选择文件夹
|
// 选择文件夹
|
||||||
const result = await dialog.showOpenDialog({
|
const result = await dialog.showOpenDialog({
|
||||||
properties: ['openDirectory'],
|
properties: ['openDirectory'],
|
||||||
title: '选择文件夹'
|
title: t('选择文件夹')
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.filePaths.length === 0) {
|
if (result.filePaths.length === 0) {
|
||||||
throw new Error('没有选择文件夹')
|
throw new Error(t('没有选择的文件或文件夹'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return successMessage(result.filePaths[0], '选择文件夹成功', 'SystemIpc_SelectFolderOrFile')
|
return successMessage(result.filePaths[0], t("选择文件/文件夹成功"), 'SystemIpc_SelectFolderOrFile')
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('选择文件或文件夹错误:', error)
|
console.error('选择文件或文件夹错误:', error)
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'选择文件或文件夹错误,错误信息如下:' + error.message,
|
t("选择文件/文件夹失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'SystemIpc_SelectFolderOrFile'
|
'SystemIpc_SelectFolderOrFile'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -242,4 +262,60 @@ export default class ElectronInterface {
|
|||||||
public OpenUrl(url: string) {
|
public OpenUrl(url: string) {
|
||||||
shell.openExternal(url)
|
shell.openExternal(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取文件内容(仅支持常见的文本格式)
|
||||||
|
* @param filePath 文件路径
|
||||||
|
* @returns 返回文件内容或错误信息
|
||||||
|
*/
|
||||||
|
public async ReadTextFile(filePath: string): Promise<SuccessItem | ErrorItem> {
|
||||||
|
try {
|
||||||
|
// 定义支持的文本文件格式
|
||||||
|
const supportedExtensions = [
|
||||||
|
'.txt', '.json', '.xml', '.html', '.htm', '.css', '.js', '.ts',
|
||||||
|
'.jsx', '.tsx', '.vue', '.md', '.yml', '.yaml', '.csv', '.log',
|
||||||
|
'.ini', '.conf', '.config', '.py', '.java', '.c', '.cpp', '.h',
|
||||||
|
'.php', '.rb', '.go', '.rs', '.swift', '.kt', '.scala', '.sql',
|
||||||
|
'.sh', '.bat', '.ps1', '.dockerfile', '.gitignore', '.env'
|
||||||
|
]
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
if (!(await CheckFileOrDirExist(filePath))) {
|
||||||
|
throw new Error(t('文件不存在'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件扩展名
|
||||||
|
const ext = path.extname(filePath).toLowerCase()
|
||||||
|
|
||||||
|
// 检查文件格式是否支持
|
||||||
|
if (!supportedExtensions.includes(ext)) {
|
||||||
|
throw new Error(t("不支持的文件格式: {ext}。支持的格式: {supportedExt}", {
|
||||||
|
ext: ext,
|
||||||
|
supportedExt: supportedExtensions.join(', ')
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取文件内容
|
||||||
|
const content = await fs.readFile(filePath, 'utf-8')
|
||||||
|
|
||||||
|
return successMessage(
|
||||||
|
{
|
||||||
|
content: content,
|
||||||
|
filePath: filePath,
|
||||||
|
size: Buffer.byteLength(content, 'utf-8'),
|
||||||
|
extension: ext
|
||||||
|
},
|
||||||
|
t('读取文件成功!'),
|
||||||
|
'SystemIpc_ReadTextFile'
|
||||||
|
)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('读取文件错误:', error)
|
||||||
|
return errorMessage(
|
||||||
|
t("读取文件失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
|
'SystemIpc_ReadTextFile'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
|
import { t } from '@/i18n'
|
||||||
import { errorMessage } from '@/public/generalTools'
|
import { errorMessage } from '@/public/generalTools'
|
||||||
|
|
||||||
export class UserSoftware {
|
export class UserSoftware {
|
||||||
constructor() {}
|
constructor() { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步授权信息
|
* 同步授权信息
|
||||||
@ -19,7 +20,7 @@ export class UserSoftware {
|
|||||||
}
|
}
|
||||||
console.log('授权信息', global.am)
|
console.log('授权信息', global.am)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage('同步授权信息失败', 'SystemIpc_SyncAuthorization')
|
errorMessage(t("同步授权信息失败"), 'SystemIpc_SyncAuthorization')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum'
|
import { BookBackTaskStatus } from '@/define/enum/bookEnum'
|
||||||
import { TaskServiceHandle } from './taskServiceHandle'
|
import { TaskServiceHandle } from './taskServiceHandle'
|
||||||
import { TaskModal } from '@/define/model/task'
|
import { TaskModal } from '@/define/model/task'
|
||||||
import { Book } from '@/define/model/book/book'
|
import { Book } from '@/define/model/book/book'
|
||||||
@ -13,22 +13,7 @@ export class TaskHandle {
|
|||||||
await this.taskServiceHandle.StartTaskQueue(isGiveUp)
|
await this.taskServiceHandle.StartTaskQueue(isGiveUp)
|
||||||
|
|
||||||
/** 添加单个个任务 */
|
/** 添加单个个任务 */
|
||||||
AddOneTask = async (
|
AddOneTask = async (task: TaskModal.Task) => await this.taskServiceHandle.AddOneTask(task)
|
||||||
bookId: string,
|
|
||||||
taskType: BookBackTaskType,
|
|
||||||
executeType: TaskExecuteType = TaskExecuteType.AUTO,
|
|
||||||
bookTaskId: string | undefined = undefined,
|
|
||||||
bookTaskDetailId: string | undefined = undefined,
|
|
||||||
responseMessageName?: string
|
|
||||||
) =>
|
|
||||||
await this.taskServiceHandle.AddOneTask(
|
|
||||||
bookId,
|
|
||||||
taskType,
|
|
||||||
executeType,
|
|
||||||
bookTaskId,
|
|
||||||
bookTaskDetailId,
|
|
||||||
responseMessageName
|
|
||||||
)
|
|
||||||
|
|
||||||
/** 添加多个任务 */
|
/** 添加多个任务 */
|
||||||
AddMultiTask = async (tasks: TaskModal.Task[]) => await this.taskServiceHandle.AddMultiTask(tasks)
|
AddMultiTask = async (tasks: TaskModal.Task[]) => await this.taskServiceHandle.AddMultiTask(tasks)
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import { SDHandle } from '../sd'
|
|||||||
import { OptionRealmService } from '@/define/db/service/optionService'
|
import { OptionRealmService } from '@/define/db/service/optionService'
|
||||||
import { OptionKeyName } from '@/define/enum/option'
|
import { OptionKeyName } from '@/define/enum/option'
|
||||||
import { optionSerialization } from '../option/optionSerialization'
|
import { optionSerialization } from '../option/optionSerialization'
|
||||||
|
import { bookHandle } from '../book'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class TaskManager {
|
export class TaskManager {
|
||||||
isExecuting: boolean = false
|
isExecuting: boolean = false
|
||||||
@ -352,19 +354,18 @@ export class TaskManager {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
/** 添加图片转视频后台人物 */
|
/** 添加图片转视频后台人物 */
|
||||||
// async AddImageToVideo(task: TaskModal.Task) {
|
async AddImageToVideo(task: TaskModal.Task) {
|
||||||
// let batch = task.messageName
|
let batch = task.messageName
|
||||||
// global.taskQueue.enqueue(
|
global.taskQueue.enqueue(
|
||||||
// async () => {
|
async () => {
|
||||||
// this.videoGlobal = new VideoGlobal()
|
await bookHandle.MediaToVideo(task)
|
||||||
// await this.videoGlobal.ImageToVideo(task)
|
},
|
||||||
// },
|
`${batch}_${task.id}`,
|
||||||
// `${batch}_${task.id}`,
|
batch,
|
||||||
// batch,
|
`${batch}_${task.id}_${new Date().getTime()}`,
|
||||||
// `${batch}_${task.id}_${new Date().getTime()}`,
|
this.taskListService.SetMessageNameTaskToFail
|
||||||
// this.taskListService.SetMessageNameTaskToFail
|
)
|
||||||
// )
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * 添加 FLUX api 到内存队列中
|
// * 添加 FLUX api 到内存队列中
|
||||||
@ -436,11 +437,13 @@ export class TaskManager {
|
|||||||
// case BookBackTaskType.RUNWAY_VIDEO:
|
// case BookBackTaskType.RUNWAY_VIDEO:
|
||||||
// case BookBackTaskType.LUMA_VIDEO:
|
// case BookBackTaskType.LUMA_VIDEO:
|
||||||
// case BookBackTaskType.KLING_VIDEO:
|
// case BookBackTaskType.KLING_VIDEO:
|
||||||
// this.AddImageToVideo(task)
|
case BookBackTaskType.MJ_VIDEO:
|
||||||
// break
|
case BookBackTaskType.MJ_VIDEO_EXTEND:
|
||||||
|
this.AddImageToVideo(task)
|
||||||
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error('未知的任务类型')
|
throw new Error(t('未知的任务类型'))
|
||||||
}
|
}
|
||||||
// 是不是要添加自动任务
|
// 是不是要添加自动任务
|
||||||
// await this.AddTaskHandle(task, true);
|
// await this.AddTaskHandle(task, true);
|
||||||
@ -452,7 +455,10 @@ export class TaskManager {
|
|||||||
})
|
})
|
||||||
return successMessage(
|
return successMessage(
|
||||||
updateRes,
|
updateRes,
|
||||||
`${task.name}_${task.id} 任务添加调度完成`,
|
t("{taskName}_{taskId} 任务添加调度完成", {
|
||||||
|
taskName: task.name,
|
||||||
|
taskId: task.id
|
||||||
|
}),
|
||||||
'TaskManager_AddQueue'
|
'TaskManager_AddQueue'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -460,11 +466,15 @@ export class TaskManager {
|
|||||||
this.taskListService.UpdateTaskStatus({
|
this.taskListService.UpdateTaskStatus({
|
||||||
id: task.id as string,
|
id: task.id as string,
|
||||||
status: BookBackTaskStatus.FAIL,
|
status: BookBackTaskStatus.FAIL,
|
||||||
errorMessage: '任务调度失败,请手动重试'
|
errorMessage: t('任务调度失败,请手动重试')
|
||||||
})
|
})
|
||||||
|
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`处理 ${task.type} 类型任务 ${task.name} 失败,失败信息如下:${error.message}`,
|
t("处理 {taskType} 类型任务 {taskName} 失败,失败信息如下,{error}", {
|
||||||
|
taskType: task.type,
|
||||||
|
taskName: task.name,
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'TaskManager_handleTask'
|
'TaskManager_handleTask'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
import { TaskListService } from '@/define/db/service/book/taskListService'
|
import { TaskListService } from '@/define/db/service/book/taskListService'
|
||||||
import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum'
|
import { BookBackTaskStatus, BookBackTaskType } from '@/define/enum/bookEnum'
|
||||||
import { GeneralResponse } from '@/define/model/generalResponse'
|
import { GeneralResponse } from '@/define/model/generalResponse'
|
||||||
import { errorMessage, successMessage } from '@/public/generalTools'
|
import { errorMessage, successMessage } from '@/public/generalTools'
|
||||||
import { TaskManager } from './taskManage'
|
import { TaskManager } from './taskManage'
|
||||||
import { TaskModal } from '@/define/model/task'
|
import { TaskModal } from '@/define/model/task'
|
||||||
import { Book } from '@/define/model/book/book'
|
import { Book } from '@/define/model/book/book'
|
||||||
|
import { t } from '@/i18n'
|
||||||
export class TaskServiceHandle {
|
export class TaskServiceHandle {
|
||||||
taskListService!: TaskListService
|
taskListService!: TaskListService
|
||||||
|
|
||||||
constructor() {}
|
constructor() { }
|
||||||
|
|
||||||
private async InitTaskServiceHandle() {
|
private async InitTaskServiceHandle() {
|
||||||
if (!this.taskListService) {
|
if (!this.taskListService) {
|
||||||
@ -55,61 +56,64 @@ export class TaskServiceHandle {
|
|||||||
await global.taskManager.InitListeners() // 启动监听
|
await global.taskManager.InitListeners() // 启动监听
|
||||||
// 重新设置
|
// 重新设置
|
||||||
}
|
}
|
||||||
return successMessage(null, '启动后台任务成功', 'BackTaskService_StartBackTask')
|
return successMessage(null, t('启动后台任务成功'), 'BackTaskService_StartBackTask')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage('启动任务队列失败,' + error.message, 'TaskServiceHandle.StartTaskQueue')
|
return errorMessage(t("启动后台任务失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}), 'TaskServiceHandle.StartTaskQueue')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加书籍后台任务
|
* 添加单个后台任务
|
||||||
*
|
*
|
||||||
* 该方法向系统添加一个新的小说相关的后台任务。后台任务可用于执行各种与书籍处理相关的操作,
|
* 此方法用于向任务队列中添加一个新的后台任务。后台任务可以与特定的书籍、书籍任务或分镜关联,
|
||||||
* 如生成图像、翻译内容、合并提示词等。任务可以自动执行或手动触发,并且可以与特定的书籍任务
|
* 并支持自动或手动执行。任务类型和执行方式由调用方指定。
|
||||||
* 或分镜关联。
|
|
||||||
*
|
*
|
||||||
* @param {string} bookId - 书籍ID,指定任务关联的书籍
|
* @param {TaskModal.Task} task - 任务对象,包含任务的详细信息:
|
||||||
* @param {BookBackTaskType} taskType - 任务类型,如图像生成、翻译等
|
* - bookId: 书籍ID,任务关联的书籍
|
||||||
* @param {TaskExecuteType} [executeType=TaskExecuteType.AUTO] - 任务执行类型,默认为自动执行
|
* - type: 任务类型(如图像生成、翻译等)
|
||||||
* @param {string | null} [bookTaskId=null] - 关联的书籍任务ID,可选
|
* - executeType: 执行类型(自动或手动)
|
||||||
* @param {string | null} [bookTaskDetailId=null] - 关联的分镜ID,可选
|
* - bookTaskId: 关联的书籍任务ID(可选)
|
||||||
* @param {string} [responseMessageName] - 任务完成后通知的消息通道名称,可选
|
* - bookTaskDetailId: 关联的分镜ID(可选)
|
||||||
|
* - messageName: 任务完成后通知的消息通道名称(可选)
|
||||||
*
|
*
|
||||||
* @returns {Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem>}
|
* @returns {Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem>}
|
||||||
* 返回操作结果,成功则包含任务ID,失败则包含错误信息
|
* 返回操作结果:
|
||||||
|
* - 成功:包含任务ID和成功消息
|
||||||
|
* - 失败:包含错误信息
|
||||||
|
*
|
||||||
|
* @throws {Error} 如果任务添加失败,将捕获异常并返回标准化的错误响应。
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* // 添加一个自动执行的MJ图像生成任务
|
* // 添加一个自动执行的图像生成任务
|
||||||
* const result = await taskService.AddBookBackTask(
|
* const result = await taskService.AddOneTask({
|
||||||
* "book-123",
|
* bookId: "book-123",
|
||||||
* BookBackTaskType.MJ_GENERATE_IMAGE,
|
* type: BookBackTaskType.MJ_GENERATE_IMAGE,
|
||||||
* TaskExecuteType.AUTO,
|
* executeType: TaskExecuteType.AUTO,
|
||||||
* "booktask-456",
|
* bookTaskId: "task-456",
|
||||||
* "detail-789",
|
* bookTaskDetailId: "detail-789",
|
||||||
* "mj-channel"
|
* messageName: "task-channel"
|
||||||
* );
|
* });
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async AddOneTask(
|
async AddOneTask(
|
||||||
bookId: string,
|
task: TaskModal.Task
|
||||||
taskType: BookBackTaskType,
|
|
||||||
executeType: TaskExecuteType = TaskExecuteType.AUTO,
|
|
||||||
bookTaskId: string | undefined = undefined,
|
|
||||||
bookTaskDetailId: string | undefined = undefined,
|
|
||||||
responseMessageName?: string
|
|
||||||
): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
|
||||||
try {
|
try {
|
||||||
await this.InitTaskServiceHandle()
|
await this.InitTaskServiceHandle()
|
||||||
let res = this.taskListService.AddOneTask(
|
let res = this.taskListService.AddOneTask(
|
||||||
bookId,
|
task.bookId as string,
|
||||||
taskType,
|
task.type as BookBackTaskType,
|
||||||
executeType,
|
task.executeType,
|
||||||
bookTaskId,
|
task.bookTaskId,
|
||||||
bookTaskDetailId,
|
task.bookTaskDetailId,
|
||||||
responseMessageName
|
task.messageName
|
||||||
)
|
)
|
||||||
return successMessage(res, '添加后台任务成功', 'TaskServiceHandle.AddBookBackTask')
|
return successMessage(res, t('添加后台任务成功'), 'TaskServiceHandle.AddBookBackTask')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage('添加后台任务失败,' + error.message, 'TaskServiceHandle.AddBookBackTask')
|
return errorMessage(t("添加后台任务失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}), 'TaskServiceHandle.AddBookBackTask')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,10 +166,12 @@ export class TaskServiceHandle {
|
|||||||
element.messageName
|
element.messageName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return successMessage(null, `添加多个任务成功`, 'TaskIpc_AddMultiBookBackTask')
|
return successMessage(null, t(`添加多个任务成功`), 'TaskIpc_AddMultiBookBackTask')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'添加多个后台任务失败,' + error.message,
|
t("添加多个任务失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'TaskServiceHandle.AddMultiTask'
|
'TaskServiceHandle.AddMultiTask'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -203,12 +209,14 @@ export class TaskServiceHandle {
|
|||||||
let res = this.taskListService.GetAssignStatusTaskCount(status)
|
let res = this.taskListService.GetAssignStatusTaskCount(status)
|
||||||
return successMessage(
|
return successMessage(
|
||||||
res,
|
res,
|
||||||
'获取指定状态的任务成功',
|
t('获取指定状态的任务成功'),
|
||||||
'TaskServiceHandle.GetAssignStatusTaskCount'
|
'TaskServiceHandle.GetAssignStatusTaskCount'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'获取指定状态的任务失败,' + error.message,
|
t("获取指定状态的任务失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'TaskServiceHandle.GetAssignStatusTaskCount'
|
'TaskServiceHandle.GetAssignStatusTaskCount'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -244,10 +252,12 @@ export class TaskServiceHandle {
|
|||||||
try {
|
try {
|
||||||
await this.InitTaskServiceHandle()
|
await this.InitTaskServiceHandle()
|
||||||
let res = this.taskListService.GetTaskCollection(queryTaskCondition)
|
let res = this.taskListService.GetTaskCollection(queryTaskCondition)
|
||||||
return successMessage(res, '获取后台任务集合成功', 'TaskServiceHandle.GetTaskCollection')
|
return successMessage(res, t('获取后台任务集合成功'), 'TaskServiceHandle.GetTaskCollection')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'获取后台任务集合失败,' + error.message,
|
t("获取后台任务集合失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'TaskServiceHandle.GetTaskCollection'
|
'TaskServiceHandle.GetTaskCollection'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -278,10 +288,12 @@ export class TaskServiceHandle {
|
|||||||
try {
|
try {
|
||||||
await this.InitTaskServiceHandle()
|
await this.InitTaskServiceHandle()
|
||||||
let res = this.taskListService.UpdateTaskStatus(bookBackTask)
|
let res = this.taskListService.UpdateTaskStatus(bookBackTask)
|
||||||
return successMessage(res, '更新后台任务状态成功', 'TaskServiceHandle.UpdateTaskStatus')
|
return successMessage(res, t('更新后台任务状态成功'), 'TaskServiceHandle.UpdateTaskStatus')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'更新后台任务状态失败,' + error.message,
|
t("修改后台任务失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'TaskServiceHandle.UpdateTaskStatus'
|
'TaskServiceHandle.UpdateTaskStatus'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { optionSerialization } from '../option/optionSerialization'
|
|||||||
import { SettingModal } from '@/define/model/setting'
|
import { SettingModal } from '@/define/model/setting'
|
||||||
import { GetApiDefineDataById } from '@/define/data/apiData'
|
import { GetApiDefineDataById } from '@/define/data/apiData'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class TranslateCommon {
|
export class TranslateCommon {
|
||||||
/** 请求的地址 */
|
/** 请求的地址 */
|
||||||
@ -28,7 +29,7 @@ export class TranslateCommon {
|
|||||||
|
|
||||||
optionRealmService!: OptionRealmService
|
optionRealmService!: OptionRealmService
|
||||||
|
|
||||||
constructor() {}
|
constructor() { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化翻译设置
|
* 初始化翻译设置
|
||||||
@ -104,7 +105,7 @@ export class TranslateCommon {
|
|||||||
let from = value.from
|
let from = value.from
|
||||||
let to = value.to
|
let to = value.to
|
||||||
if (value.isSplit) {
|
if (value.isSplit) {
|
||||||
throw new Error('使用GPT翻译不支持拆分')
|
throw new Error(t('使用GPT翻译不支持拆分'))
|
||||||
}
|
}
|
||||||
let model = this.translationAppId
|
let model = this.translationAppId
|
||||||
let token = this.translationSecret
|
let token = this.translationSecret
|
||||||
@ -189,7 +190,7 @@ export class TranslateCommon {
|
|||||||
"In a modern city, a streamlined car is parked on the street. A man in his thirties, with short brown hair combed back, a calm, confident look, tall and thin in a clean white shirt and black pants, sits in the car. The interior of the car is clean and modern, and the background is blurred to highlight the man's calm demeanor. The man's cell phone is ringing. The scene is set in the present."
|
"In a modern city, a streamlined car is parked on the street. A man in his thirties, with short brown hair combed back, a calm, confident look, tall and thin in a clean white shirt and black pants, sits in the car. The interior of the car is clean and modern, and the background is blurred to highlight the man's calm demeanor. The man's cell phone is ringing. The scene is set in the present."
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
throw new Error('GPT翻译只支持中英互译')
|
throw new Error(t('GPT翻译只支持中英互译'))
|
||||||
}
|
}
|
||||||
|
|
||||||
data.messages.push({
|
data.messages.push({
|
||||||
@ -228,7 +229,11 @@ export class TranslateCommon {
|
|||||||
to: to,
|
to: to,
|
||||||
data: res_data
|
data: res_data
|
||||||
},
|
},
|
||||||
`GPT${from == 'en' ? '英' : '中'}译${from == 'en' ? '英' : '中'}翻译成功`,
|
t("AI翻译 {source} 译 {target} 成功", {
|
||||||
|
source: from == 'en' ? '英' : '中',
|
||||||
|
target: to == 'en' ? '英' : '中'
|
||||||
|
})
|
||||||
|
,
|
||||||
'Translate_TranslateReturnNowGPT'
|
'Translate_TranslateReturnNowGPT'
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { OptionRealmService } from '@/define/db/service/optionService'
|
|||||||
import { OptionKeyName } from '@/define/enum/option'
|
import { OptionKeyName } from '@/define/enum/option'
|
||||||
import { optionSerialization } from '../option/optionSerialization'
|
import { optionSerialization } from '../option/optionSerialization'
|
||||||
import { SettingModal } from '@/define/model/setting'
|
import { SettingModal } from '@/define/model/setting'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 翻译服务处理类
|
* 翻译服务处理类
|
||||||
@ -123,7 +124,7 @@ export class TranslateServiceHandle {
|
|||||||
switch (value.type) {
|
switch (value.type) {
|
||||||
case TranslateType.REVERSE_PROMPT_TRANSLATE:
|
case TranslateType.REVERSE_PROMPT_TRANSLATE:
|
||||||
if (value.reversePromptId == null || isEmpty(value.reversePromptId)) {
|
if (value.reversePromptId == null || isEmpty(value.reversePromptId)) {
|
||||||
throw new Error('反推提示词的ID不能为空')
|
throw new Error(t('反推提示词的ID不能为空'))
|
||||||
}
|
}
|
||||||
await this.TranslateProcessReversePrompt(
|
await this.TranslateProcessReversePrompt(
|
||||||
value.bookTaskDetailId as string,
|
value.bookTaskDetailId as string,
|
||||||
@ -142,7 +143,7 @@ export class TranslateServiceHandle {
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error('未知的翻译类型')
|
throw new Error(t('未知的翻译类型'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +169,7 @@ export class TranslateServiceHandle {
|
|||||||
)
|
)
|
||||||
let generalSetting = optionSerialization<SettingModal.GeneralSettings>(
|
let generalSetting = optionSerialization<SettingModal.GeneralSettings>(
|
||||||
generalSettingOption,
|
generalSettingOption,
|
||||||
'‘设置 -> 通用设置’'
|
t('设置 -> 通用设置')
|
||||||
)
|
)
|
||||||
|
|
||||||
for (let i = 0; i < value.length; i++) {
|
for (let i = 0; i < value.length; i++) {
|
||||||
@ -182,7 +183,7 @@ export class TranslateServiceHandle {
|
|||||||
let srcString = ''
|
let srcString = ''
|
||||||
if (element.isSplit) {
|
if (element.isSplit) {
|
||||||
// let dstStrs = []
|
// let dstStrs = []
|
||||||
throw new Error('拆分翻译的暂时不支持')
|
throw new Error(t('使用GPT翻译不支持拆分'))
|
||||||
} else {
|
} else {
|
||||||
// 没有拆分的,只有一句
|
// 没有拆分的,只有一句
|
||||||
srcString = data.data[0].dst
|
srcString = data.data[0].dst
|
||||||
@ -217,10 +218,12 @@ export class TranslateServiceHandle {
|
|||||||
}
|
}
|
||||||
await ExecuteConcurrently(tasks, global.am.isPro ? (generalSetting.concurrency ?? 1) : 1)
|
await ExecuteConcurrently(tasks, global.am.isPro ? (generalSetting.concurrency ?? 1) : 1)
|
||||||
// 将翻译后的数据返回,前端进行修改
|
// 将翻译后的数据返回,前端进行修改
|
||||||
return successMessage(null, '全部翻译完成', 'TranslateService_TranslateNowReturn')
|
return successMessage(null, t('全部翻译完成'), 'TranslateService_TranslateNowReturn')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'翻译失败,失败信息如下:' + error.toString(),
|
t("翻译失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'TranslateService_TranslateNowReturn'
|
'TranslateService_TranslateNowReturn'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/main/service/video/index.ts
Normal file
15
src/main/service/video/index.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { TaskModal } from '@/define/model/task'
|
||||||
|
import { MJVideoService } from './mjVideo'
|
||||||
|
export class VideoHandle {
|
||||||
|
mjVideoService: MJVideoService
|
||||||
|
// 这里可以添加 VideoHandle 特有的方法
|
||||||
|
constructor() {
|
||||||
|
// mixin 装饰器会处理初始化
|
||||||
|
this.mjVideoService = new MJVideoService()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** MJ图片转视频处理方法 将指定的图片通过Midjourney API转换为视频 */
|
||||||
|
MJImageToVideo(task: TaskModal.Task) {
|
||||||
|
return this.mjVideoService.MJImageToVideo(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/main/service/video/lumaVideo.ts
Normal file
30
src/main/service/video/lumaVideo.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { TaskModal } from '@/define/model/task'
|
||||||
|
import { MJBasic } from '../mj/mjBasic'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Luma 视频服务类
|
||||||
|
* 处理 Luma AI 相关的视频生成功能
|
||||||
|
*/
|
||||||
|
export class LumaVideoService extends MJBasic {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Luma图片转视频处理方法
|
||||||
|
* @param task 任务对象
|
||||||
|
*/
|
||||||
|
async LumaImageToVideo(task: TaskModal.Task) {
|
||||||
|
// TODO: 实现 Luma 图片转视频功能
|
||||||
|
console.log('LumaImageToVideo called with task:', task.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Luma文本转视频处理方法
|
||||||
|
* @param task 任务对象
|
||||||
|
*/
|
||||||
|
async LumaTextToVideo(task: TaskModal.Task) {
|
||||||
|
// TODO: 实现 Luma 文本转视频功能
|
||||||
|
console.log('LumaTextToVideo called with task:', task.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
442
src/main/service/video/mjVideo.ts
Normal file
442
src/main/service/video/mjVideo.ts
Normal file
@ -0,0 +1,442 @@
|
|||||||
|
import { TaskModal } from '@/define/model/task'
|
||||||
|
|
||||||
|
import { MJApiService } from '../mj/mjApiService'
|
||||||
|
import { ImageGenerateMode } from '@/define/data/mjData'
|
||||||
|
import { cloneDeep, isEmpty } from 'lodash'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
import { ValidateJson } from '@/define/Tools/validate'
|
||||||
|
import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
||||||
|
import {
|
||||||
|
MappingTaskTypeToVideoModel,
|
||||||
|
MJVideoBatchSize,
|
||||||
|
MJVideoMotion,
|
||||||
|
MJVideoType,
|
||||||
|
VideoStatus
|
||||||
|
} from '@/define/enum/video'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { SendReturnMessage, successMessage } from '@/public/generalTools'
|
||||||
|
import { ResponseMessageType } from '@/define/enum/softwareEnum'
|
||||||
|
import { Book } from '@/define/model/book/book'
|
||||||
|
import { BookBackTaskStatus, BookTaskStatus } from '@/define/enum/bookEnum'
|
||||||
|
import path from 'path'
|
||||||
|
import { CheckFolderExistsOrCreate, CopyFileOrFolder } from '@/define/Tools/file'
|
||||||
|
import { DownloadFile } from '@/define/Tools/common'
|
||||||
|
import { getProjectPath } from '../option/optionCommonService'
|
||||||
|
|
||||||
|
export class MJVideoService extends MJApiService {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region MJImageToVideo
|
||||||
|
/**
|
||||||
|
* MJ图片转视频处理方法
|
||||||
|
* 将指定的图片通过Midjourney API转换为视频
|
||||||
|
* @param task 任务对象,包含小说任务详情ID等信息
|
||||||
|
* @returns Promise<void>
|
||||||
|
* @throws 当初始化失败、参数错误或API调用失败时抛出异常
|
||||||
|
*/
|
||||||
|
async MJImageToVideo(task: TaskModal.Task) {
|
||||||
|
try {
|
||||||
|
await this.InitMJBasic()
|
||||||
|
|
||||||
|
// 加载设置
|
||||||
|
await this.InitMJSetting(ImageGenerateMode.MJ_API)
|
||||||
|
|
||||||
|
// 检查是否支持视频功能
|
||||||
|
if (this.videoUrl == null || isEmpty(this.videoUrl)) {
|
||||||
|
throw new Error(t('当前Midjourney模式不支持视频生成功能,请更换为MJ API或本地代理模式后重试!'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 token
|
||||||
|
if (this.token == null || isEmpty(this.token)) {
|
||||||
|
throw new Error(
|
||||||
|
t("对应Midjourney模式的Token不能为空,请前往 {settingPath} 中配置", { settingPath: t("设置 -> MJ设置") })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始处理小说数据
|
||||||
|
let bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(
|
||||||
|
task.bookTaskDetailId as string, true
|
||||||
|
)
|
||||||
|
|
||||||
|
// 获取视频配置信息
|
||||||
|
let videoMessage = bookTaskDetail.videoMessage
|
||||||
|
if (videoMessage == null || videoMessage == undefined) {
|
||||||
|
throw new Error(t("小说批次任务的分镜数据的转视频配置为空,请检查"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 MJ Video 的options
|
||||||
|
let mjVideoOptionsString = bookTaskDetail.videoMessage?.mjVideoOptions as string
|
||||||
|
if (!ValidateJson(mjVideoOptionsString)) {
|
||||||
|
throw new Error(t("当前分镜数据的MJ图转视频参数为空或参数校验失败,请检查"))
|
||||||
|
}
|
||||||
|
let mjVideoOptions: BookTaskDetail.MjVideoOptions = JSON.parse(mjVideoOptionsString)
|
||||||
|
|
||||||
|
let imageUrl = videoMessage.imageUrl?.trim() || mjVideoOptions.image?.trim() || ''
|
||||||
|
let prompt = videoMessage.prompt?.trim()
|
||||||
|
let motion: MJVideoMotion =
|
||||||
|
mjVideoOptions.motion === MJVideoMotion.High ? MJVideoMotion.High : MJVideoMotion.Low
|
||||||
|
|
||||||
|
let videoType = mjVideoOptions.videoType ?? MJVideoType.HD
|
||||||
|
|
||||||
|
let batchSize = mjVideoOptions.batchSize ?? MJVideoBatchSize.FOUR
|
||||||
|
|
||||||
|
let endImageUrl = mjVideoOptions.endImageUrl?.trim() || ''
|
||||||
|
let loop = mjVideoOptions.loop ?? false
|
||||||
|
|
||||||
|
let raw = mjVideoOptions.raw ?? false
|
||||||
|
|
||||||
|
// 判断 图片是不是网络图片,不是网络图片的话判断当前图片再本地是不是存在,存在的话讲图片转为 base64
|
||||||
|
if (
|
||||||
|
!imageUrl.startsWith('http') ||
|
||||||
|
(!isEmpty(endImageUrl) && !endImageUrl.startsWith('http'))
|
||||||
|
) {
|
||||||
|
throw new Error(t("不支持的图片链接,仅支持网络图片链接!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在提示词后面添加 --raw
|
||||||
|
if (!isEmpty(prompt) && raw) {
|
||||||
|
prompt = prompt + ' --raw'
|
||||||
|
}
|
||||||
|
prompt = imageUrl + ' ' + prompt
|
||||||
|
|
||||||
|
// 添加 批次信息
|
||||||
|
prompt = prompt + ' --bs ' + batchSize
|
||||||
|
|
||||||
|
if (loop) {
|
||||||
|
prompt = prompt + ' --end loop'
|
||||||
|
} else {
|
||||||
|
if (!isEmpty(endImageUrl)) {
|
||||||
|
prompt = prompt + ' --end ' + endImageUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: {
|
||||||
|
prompt: string
|
||||||
|
motion?: MJVideoMotion
|
||||||
|
videoType: MJVideoType
|
||||||
|
} = {
|
||||||
|
prompt: prompt,
|
||||||
|
motion: motion,
|
||||||
|
videoType: videoType
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始请求
|
||||||
|
let res = await axios.post(this.videoUrl, body, {
|
||||||
|
headers: {
|
||||||
|
Authorization: this.token
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('MJImageToVideo response', res.data)
|
||||||
|
|
||||||
|
let resData = res.data
|
||||||
|
let id = resData.result
|
||||||
|
|
||||||
|
// 修改Task, 将数据写入
|
||||||
|
this.taskListService.UpdateBackTaskData(task.id as string, {
|
||||||
|
taskId: id as string,
|
||||||
|
taskMessage: JSON.stringify(resData)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 修改videoMessage
|
||||||
|
videoMessage.taskId = id
|
||||||
|
videoMessage.status = VideoStatus.WAIT
|
||||||
|
videoMessage.messageData = JSON.stringify(resData)
|
||||||
|
videoMessage.msg = ''
|
||||||
|
delete videoMessage.imageUrl // 不要修改原本的图片地址
|
||||||
|
|
||||||
|
this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage(
|
||||||
|
task.bookTaskDetailId as string,
|
||||||
|
videoMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
// 添加任务成功 返回前端任务事件
|
||||||
|
SendReturnMessage(
|
||||||
|
{
|
||||||
|
code: 1,
|
||||||
|
id: task.bookTaskDetailId as string,
|
||||||
|
message: t('已成功提交Midjourney图转视频任务,任务ID:{taskId}', { taskId: id }),
|
||||||
|
type: ResponseMessageType.MJ_VIDEO,
|
||||||
|
data: JSON.stringify(videoMessage)
|
||||||
|
},
|
||||||
|
task.messageName as string
|
||||||
|
)
|
||||||
|
|
||||||
|
// 开始循环查询任务状态
|
||||||
|
|
||||||
|
await this.FetchMJVideoResult(bookTaskDetail, task, id)
|
||||||
|
return successMessage(
|
||||||
|
t('Midjourney图转视频任务执行完成。'),
|
||||||
|
'MJVideoService_MJImageToVideo'
|
||||||
|
)
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(
|
||||||
|
t('Midjourney图转视频任务执行失败,失败信息如下:{error}', { error: error.message })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region FetchMJVideoResult
|
||||||
|
/**
|
||||||
|
* 获取并处理MJ视频生成结果
|
||||||
|
*
|
||||||
|
* 该方法会循环查询MJ视频任务的状态,直到任务完成(成功或失败)。
|
||||||
|
* 在查询过程中会实时更新任务状态和进度,并向前端发送状态消息。
|
||||||
|
*
|
||||||
|
* @param {Book.SelectBookTaskDetail} bookTaskDetail - 小说分镜详情对象
|
||||||
|
* @param {TaskModal.Task} task - 当前执行的任务对象
|
||||||
|
* @param {string} taskId - MJ API返回的任务ID
|
||||||
|
* @param {boolean} useTransfer - 是否使用传输模式(默认false,暂未使用)
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>} 无返回值的Promise,任务完成时结束
|
||||||
|
*
|
||||||
|
* @throws {Error} 当任务失败或API调用失败时抛出异常
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 处理流程:
|
||||||
|
* 1. 循环调用MJ API查询任务状态
|
||||||
|
* 2. 根据返回状态判断任务进展:
|
||||||
|
* - failure/cancel: 标记为失败,更新错误信息
|
||||||
|
* - success且进度100%: 标记为成功,保存视频URL
|
||||||
|
* - 其他状态: 标记为处理中,继续轮询
|
||||||
|
* 3. 每次状态变更都会:
|
||||||
|
* - 更新videoMessage到数据库
|
||||||
|
* - 更新任务状态
|
||||||
|
* - 发送实时消息给前端
|
||||||
|
* 4. 轮询间隔为20秒
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await this.FetchMJVideoResult(bookTaskDetail, task, "task_123456");
|
||||||
|
*/
|
||||||
|
async FetchMJVideoResult(
|
||||||
|
bookTaskDetail: Book.SelectBookTaskDetail,
|
||||||
|
task: TaskModal.Task,
|
||||||
|
taskId: string,
|
||||||
|
useTransfer: boolean = false
|
||||||
|
) {
|
||||||
|
console.log(useTransfer)
|
||||||
|
while (true) {
|
||||||
|
let fetchUrl = this.fetchTaskUrl.replace('${id}', taskId)
|
||||||
|
|
||||||
|
let res = await axios.get(fetchUrl, {
|
||||||
|
headers: {
|
||||||
|
Authorization: this.token
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log('FetchMJVideoResult response', res.data)
|
||||||
|
|
||||||
|
let resData = res.data
|
||||||
|
|
||||||
|
// 判断状态
|
||||||
|
let status = resData.status.toLowerCase()
|
||||||
|
let code = status == 'failure' || status == 'cancel' ? 0 : 1
|
||||||
|
let progress =
|
||||||
|
resData.progress && resData.progress.length > 0
|
||||||
|
? parseInt(resData.progress.slice(0, -1))
|
||||||
|
: 0
|
||||||
|
|
||||||
|
if (code == 0) {
|
||||||
|
// 任务失败
|
||||||
|
// 修改小说分镜的 videoMessage
|
||||||
|
let videoMessage = cloneDeep(bookTaskDetail.videoMessage) ?? {}
|
||||||
|
|
||||||
|
videoMessage.status = VideoStatus.FAIL
|
||||||
|
videoMessage.msg = resData.failReason
|
||||||
|
videoMessage.taskId = taskId
|
||||||
|
videoMessage.messageData = JSON.stringify(resData)
|
||||||
|
delete videoMessage.imageUrl
|
||||||
|
|
||||||
|
// 修改 videoMessage数据
|
||||||
|
this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage(
|
||||||
|
bookTaskDetail.id as string,
|
||||||
|
videoMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
// 修改TASK
|
||||||
|
this.taskListService.UpdateBackTaskData(task.id as string, {
|
||||||
|
taskId: taskId,
|
||||||
|
taskMessage: JSON.stringify(resData)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 返回前端数据
|
||||||
|
SendReturnMessage(
|
||||||
|
{
|
||||||
|
code: 0,
|
||||||
|
id: bookTaskDetail.id as string,
|
||||||
|
message: t('Midjourney图转视频任务执行失败,失败信息如下:{error}', {
|
||||||
|
error: resData.failReason
|
||||||
|
}),
|
||||||
|
type: ResponseMessageType.MJ_VIDEO,
|
||||||
|
data: JSON.stringify(videoMessage)
|
||||||
|
},
|
||||||
|
task.messageName as string
|
||||||
|
)
|
||||||
|
throw new Error(resData.failReason)
|
||||||
|
} else {
|
||||||
|
// 请求成功 但是需要判断状态和返回的进度
|
||||||
|
if (progress == 100 && status == 'success') {
|
||||||
|
// 任务成功 修改 videoMessage
|
||||||
|
let videoMessage = cloneDeep(bookTaskDetail.videoMessage) ?? {}
|
||||||
|
videoMessage.status = VideoStatus.SUCCESS
|
||||||
|
videoMessage.taskId = taskId
|
||||||
|
if (resData.videoUrls && resData.videoUrls.length > 0) {
|
||||||
|
videoMessage.videoUrls = []
|
||||||
|
resData.videoUrls.forEach((item: any) => {
|
||||||
|
videoMessage.videoUrls?.push(item.url)
|
||||||
|
})
|
||||||
|
videoMessage.videoUrl = videoMessage.videoUrls[0]
|
||||||
|
}
|
||||||
|
videoMessage.messageData = JSON.stringify(resData)
|
||||||
|
delete videoMessage.imageUrl
|
||||||
|
|
||||||
|
this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage(
|
||||||
|
task.bookTaskDetailId as string,
|
||||||
|
videoMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
// 修改小说分镜状态
|
||||||
|
this.bookTaskDetailService.ModifyBookTaskDetailById(task.bookTaskDetailId as string, {
|
||||||
|
status: BookTaskStatus.IMAGE_TO_VIDEO_SUCCESS
|
||||||
|
})
|
||||||
|
|
||||||
|
// 修改任务状态
|
||||||
|
this.taskListService.UpdateBackTaskData(task.id as string, {
|
||||||
|
status: BookBackTaskStatus.DONE,
|
||||||
|
taskId: taskId,
|
||||||
|
taskMessage: JSON.stringify(resData)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 下载 视频
|
||||||
|
await this.DownloadMJVideo(videoMessage.videoUrls || [], task, bookTaskDetail)
|
||||||
|
|
||||||
|
SendReturnMessage(
|
||||||
|
{
|
||||||
|
code: 1,
|
||||||
|
id: bookTaskDetail.id as string,
|
||||||
|
message: t('Midjourney图转视频任务执行完成。'),
|
||||||
|
type: ResponseMessageType.MJ_VIDEO,
|
||||||
|
data: JSON.stringify(videoMessage)
|
||||||
|
},
|
||||||
|
task.messageName as string
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有失败 没有成功 在执行中
|
||||||
|
// 再执行中
|
||||||
|
let videoMessage = cloneDeep(bookTaskDetail.videoMessage) ?? {}
|
||||||
|
videoMessage.status = VideoStatus.PROCESSING
|
||||||
|
videoMessage.taskId = taskId
|
||||||
|
videoMessage.messageData = JSON.stringify(resData)
|
||||||
|
delete videoMessage.imageUrl
|
||||||
|
this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage(
|
||||||
|
task.bookTaskDetailId as string,
|
||||||
|
videoMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
SendReturnMessage(
|
||||||
|
{
|
||||||
|
code: 1,
|
||||||
|
id: bookTaskDetail.id as string,
|
||||||
|
message: t('Midjourney图转视频任务执行中...'),
|
||||||
|
type: ResponseMessageType.MJ_VIDEO,
|
||||||
|
data: JSON.stringify(videoMessage)
|
||||||
|
},
|
||||||
|
task.messageName as string
|
||||||
|
)
|
||||||
|
|
||||||
|
// 没有成功 等待二十秒后继续执行
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 20000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region DownloadVideo
|
||||||
|
/**
|
||||||
|
* 下载视频到本地指定路径
|
||||||
|
* @param videoUrl 视频的URL地址
|
||||||
|
* @param savePath 本地保存路径
|
||||||
|
*/
|
||||||
|
async DownloadMJVideo(
|
||||||
|
videoUrls: string[],
|
||||||
|
task: TaskModal.Task,
|
||||||
|
bookTaskDetail: Book.SelectBookTaskDetail
|
||||||
|
) {
|
||||||
|
// 处理完成 开始下载指定的图片
|
||||||
|
|
||||||
|
let bookTask = await this.bookTaskService.GetBookTaskDataById(
|
||||||
|
bookTaskDetail.bookTaskId as string,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
let tempVideoUrls = bookTaskDetail.subVideoPath || []
|
||||||
|
let newVideoUrls: string[] = []
|
||||||
|
let outVideoPath: string = ''
|
||||||
|
|
||||||
|
const project_path = await getProjectPath()
|
||||||
|
|
||||||
|
// 开始下载所有视频
|
||||||
|
for (let i = 0; i < videoUrls.length; i++) {
|
||||||
|
const videoUrl = videoUrls[i]
|
||||||
|
// 处理文件地址和下载
|
||||||
|
let videoPath = path.join(
|
||||||
|
bookTask.imageFolder as string,
|
||||||
|
`video/subVideo/${bookTaskDetail.name}/${new Date().getTime()}_${i}.mp4`
|
||||||
|
)
|
||||||
|
await CheckFolderExistsOrCreate(path.dirname(videoPath))
|
||||||
|
await DownloadFile(videoUrl, videoPath)
|
||||||
|
|
||||||
|
// 处理返回数据信息
|
||||||
|
// 开始修改信息
|
||||||
|
// 将信息添加到里面
|
||||||
|
let a = {
|
||||||
|
localPath: path.relative(project_path, videoPath),
|
||||||
|
remotePath: videoUrl,
|
||||||
|
taskId: bookTaskDetail.videoMessage?.taskId,
|
||||||
|
index: i,
|
||||||
|
type: MappingTaskTypeToVideoModel(task.type as string)
|
||||||
|
}
|
||||||
|
newVideoUrls.push(JSON.stringify(a))
|
||||||
|
if (i == 0) {
|
||||||
|
outVideoPath = path.join(
|
||||||
|
bookTask.imageFolder as string,
|
||||||
|
'video',
|
||||||
|
bookTaskDetail.name + path.extname(videoPath)
|
||||||
|
)
|
||||||
|
await CopyFileOrFolder(videoPath, outVideoPath as string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理
|
||||||
|
|
||||||
|
// 开始处理数据
|
||||||
|
// 将原有的视频路径合并到新数组中
|
||||||
|
newVideoUrls.push(...tempVideoUrls)
|
||||||
|
await this.bookTaskDetailService.ModifyBookTaskDetailById(bookTaskDetail.id as string, {
|
||||||
|
subVideoPath: newVideoUrls,
|
||||||
|
generateVideoPath: outVideoPath != '' ? outVideoPath : ''
|
||||||
|
})
|
||||||
|
|
||||||
|
let newBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(
|
||||||
|
task.bookTaskDetailId as string,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
// 讲数据返回前端
|
||||||
|
SendReturnMessage(
|
||||||
|
{
|
||||||
|
code: 1,
|
||||||
|
id: task.bookTaskDetailId as string,
|
||||||
|
message: t('视频生成成功'),
|
||||||
|
type: ResponseMessageType.VIDEO_SUCESS,
|
||||||
|
data: JSON.stringify(newBookTaskDetail)
|
||||||
|
},
|
||||||
|
task.messageName as string
|
||||||
|
)
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ import { define } from '@/define/define'
|
|||||||
import { DEFINE_STRING } from '@/define/ipcDefineString'
|
import { DEFINE_STRING } from '@/define/ipcDefineString'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { GetOpenAISuccessResponse, GetRixApiErrorResponse } from '@/define/response/openAIResponse'
|
import { GetOpenAISuccessResponse, GetRixApiErrorResponse } from '@/define/response/openAIResponse'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
export class CopyWritingServiceHandle extends BookBasicHandle {
|
export class CopyWritingServiceHandle extends BookBasicHandle {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -34,17 +35,17 @@ export class CopyWritingServiceHandle extends BookBasicHandle {
|
|||||||
|
|
||||||
let simpleSetting = optionSerialization<SettingModal.CopyWritingSimpleSettings>(
|
let simpleSetting = optionSerialization<SettingModal.CopyWritingSimpleSettings>(
|
||||||
simpleSettingOption,
|
simpleSettingOption,
|
||||||
' 文案处理->设置 '
|
t('文案处理->设置')
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isEmpty(simpleSetting.gptType) || isEmpty(simpleSetting.gptData)) {
|
if (isEmpty(simpleSetting.gptType) || isEmpty(simpleSetting.gptData)) {
|
||||||
throw new Error('设置数据不完整,请检查提示词类型,提示词预设数据是否完整')
|
throw new Error(t('设置数据不完整,请检查提示词类型,提示词预设数据是否完整'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let wordStruct = simpleSetting.wordStruct
|
let wordStruct = simpleSetting.wordStruct
|
||||||
let filterWordStruct = wordStruct.filter((item) => ids.includes(item.id))
|
let filterWordStruct = wordStruct.filter((item) => ids.includes(item.id))
|
||||||
if (filterWordStruct.length === 0) {
|
if (filterWordStruct.length === 0) {
|
||||||
throw new Error('没有找到需要处理的文案ID对应的数据,请检查数据是否正确')
|
throw new Error(t('没有找到需要处理的文案ID对应的数据,请检查数据是否正确'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let apiSettingOption = this.optionRealmService.GetOptionByKey(
|
let apiSettingOption = this.optionRealmService.GetOptionByKey(
|
||||||
@ -53,10 +54,10 @@ export class CopyWritingServiceHandle extends BookBasicHandle {
|
|||||||
|
|
||||||
let apiSetting = optionSerialization<SettingModal.CopyWritingAPISettings>(
|
let apiSetting = optionSerialization<SettingModal.CopyWritingAPISettings>(
|
||||||
apiSettingOption,
|
apiSettingOption,
|
||||||
' 文案处理->设置 '
|
t('文案处理->设置')
|
||||||
)
|
)
|
||||||
if (isEmpty(apiSetting.apiKey) || isEmpty(apiSetting.gptUrl) || isEmpty(apiSetting.model)) {
|
if (isEmpty(apiSetting.apiKey) || isEmpty(apiSetting.gptUrl) || isEmpty(apiSetting.model)) {
|
||||||
throw new Error('文案处理API设置不完整,请检查API地址,密钥和模型是否设置正确')
|
throw new Error(t('文案处理API设置不完整,请检查API地址,密钥和模型是否设置正确'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -118,7 +119,7 @@ export class CopyWritingServiceHandle extends BookBasicHandle {
|
|||||||
{
|
{
|
||||||
code: 1,
|
code: 1,
|
||||||
id: wordStruct.id,
|
id: wordStruct.id,
|
||||||
message: '文案生成成功',
|
message: t('文案生成成功'),
|
||||||
data: {
|
data: {
|
||||||
oldWord: wordStruct.oldWord,
|
oldWord: wordStruct.oldWord,
|
||||||
newWord: resData
|
newWord: resData
|
||||||
@ -164,7 +165,9 @@ export class CopyWritingServiceHandle extends BookBasicHandle {
|
|||||||
|
|
||||||
// 判断返回的状态,如果是失败的话直接返回错误信息
|
// 判断返回的状态,如果是失败的话直接返回错误信息
|
||||||
if (axiosRes.status != 200) {
|
if (axiosRes.status != 200) {
|
||||||
throw new Error('请求失败')
|
throw new Error(t("请求失败,状态码:{statusCode}", {
|
||||||
|
statusCode: axiosRes.status
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
let dataRes = axiosRes.data
|
let dataRes = axiosRes.data
|
||||||
if (dataRes.code == 1) {
|
if (dataRes.code == 1) {
|
||||||
@ -174,7 +177,7 @@ export class CopyWritingServiceHandle extends BookBasicHandle {
|
|||||||
} else {
|
} else {
|
||||||
// 系统报错
|
// 系统报错
|
||||||
if (dataRes.code == 5000) {
|
if (dataRes.code == 5000) {
|
||||||
throw new Error('系统错误,错误信息如下:' + dataRes.message)
|
throw new Error(dataRes.message)
|
||||||
} else {
|
} else {
|
||||||
// 处理不同类型的错误消息
|
// 处理不同类型的错误消息
|
||||||
throw new Error(GetRixApiErrorResponse(dataRes.data))
|
throw new Error(GetRixApiErrorResponse(dataRes.data))
|
||||||
@ -185,7 +188,7 @@ export class CopyWritingServiceHandle extends BookBasicHandle {
|
|||||||
async CopyWritingAIGeneration(ids: string[]) {
|
async CopyWritingAIGeneration(ids: string[]) {
|
||||||
try {
|
try {
|
||||||
if (ids.length === 0) {
|
if (ids.length === 0) {
|
||||||
throw new Error('没有需要处理的文案ID')
|
throw new Error(t('没有需要处理的文案ID'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let { apiSetting, simpleSetting } = await this.getCopyWritingSetting(ids)
|
let { apiSetting, simpleSetting } = await this.getCopyWritingSetting(ids)
|
||||||
@ -193,7 +196,7 @@ export class CopyWritingServiceHandle extends BookBasicHandle {
|
|||||||
let wordStruct = simpleSetting.wordStruct
|
let wordStruct = simpleSetting.wordStruct
|
||||||
let filterWordStruct = wordStruct.filter((item) => ids.includes(item.id))
|
let filterWordStruct = wordStruct.filter((item) => ids.includes(item.id))
|
||||||
if (filterWordStruct.length === 0) {
|
if (filterWordStruct.length === 0) {
|
||||||
throw new Error('没有找到需要处理的文案ID对应的数据,请检查数据是否正确')
|
throw new Error(t('没有找到需要处理的文案ID对应的数据,请检查数据是否正确'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始循环请求AI
|
// 开始循环请求AI
|
||||||
@ -229,7 +232,7 @@ export class CopyWritingServiceHandle extends BookBasicHandle {
|
|||||||
{
|
{
|
||||||
code: 1,
|
code: 1,
|
||||||
id: element.id,
|
id: element.id,
|
||||||
message: '文案生成成功',
|
message: t('文案生成成功'),
|
||||||
data: {
|
data: {
|
||||||
oldWord: element.oldWord,
|
oldWord: element.oldWord,
|
||||||
newWord: returnData
|
newWord: returnData
|
||||||
@ -243,12 +246,14 @@ export class CopyWritingServiceHandle extends BookBasicHandle {
|
|||||||
// 处理完毕 返回数据。这边不做任何的保存动作
|
// 处理完毕 返回数据。这边不做任何的保存动作
|
||||||
return successMessage(
|
return successMessage(
|
||||||
wordStruct,
|
wordStruct,
|
||||||
'AI处理文案成功',
|
t('AI处理文案成功'),
|
||||||
'CopywritingAIGenerationService_CopyWritingAIGeneration'
|
'CopywritingAIGenerationService_CopyWritingAIGeneration'
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
'AI处理文案失败,失败原因如下:' + error.message,
|
t("AI处理文案失败,{error}", {
|
||||||
|
error: error.message
|
||||||
|
}),
|
||||||
'CopyWritingServiceHandle_CopyWritingAIGeneration'
|
'CopyWritingServiceHandle_CopyWritingAIGeneration'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { bookTaskDetailPreload } from './bookProload/bookTaskDetailPreload'
|
|||||||
import { bookPromptPreload } from './bookProload/bookPromptPreload'
|
import { bookPromptPreload } from './bookProload/bookPromptPreload'
|
||||||
import { bookImagePreload } from './bookProload/bookImagePreload'
|
import { bookImagePreload } from './bookProload/bookImagePreload'
|
||||||
import { bookExportPreload } from './bookProload/bookExportPreload'
|
import { bookExportPreload } from './bookProload/bookExportPreload'
|
||||||
|
import { bookVideoPreload } from './bookProload/bookVideoPreload'
|
||||||
|
|
||||||
const book = {
|
const book = {
|
||||||
...bookDataPreload,
|
...bookDataPreload,
|
||||||
@ -16,7 +17,11 @@ const book = {
|
|||||||
|
|
||||||
...bookImagePreload,
|
...bookImagePreload,
|
||||||
|
|
||||||
...bookExportPreload
|
...bookExportPreload,
|
||||||
|
|
||||||
|
video: {
|
||||||
|
...bookVideoPreload
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { book }
|
export { book }
|
||||||
|
|||||||
27
src/preload/subPreload/bookProload/bookVideoPreload.ts
Normal file
27
src/preload/subPreload/bookProload/bookVideoPreload.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { DEFINE_STRING } from '@/define/ipcDefineString'
|
||||||
|
import { ipcRenderer } from 'electron'
|
||||||
|
import { BookTaskDetail } from '@/define/model/book/bookTaskDetail'
|
||||||
|
|
||||||
|
export const bookVideoPreload = {
|
||||||
|
/** 获取指定条件的小说图转视频数据,包含子批次 */
|
||||||
|
GetVideoBookInfoList: async (condition: BookVideo.BookVideoInfoListQuertCondition) => {
|
||||||
|
return await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_VIDEO_BOOK_INFO_LIST, condition)
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 初始化视频消息数据 */
|
||||||
|
InitVideoMessage: async (bookTaskId: string) => {
|
||||||
|
return await ipcRenderer.invoke(DEFINE_STRING.BOOK.INIT_VIDEO_MESSAGE, bookTaskId)
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 更新小说分镜的视频消息 */
|
||||||
|
UpdateBookTaskDetailVideoMessage: async (
|
||||||
|
bookTaskDetailId: string,
|
||||||
|
videoMessage: Partial<BookTaskDetail.VideoMessage>
|
||||||
|
) => {
|
||||||
|
return await ipcRenderer.invoke(
|
||||||
|
DEFINE_STRING.BOOK.UPDATE_BOOK_TASK_DETAIL_VIDEO_MESSAGE,
|
||||||
|
bookTaskDetailId,
|
||||||
|
videoMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -42,6 +42,10 @@ const system = {
|
|||||||
SelectFolderOrFile: (extensions?: string[]) =>
|
SelectFolderOrFile: (extensions?: string[]) =>
|
||||||
ipcRenderer.invoke(DEFINE_STRING.SYSTEM.SELECT_FOLDER_OR_FILE, extensions),
|
ipcRenderer.invoke(DEFINE_STRING.SYSTEM.SELECT_FOLDER_OR_FILE, extensions),
|
||||||
|
|
||||||
|
/** 读取文本文件内容 */
|
||||||
|
ReadTextFile: (filePath: string) =>
|
||||||
|
ipcRenderer.invoke(DEFINE_STRING.SYSTEM.READ_TEXT_FILE, filePath),
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 系统相关
|
//#region 系统相关
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { GeneralResponse } from '@/define/model/generalResponse'
|
import { GeneralResponse } from '@/define/model/generalResponse'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回成功的消息,包含code,data,message
|
* 返回成功的消息,包含code,data,message
|
||||||
@ -13,7 +14,7 @@ function successMessage(
|
|||||||
service?: string
|
service?: string
|
||||||
): GeneralResponse.SuccessItem {
|
): GeneralResponse.SuccessItem {
|
||||||
if (service) {
|
if (service) {
|
||||||
global.logger.success(service, message ? message : '成功返回数据')
|
global.logger.success(service, message ? message : t('成功返回数据'))
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
code: 1,
|
code: 1,
|
||||||
@ -30,7 +31,7 @@ function successMessage(
|
|||||||
*/
|
*/
|
||||||
function errorMessage(message: string, service?: string): GeneralResponse.ErrorItem {
|
function errorMessage(message: string, service?: string): GeneralResponse.ErrorItem {
|
||||||
if (service) {
|
if (service) {
|
||||||
global.logger.error(service, message ? message : '未知报错,没有捕获的错误')
|
global.logger.error(service, message ? message : t('未知报错,没有捕获的错误'))
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
code: 0,
|
code: 0,
|
||||||
|
|||||||
22
src/renderer/components.d.ts
vendored
22
src/renderer/components.d.ts
vendored
@ -10,7 +10,7 @@ declare module 'vue' {
|
|||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AddOrModifyPreset: typeof import('./src/components/Preset/AddOrModifyPreset.vue')['default']
|
AddOrModifyPreset: typeof import('./src/components/Preset/AddOrModifyPreset.vue')['default']
|
||||||
AIGroup: typeof import('./src/components/Original/Copywriter/AIGroup.vue')['default']
|
AIGroup: typeof import('./src/components/Original/Copywriter/AIGroup.vue')['default']
|
||||||
AISetting: typeof import('./src/components/Setting/AISetting.vue')['default']
|
AISetting: typeof import('./src/components/Setting/InferenceSetting/AISetting.vue')['default']
|
||||||
AllImagePreview: typeof import('./src/components/Original/BookTaskDetail/AllImagePreview.vue')['default']
|
AllImagePreview: typeof import('./src/components/Original/BookTaskDetail/AllImagePreview.vue')['default']
|
||||||
APIIcon: typeof import('./src/components/common/Icon/APIIcon.vue')['default']
|
APIIcon: typeof import('./src/components/common/Icon/APIIcon.vue')['default']
|
||||||
AppearanceSettings: typeof import('./src/components/Setting/AppearanceSettings.vue')['default']
|
AppearanceSettings: typeof import('./src/components/Setting/AppearanceSettings.vue')['default']
|
||||||
@ -25,8 +25,7 @@ declare module 'vue' {
|
|||||||
CopyWritingCategoryMenu: typeof import('./src/components/CopyWriting/CopyWritingCategoryMenu.vue')['default']
|
CopyWritingCategoryMenu: typeof import('./src/components/CopyWriting/CopyWritingCategoryMenu.vue')['default']
|
||||||
CopyWritingContent: typeof import('./src/components/CopyWriting/CopyWritingContent.vue')['default']
|
CopyWritingContent: typeof import('./src/components/CopyWriting/CopyWritingContent.vue')['default']
|
||||||
CopyWritingShowAIGenerate: typeof import('./src/components/CopyWriting/CopyWritingShowAIGenerate.vue')['default']
|
CopyWritingShowAIGenerate: typeof import('./src/components/CopyWriting/CopyWritingShowAIGenerate.vue')['default']
|
||||||
CopyWritingSimpleSetting: typeof import('./src/components/CopyWriting/CopyWritingSimpleSetting.vue')['default']
|
CustomInferencePreset: typeof import('./src/components/Setting/InferenceSetting/CustomInferencePreset.vue')['default']
|
||||||
CustomInferencePreset: typeof import('./src/components/Setting/CustomInferencePreset.vue')['default']
|
|
||||||
CWInputWord: typeof import('./src/components/CopyWriting/CWInputWord.vue')['default']
|
CWInputWord: typeof import('./src/components/CopyWriting/CWInputWord.vue')['default']
|
||||||
DataTableAction: typeof import('./src/components/Original/BookTaskDetail/DataTableAction.vue')['default']
|
DataTableAction: typeof import('./src/components/Original/BookTaskDetail/DataTableAction.vue')['default']
|
||||||
DatatableAfterGpt: typeof import('./src/components/Original/BookTaskDetail/DatatableAfterGpt.vue')['default']
|
DatatableAfterGpt: typeof import('./src/components/Original/BookTaskDetail/DatatableAfterGpt.vue')['default']
|
||||||
@ -53,6 +52,19 @@ declare module 'vue' {
|
|||||||
JianyingKeyFrameSetting: typeof import('./src/components/Setting/JianyingKeyFrameSetting.vue')['default']
|
JianyingKeyFrameSetting: typeof import('./src/components/Setting/JianyingKeyFrameSetting.vue')['default']
|
||||||
LoadingComponent: typeof import('./src/components/common/LoadingComponent.vue')['default']
|
LoadingComponent: typeof import('./src/components/common/LoadingComponent.vue')['default']
|
||||||
ManageAISetting: typeof import('./src/components/CopyWriting/ManageAISetting.vue')['default']
|
ManageAISetting: typeof import('./src/components/CopyWriting/ManageAISetting.vue')['default']
|
||||||
|
MediaToVideoInfoBasicInfo: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoBasicInfo.vue')['default']
|
||||||
|
MediaToVideoInfoConfig: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoConfig.vue')['default']
|
||||||
|
MediaToVideoInfoEmptyState: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoEmptyState.vue')['default']
|
||||||
|
MediaToVideoInfoHome: typeof import('./src/components/MediaToVideo/MediaToVideoInfoHome.vue')['default']
|
||||||
|
MediaToVideoInfoMJVideoExtend: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoMJVideo/MediaToVideoInfoMJVideoExtend.vue')['default']
|
||||||
|
MediaToVideoInfoMJVideoImageToVideo: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoMJVideo/MediaToVideoInfoMJVideoImageToVideo.vue')['default']
|
||||||
|
MediaToVideoInfoMJVideoInfo: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoMJVideo/MediaToVideoInfoMJVideoInfo.vue')['default']
|
||||||
|
MediaToVideoInfoMJVideoSelectParentTask: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoMJVideo/MediaToVideoInfoMJVideoSelectParentTask.vue')['default']
|
||||||
|
MediaToVideoInfoTaskDetail: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoTaskDetail.vue')['default']
|
||||||
|
MediaToVideoInfoTaskList: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoTaskList.vue')['default']
|
||||||
|
MediaToVideoInfoTaskOptions: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoTaskOptions.vue')['default']
|
||||||
|
MediaToVideoInfoVideoConfig: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoVideoConfig.vue')['default']
|
||||||
|
MediaToVideoInfoVideoListInfo: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoVideoListInfo.vue')['default']
|
||||||
MenuOpenRound: typeof import('./src/components/common/Icon/MenuOpenRound.vue')['default']
|
MenuOpenRound: typeof import('./src/components/common/Icon/MenuOpenRound.vue')['default']
|
||||||
MessageAndProgress: typeof import('./src/components/Original/BookTaskDetail/MessageAndProgress.vue')['default']
|
MessageAndProgress: typeof import('./src/components/Original/BookTaskDetail/MessageAndProgress.vue')['default']
|
||||||
MJAccountDialog: typeof import('./src/components/Setting/MJSetting/MJAccountDialog.vue')['default']
|
MJAccountDialog: typeof import('./src/components/Setting/MJSetting/MJAccountDialog.vue')['default']
|
||||||
@ -121,7 +133,6 @@ declare module 'vue' {
|
|||||||
OriginalViewBookInfo: typeof import('./src/components/Original/MainHome/OriginalViewBookInfo.vue')['default']
|
OriginalViewBookInfo: typeof import('./src/components/Original/MainHome/OriginalViewBookInfo.vue')['default']
|
||||||
OriginalViewBookTaskInfo: typeof import('./src/components/Original/MainHome/OriginalViewBookTaskInfo.vue')['default']
|
OriginalViewBookTaskInfo: typeof import('./src/components/Original/MainHome/OriginalViewBookTaskInfo.vue')['default']
|
||||||
PresetShowCard: typeof import('./src/components/Preset/PresetShowCard.vue')['default']
|
PresetShowCard: typeof import('./src/components/Preset/PresetShowCard.vue')['default']
|
||||||
ProjectItem: typeof import('./src/components/Original/MainHome/ProjectItem.vue')['default']
|
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
SceneAnalysis: typeof import('./src/components/Original/Analysis/SceneAnalysis.vue')['default']
|
SceneAnalysis: typeof import('./src/components/Original/Analysis/SceneAnalysis.vue')['default']
|
||||||
@ -132,15 +143,12 @@ declare module 'vue' {
|
|||||||
SelectStylePreset: typeof import('./src/components/Preset/SelectStylePreset.vue')['default']
|
SelectStylePreset: typeof import('./src/components/Preset/SelectStylePreset.vue')['default']
|
||||||
StylePreset: typeof import('./src/components/Preset/StylePreset.vue')['default']
|
StylePreset: typeof import('./src/components/Preset/StylePreset.vue')['default']
|
||||||
TextEllipsis: typeof import('./src/components/common/TextEllipsis.vue')['default']
|
TextEllipsis: typeof import('./src/components/common/TextEllipsis.vue')['default']
|
||||||
ToolBoxHome: typeof import('./src/components/ToolBox/ToolBoxHome.vue')['default']
|
|
||||||
ToolGrid: typeof import('./src/components/ToolBox/ToolGrid.vue')['default']
|
ToolGrid: typeof import('./src/components/ToolBox/ToolGrid.vue')['default']
|
||||||
TooltipButton: typeof import('./src/components/common/TooltipButton.vue')['default']
|
TooltipButton: typeof import('./src/components/common/TooltipButton.vue')['default']
|
||||||
TooltipDropdown: typeof import('./src/components/common/TooltipDropdown.vue')['default']
|
TooltipDropdown: typeof import('./src/components/common/TooltipDropdown.vue')['default']
|
||||||
TopMenuButtons: typeof import('./src/components/Original/BookTaskDetail/TopMenuButtons.vue')['default']
|
TopMenuButtons: typeof import('./src/components/Original/BookTaskDetail/TopMenuButtons.vue')['default']
|
||||||
UploadRound: typeof import('./src/components/common/Icon/UploadRound.vue')['default']
|
UploadRound: typeof import('./src/components/common/Icon/UploadRound.vue')['default']
|
||||||
UserAnalysis: typeof import('./src/components/Original/Analysis/UserAnalysis.vue')['default']
|
UserAnalysis: typeof import('./src/components/Original/Analysis/UserAnalysis.vue')['default']
|
||||||
Versions: typeof import('./src/components/Versions.vue')['default']
|
|
||||||
ViewBookInfo: typeof import('./src/components/Original/MainHome/ViewBookInfo.vue')['default']
|
|
||||||
WechatGroup: typeof import('./src/components/SoftHome/WechatGroup.vue')['default']
|
WechatGroup: typeof import('./src/components/SoftHome/WechatGroup.vue')['default']
|
||||||
WordGroup: typeof import('./src/components/Original/Copywriter/WordGroup.vue')['default']
|
WordGroup: typeof import('./src/components/Original/Copywriter/WordGroup.vue')['default']
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,22 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>LaiTool PRO</title>
|
<title>LaiTool PRO</title>
|
||||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||||
<meta
|
<!-- <meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="img-src 'self' data: blob: file: https: http:; default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';"
|
content="
|
||||||
/>
|
default-src 'self';
|
||||||
|
img-src 'self' data: blob: file: https: http:;
|
||||||
|
media-src 'self' data: blob: file: https: http:;
|
||||||
|
video-src 'self' data: blob: file: https: http:;
|
||||||
|
audio-src 'self' data: blob: file: https: http:;
|
||||||
|
script-src 'self' 'unsafe-inline' 'unsafe-eval';
|
||||||
|
style-src 'self' 'unsafe-inline';
|
||||||
|
font-src 'self' data:;
|
||||||
|
connect-src 'self' https: http: ws: wss:;
|
||||||
|
object-src 'none';
|
||||||
|
base-uri 'self';
|
||||||
|
"
|
||||||
|
/> -->
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -53,7 +53,6 @@ import { darkTheme } from 'naive-ui'
|
|||||||
import LoadingScreen from '@renderer/views/LoadingScreen.vue'
|
import LoadingScreen from '@renderer/views/LoadingScreen.vue'
|
||||||
import Authorization from '@renderer/views/Authorization.vue'
|
import Authorization from '@renderer/views/Authorization.vue'
|
||||||
import { createActiveColor, createHoverColor } from '@/renderer/src/common/color'
|
import { createActiveColor, createHoverColor } from '@/renderer/src/common/color'
|
||||||
import { define } from '@/define/define'
|
|
||||||
import { useAuthorization } from '@/renderer/src/hooks/useAuthorization'
|
import { useAuthorization } from '@/renderer/src/hooks/useAuthorization'
|
||||||
|
|
||||||
const themeStore = useThemeStore()
|
const themeStore = useThemeStore()
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { errorMessage, successMessage } from '@/public/generalTools'
|
|||||||
import { usePresetStore } from '@renderer/stores'
|
import { usePresetStore } from '@renderer/stores'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
import { checkImageExists } from './image'
|
import { checkImageExists } from './image'
|
||||||
|
import { t } from '@/i18n'
|
||||||
const presetStore = usePresetStore()
|
const presetStore = usePresetStore()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -129,7 +130,7 @@ export async function checBookTaskDetailImageExist(
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
if (hasAllImage != -1) {
|
if (hasAllImage != -1) {
|
||||||
return errorMessage('分镜的图片没有全部出完,不能继续该操作!!')
|
return errorMessage(t('分镜的图片没有全部出完,不能继续该操作!!'))
|
||||||
}
|
}
|
||||||
// 检查所有的图片是否存在
|
// 检查所有的图片是否存在
|
||||||
let imageCheck = false
|
let imageCheck = false
|
||||||
@ -137,7 +138,7 @@ export async function checBookTaskDetailImageExist(
|
|||||||
for (let i = 0; i < bookTaskDetails.length; i++) {
|
for (let i = 0; i < bookTaskDetails.length; i++) {
|
||||||
const element = bookTaskDetails[i]
|
const element = bookTaskDetails[i]
|
||||||
if (element.outImagePath == undefined || element.outImagePath == null) {
|
if (element.outImagePath == undefined || element.outImagePath == null) {
|
||||||
return errorMessage('分镜的图片没有全部出完,不能继续该操作!!')
|
return errorMessage(t('分镜的图片没有全部出完,不能继续该操作!!'))
|
||||||
}
|
}
|
||||||
let c = await checkImageExists(element.outImagePath.split('?t')[0])
|
let c = await checkImageExists(element.outImagePath.split('?t')[0])
|
||||||
if (!c) {
|
if (!c) {
|
||||||
@ -147,8 +148,11 @@ export async function checBookTaskDetailImageExist(
|
|||||||
}
|
}
|
||||||
if (imageCheck) {
|
if (imageCheck) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
`分镜 ${notFoundImage.join(',')} 图片在本地未找到,不能继续该操作,请检查对应分镜的图片路径是否正确`
|
t("分镜 {name} 图片在本地未找到,不能继续该操作,请检查对应分镜的图片路径是否正确", {
|
||||||
|
name: notFoundImage.join(',')
|
||||||
|
})
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return successMessage(null, '分镜的图片全部存在,可以进行高清处理')
|
return successMessage(null, t("分镜的图片全部存在,可以进行高清处理"))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { getAPIOptions } from '@/define/data/apiData'
|
import { getAPIOptions } from '@/define/data/apiData'
|
||||||
import { ImageCategory, ImageToVideoCategory } from '@/define/data/imageData'
|
import { ImageCategory } from '@/define/data/imageData'
|
||||||
import { getMJSpeedOptions, ImageGenerateMode, MJRobotType } from '@/define/data/mjData'
|
import { getMJSpeedOptions, ImageGenerateMode, MJRobotType } from '@/define/data/mjData'
|
||||||
import { JianyingKeyFrameEnum } from '@/define/enum/jianyingEnum'
|
import { JianyingKeyFrameEnum } from '@/define/enum/jianyingEnum'
|
||||||
import { OptionKeyName, OptionType } from '@/define/enum/option'
|
import { OptionKeyName, OptionType } from '@/define/enum/option'
|
||||||
|
import { ImageToVideoModels } from '@/define/enum/video'
|
||||||
import { SettingModal } from '@/define/model/setting'
|
import { SettingModal } from '@/define/model/setting'
|
||||||
import { ValidateJson, ValidateJsonAndParse } from '@/define/Tools/validate'
|
import { ValidateJson, ValidateJsonAndParse } from '@/define/Tools/validate'
|
||||||
|
import { t } from '@/i18n'
|
||||||
import { optionSerialization } from '@/main/service/option/optionSerialization'
|
import { optionSerialization } from '@/main/service/option/optionSerialization'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
|
|
||||||
@ -16,8 +18,8 @@ export const defaultGeneralSetting: SettingModal.GeneralSettings = {
|
|||||||
concurrency: 1, // 系统并发数 (1-16)
|
concurrency: 1, // 系统并发数 (1-16)
|
||||||
defaultImgGenMethod: ImageCategory.Midjourney, // 默认生图方式
|
defaultImgGenMethod: ImageCategory.Midjourney, // 默认生图方式
|
||||||
hdScale: 2, // 高清倍数 (1-4)
|
hdScale: 2, // 高清倍数 (1-4)
|
||||||
defaultImg2Video: ImageToVideoCategory.RUNWAY, // 默认图转视频方式
|
defaultImg2Video: ImageToVideoModels.RUNWAY, // 默认图转视频方式
|
||||||
language: 'zh-CN' // 系统语言
|
language: 'zh-cn' // 系统语言
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,7 +69,9 @@ export async function InitGeneralSetting() {
|
|||||||
OptionType.JSON
|
OptionType.JSON
|
||||||
)
|
)
|
||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
throw new Error('初始化通用设置失败')
|
throw new Error(t("初始化通用设置失败,{error}", {
|
||||||
|
error: res.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
@ -152,7 +156,9 @@ export async function InitMJSetting() {
|
|||||||
OptionType.JSON
|
OptionType.JSON
|
||||||
)
|
)
|
||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
throw new Error('初始化MJ通用设置失败')
|
throw new Error(t("初始化MJ通用设置失败,{error}", {
|
||||||
|
error: res.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化API设置
|
// 初始化API设置
|
||||||
@ -182,7 +188,9 @@ export async function InitMJSetting() {
|
|||||||
OptionType.JSON
|
OptionType.JSON
|
||||||
)
|
)
|
||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
throw new Error('初始化MJ API设置失败')
|
throw new Error(t("初始化MJ API设置失败,{error}", {
|
||||||
|
error: res.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化生图包设置
|
// 初始化生图包设置
|
||||||
@ -213,7 +221,9 @@ export async function InitMJSetting() {
|
|||||||
OptionType.JSON
|
OptionType.JSON
|
||||||
)
|
)
|
||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
throw new Error('初始化MJ生图包设置失败')
|
throw new Error(t("初始化MJ生图包设置失败,{error}", {
|
||||||
|
error: res.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化 代理模式设置
|
// 初始化 代理模式设置
|
||||||
@ -242,7 +252,9 @@ export async function InitMJSetting() {
|
|||||||
OptionType.JSON
|
OptionType.JSON
|
||||||
)
|
)
|
||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
throw new Error('初始化MJ代理模式设置失败')
|
throw new Error(t("初始化MJ代理模式设置失败,{error}", {
|
||||||
|
error: res.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化 本地代理模式设置
|
// 初始化 本地代理模式设置
|
||||||
@ -271,7 +283,9 @@ export async function InitMJSetting() {
|
|||||||
OptionType.JSON
|
OptionType.JSON
|
||||||
)
|
)
|
||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
throw new Error('初始化MJ本地代理模式设置失败')
|
throw new Error(t("初始化MJ本地代理模式设置失败,{error}", {
|
||||||
|
error: res.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
@ -325,7 +339,9 @@ export async function InitInferenceAISetting() {
|
|||||||
OptionType.JSON
|
OptionType.JSON
|
||||||
)
|
)
|
||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
throw new Error('初始化推理设置失败')
|
throw new Error(t("初始化推理设置失败,{error}", {
|
||||||
|
error: res.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
@ -397,7 +413,9 @@ export async function InitSDSettingAndADetailerSetting() {
|
|||||||
OptionType.JSON
|
OptionType.JSON
|
||||||
)
|
)
|
||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
throw new Error('初始化SD设置失败')
|
throw new Error(t("初始化SD设置失败,{error}", {
|
||||||
|
error: res.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修手/修脸模型设置
|
// 修手/修脸模型设置
|
||||||
@ -416,7 +434,9 @@ export async function InitSDSettingAndADetailerSetting() {
|
|||||||
OptionType.JSON
|
OptionType.JSON
|
||||||
)
|
)
|
||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
throw new Error('初始化修手/修脸模型设置失败')
|
throw new Error(t("初始化修手/修脸模型设置失败,{error}", {
|
||||||
|
error: res.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -486,7 +506,9 @@ export async function InitJianyingKeyFrameSetting() {
|
|||||||
OptionType.JSON
|
OptionType.JSON
|
||||||
)
|
)
|
||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
throw new Error('初始化剪映关键帧设置失败')
|
throw new Error(t("初始化剪映关键帧设置失败,{error}", {
|
||||||
|
error: res.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
@ -536,7 +558,9 @@ export async function InitComfyUISetting() {
|
|||||||
OptionType.JSON
|
OptionType.JSON
|
||||||
)
|
)
|
||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
throw new Error('初始化Comfy UI设置失败')
|
throw new Error(t("初始化ComfyUI设置失败,{error}", {
|
||||||
|
error: res.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
let comfyuiWorkFlowSetting = await window.option.GetOptionByKey(
|
let comfyuiWorkFlowSetting = await window.option.GetOptionByKey(
|
||||||
@ -556,7 +580,9 @@ export async function InitComfyUISetting() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
throw new Error('初始化Comfy UI设置失败')
|
throw new Error(t("初始化ComfyUI工作流设置失败,{error}", {
|
||||||
|
error: res.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
@ -584,10 +610,12 @@ export async function InitSpecialCharacters() {
|
|||||||
OptionType.STRING
|
OptionType.STRING
|
||||||
)
|
)
|
||||||
if (saveRes.code != 1) {
|
if (saveRes.code != 1) {
|
||||||
throw new Error('初始化特殊符号字符串失败: ' + saveRes.message)
|
throw new Error(t("初始化特殊符号字符串失败,{error}", {
|
||||||
|
error: saveRes.message
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error('初始化特殊符号字符串失败: ' + error)
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,77 @@ import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '@/define/
|
|||||||
import { DEFINE_STRING } from '@/define/ipcDefineString'
|
import { DEFINE_STRING } from '@/define/ipcDefineString'
|
||||||
import { Book } from '@/define/model/book/book'
|
import { Book } from '@/define/model/book/book'
|
||||||
import { ErrorItem, SuccessItem } from '@/define/model/generalResponse'
|
import { ErrorItem, SuccessItem } from '@/define/model/generalResponse'
|
||||||
|
import { TaskModal } from '@/define/model/task'
|
||||||
|
import { t } from '@/i18n/main'
|
||||||
import { errorMessage, successMessage } from '@/public/generalTools'
|
import { errorMessage, successMessage } from '@/public/generalTools'
|
||||||
import { useBookStore } from '@renderer/stores'
|
import { useBookStore } from '@renderer/stores'
|
||||||
const bookStore = useBookStore()
|
const bookStore = useBookStore()
|
||||||
|
|
||||||
|
//#region AddOneTask
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加单个后台任务
|
||||||
|
*
|
||||||
|
* 该方法用于向系统任务队列中添加一个新的后台任务。支持多种任务类型的创建,
|
||||||
|
* 包括图像生成、视频处理、文本推理等。任务创建后会被自动调度执行,
|
||||||
|
* 并通过指定的消息通道返回执行结果。
|
||||||
|
*
|
||||||
|
* @param {TaskModal.Task} task - 完整的任务配置对象,包含以下属性:
|
||||||
|
* - bookId: 关联的书籍ID
|
||||||
|
* - type: 任务类型(如图像生成、视频转换、文本推理等)
|
||||||
|
* - executeType: 执行模式(自动执行或手动触发)
|
||||||
|
* - bookTaskId: 关联的书籍批次任务ID
|
||||||
|
* - bookTaskDetailId: 关联的具体分镜ID
|
||||||
|
* - messageName: 任务完成后的消息通道名称
|
||||||
|
* - 其他任务特定的配置参数
|
||||||
|
*
|
||||||
|
* @returns {Promise<SuccessItem | ErrorItem>} 任务添加结果:
|
||||||
|
* - 成功:返回包含任务详情和成功提示的SuccessItem对象
|
||||||
|
* - 失败:返回包含错误信息的ErrorItem对象
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 添加Midjourney图像生成任务
|
||||||
|
* const mjImageTask = {
|
||||||
|
* bookId: bookStore.selectBook.id,
|
||||||
|
* type: BookBackTaskType.MJ_IMAGE,
|
||||||
|
* executeType: TaskExecuteType.AUTO,
|
||||||
|
* bookTaskId: bookStore.selectBookTask.id,
|
||||||
|
* bookTaskDetailId: frameId,
|
||||||
|
* messageName: DEFINE_STRING.BOOK.MJ_IMAGE_GENERATE_RETURN
|
||||||
|
* };
|
||||||
|
* const result = await AddOneTask(mjImageTask);
|
||||||
|
*
|
||||||
|
* // 添加视频生成任务
|
||||||
|
* const videoTask = {
|
||||||
|
* bookId: bookStore.selectBook.id,
|
||||||
|
* type: BookBackTaskType.MJ_VIDEO,
|
||||||
|
* executeType: TaskExecuteType.AUTO,
|
||||||
|
* bookTaskId: bookStore.selectBookTask.id,
|
||||||
|
* bookTaskDetailId: frameId,
|
||||||
|
* messageName: DEFINE_STRING.BOOK.MJ_VIDEO_GENERATE_RETURN
|
||||||
|
* };
|
||||||
|
* const result = await AddOneTask(videoTask);
|
||||||
|
*/
|
||||||
|
export async function AddOneTask(task: TaskModal.Task): Promise<SuccessItem | ErrorItem> {
|
||||||
|
try {
|
||||||
|
const result = await window.task.AddOneTask(task)
|
||||||
|
|
||||||
|
if (result.code === 1) {
|
||||||
|
return successMessage(
|
||||||
|
result.data,
|
||||||
|
t("任务添加成功,任务名称:{taskName}", { taskName: result.data?.name })
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return errorMessage(t("任务添加失败,失败信息如下:{error}", { error: result.message }))
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
return errorMessage(t("任务添加失败,失败信息如下:{error}", { error: error.message }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 添加图片生成任务
|
||||||
/**
|
/**
|
||||||
* 批量添加图片生成任务
|
* 批量添加图片生成任务
|
||||||
*
|
*
|
||||||
@ -41,7 +108,7 @@ export async function AddMultiImageTask(
|
|||||||
): Promise<SuccessItem | ErrorItem> {
|
): Promise<SuccessItem | ErrorItem> {
|
||||||
let res: SuccessItem | ErrorItem
|
let res: SuccessItem | ErrorItem
|
||||||
if (bookTaskDetails.length <= 0) {
|
if (bookTaskDetails.length <= 0) {
|
||||||
return errorMessage('添加出图任务失败,未找到可生成图片的分镜,或者分镜都被锁定')
|
return errorMessage(t("添加出图任务失败,未找到可生成图片的分镜,或者分镜都被锁定"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageCategory == ImageCategory.Midjourney) {
|
if (imageCategory == ImageCategory.Midjourney) {
|
||||||
@ -133,11 +200,14 @@ export async function AddMultiImageTask(
|
|||||||
}
|
}
|
||||||
res = await window.task.AddMultiTask(tasksList)
|
res = await window.task.AddMultiTask(tasksList)
|
||||||
} else {
|
} else {
|
||||||
return errorMessage('添加出图任务失败,不支持的出图类型')
|
return errorMessage(t("添加出图任务失败,不支持的出图类型"))
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 停止指定类型的任务
|
||||||
/**
|
/**
|
||||||
* 停止图片生成任务
|
* 停止图片生成任务
|
||||||
*
|
*
|
||||||
@ -162,7 +232,7 @@ export async function AddMultiImageTask(
|
|||||||
*/
|
*/
|
||||||
export async function StopImageTask(type: string): Promise<SuccessItem | ErrorItem> {
|
export async function StopImageTask(type: string): Promise<SuccessItem | ErrorItem> {
|
||||||
if (type != 'all' && type != 'this') {
|
if (type != 'all' && type != 'this') {
|
||||||
return errorMessage('停止出图任务失败,参数错误')
|
return errorMessage(t('停止出图任务失败,参数错误'))
|
||||||
}
|
}
|
||||||
let taskTypes = [
|
let taskTypes = [
|
||||||
BookBackTaskType.MJ_IMAGE,
|
BookBackTaskType.MJ_IMAGE,
|
||||||
@ -200,7 +270,7 @@ export async function StopImageTask(type: string): Promise<SuccessItem | ErrorIt
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stopTasks.length <= 0) {
|
if (stopTasks.length <= 0) {
|
||||||
return errorMessage('停止出图任务失败,没有需要停止的任务')
|
return errorMessage(t('停止出图任务失败,没有需要停止的任务'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始执行停止的任务
|
// 开始执行停止的任务
|
||||||
@ -209,15 +279,17 @@ export async function StopImageTask(type: string): Promise<SuccessItem | ErrorIt
|
|||||||
let res = await window.task.UpdateTaskStatus({
|
let res = await window.task.UpdateTaskStatus({
|
||||||
id: element.id,
|
id: element.id,
|
||||||
status: BookBackTaskStatus.FAIL,
|
status: BookBackTaskStatus.FAIL,
|
||||||
errorMessage: '用户手动取消了任务'
|
errorMessage: t('用户手动取消了任务')
|
||||||
})
|
})
|
||||||
if (res.code != 1) {
|
if (res.code != 1) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type == 'all') {
|
if (type == 'all') {
|
||||||
return successMessage(null, '停止所有批次的出图任务成功', 'StopImageTask')
|
return successMessage(null, t('停止所有批次的出图任务成功'), 'StopImageTask')
|
||||||
} else {
|
} else {
|
||||||
return successMessage(null, '停止当前批次的出图任务成功', 'StopImageTask')
|
return successMessage(null, t('停止当前批次的出图任务成功'), 'StopImageTask')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user