diff --git a/package-lock.json b/package-lock.json index 4231726..52d311f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "laitool", - "version": "3.2.2", + "version": "3.2.3", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index b294dc6..ce4ddcd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "laitool", - "version": "3.2.2", + "version": "3.2.3", "description": "An AI tool for image processing, video processing, and other functions.", "main": "./out/main/index.js", "author": "laitool.cn", diff --git a/resources/scripts/db/book.realm.lock b/resources/scripts/db/book.realm.lock index 15a4248..6b06983 100644 Binary files a/resources/scripts/db/book.realm.lock and b/resources/scripts/db/book.realm.lock differ diff --git a/resources/scripts/db/software - 副本.realm b/resources/scripts/db/software - 副本.realm deleted file mode 100644 index 5055713..0000000 Binary files a/resources/scripts/db/software - 副本.realm and /dev/null differ diff --git a/resources/scripts/db/software - 副本.realm.lock b/resources/scripts/db/software - 副本.realm.lock deleted file mode 100644 index 0541b88..0000000 Binary files a/resources/scripts/db/software - 副本.realm.lock and /dev/null differ diff --git a/resources/scripts/db/software.realm b/resources/scripts/db/software.realm index 57b6dd7..38e7fb1 100644 Binary files a/resources/scripts/db/software.realm and b/resources/scripts/db/software.realm differ diff --git a/resources/scripts/db/software.realm.lock b/resources/scripts/db/software.realm.lock index a21c909..8379390 100644 Binary files a/resources/scripts/db/software.realm.lock and b/resources/scripts/db/software.realm.lock differ diff --git a/resources/scripts/db/tts.realm.lock b/resources/scripts/db/tts.realm.lock index d086d38..ae6f085 100644 Binary files a/resources/scripts/db/tts.realm.lock and b/resources/scripts/db/tts.realm.lock differ diff --git a/src/define/Tools/validate.ts b/src/define/Tools/validate.ts index f462742..5c39f42 100644 --- a/src/define/Tools/validate.ts +++ b/src/define/Tools/validate.ts @@ -6,6 +6,9 @@ */ export function ValidateJson(str: string): boolean { try { + if (str == null) { + return false; + } JSON.parse(str); return true } catch (e) { diff --git a/src/define/db/service/Book/bookBackTaskListService.ts b/src/define/db/service/Book/bookBackTaskListService.ts index f248a70..32f38cd 100644 --- a/src/define/db/service/Book/bookBackTaskListService.ts +++ b/src/define/db/service/Book/bookBackTaskListService.ts @@ -11,6 +11,7 @@ import { OtherData } from '../../../enum/softwareEnum.js' import { BookBackTaskList } from '../../model/Book/BookBackTaskListModel.js' import { Book } from '../../../../model/book/book.js' import { GeneralResponse } from '../../../../model/generalResponse.js' +import { TaskModal } from '@/model/task.js' const { v4: uuidv4 } = require('uuid') export class BookBackTaskListService extends BaseRealmService { diff --git a/src/define/db/service/Book/bookService.ts b/src/define/db/service/Book/bookService.ts index 32e756f..048056f 100644 --- a/src/define/db/service/Book/bookService.ts +++ b/src/define/db/service/Book/bookService.ts @@ -232,7 +232,8 @@ export class BookService extends BaseRealmService { updateTime: new Date(), createTime: new Date(), version: version, - imageCategory: imageCategory + imageCategory: imageCategory, + openVideoGenerate: false } // 添加任务 diff --git a/src/define/db/service/SoftWare/optionRealmService.ts b/src/define/db/service/SoftWare/optionRealmService.ts new file mode 100644 index 0000000..d82a5e2 --- /dev/null +++ b/src/define/db/service/SoftWare/optionRealmService.ts @@ -0,0 +1,79 @@ +import Realm from 'realm' +import { isEmpty, cloneDeep } from 'lodash' +import { OptionType } from '@/define/enum/option' +import { BaseSoftWareService } from './softwareBasic' +import { OptionModel } from '@/model/option/option' + +export class OptionRealmService extends BaseSoftWareService { + static instance: OptionRealmService | null = null + declare realm: Realm + private constructor() { + super() + } + + /** + * 获取当前实例对象,为空则创建一个新的 + * @returns + */ + public static async getInstance() { + if (OptionRealmService.instance === null) { + OptionRealmService.instance = new OptionRealmService() + await super.getInstance() + } + await OptionRealmService.instance.open() + return OptionRealmService.instance + } + + /** + * 获取指定的Option,通过key,不存在返回null + * @param key + * @returns + */ + public GetOptionByKey(key: string): OptionModel.OptionItem | null { + if (isEmpty(key)) { + return null + } + let res = this.realm.objects('Options').filtered(`key = "${key}"`); + + if (res.length > 0) { + let resData = Array.from(res).map((item) => { + let resObj = { + ...item + } + return cloneDeep(resObj) + }) + return resData[0] as OptionModel.OptionItem + } else { + return null; + } + } + + /** + * 修改指定的Option,通过key,不存在则创建 + * @param key + * @param value + */ + public ModifyOptionByKey(key: string, value: string, type: OptionType = OptionType.STRING) { + if (isEmpty(key)) { + return false + } + let option = this.realm.objectForPrimaryKey('Options', key); + if (option) { + this.realm.write(() => { + option.value = value; + option.type = type; + }) + } else { + this.realm.write(() => { + this.realm.create('Options', { + key: key, + value: value, + type: type + }) + }) + } + return true + } +} + + diff --git a/src/define/define_string/index.ts b/src/define/define_string/index.ts index 8339b10..454ef53 100644 --- a/src/define/define_string/index.ts +++ b/src/define/define_string/index.ts @@ -5,6 +5,7 @@ import SETTING from "./settingDefineString" import BOOK from "./bookDefineString" import WRITE from "./writeDefineString" import DB from "./dbDefineString" +import OPTIONS from "./optionsDefineString" export const DEFINE_STRING = { SYSTEM: SYSTEM, @@ -14,6 +15,7 @@ export const DEFINE_STRING = { SETTING: SETTING, WRITE: WRITE, DB: DB, + OPTIONS:OPTIONS, SHOW_GLOBAL_MESSAGE: "SHOW_GLOBAL_MESSAGE", SHOW_GLOBAL_MAIN_NOTIFICATION: 'SHOW_GLOBAL_MAIN_NOTIFICATION', OPEN_DEV_TOOLS_PASSWORD: 'OPEN_DEV_TOOLS_PASSWORD', diff --git a/src/define/define_string/optionsDefineString.ts b/src/define/define_string/optionsDefineString.ts new file mode 100644 index 0000000..dbb900d --- /dev/null +++ b/src/define/define_string/optionsDefineString.ts @@ -0,0 +1,19 @@ +const OPTIONS = { + + /** + * 获取指定的Option,通过key,不存在返回null + */ + GET_OPTION_BY_KEY: 'GET_OPTION_BY_KEY', + + /** + * 修改指定的Option,通过key,不存在则创建 + */ + MODIFY_OPTION_BY_KEY: 'MODIFY_OPTION_BY_KEY', + + /** + * 同步文案处理中的AI设置旧数据到新的数据表中 + */ + INIT_COPY_WRITING_AI_SETTING: "INIT_COPY_WRITING_AI_SETTING" +} + +export default OPTIONS \ No newline at end of file diff --git a/src/define/define_string/writeDefineString.ts b/src/define/define_string/writeDefineString.ts index 69a3cb0..6884bc7 100644 --- a/src/define/define_string/writeDefineString.ts +++ b/src/define/define_string/writeDefineString.ts @@ -1,7 +1,6 @@ const WRITE = { GET_WRITE_CONFIG: 'GET_WRITE_CONFIG', SAVE_WRITE_CONFIG: 'SAVE_WRITE_CONFIG', - ACTION_START: 'ACTION_START', GET_SUBTITLE_SETTING: "GET_SUBTITLE_SETTING", RESET_SUBTITLE_SETTING: "RESET_SUBTITLE_SETTING", SAVE_SUBTITLE_SETTING: "SAVE_SUBTITLE_SETTING", @@ -9,7 +8,16 @@ const WRITE = { /** 生成洗稿后文案 */ GENERATE_AFTER_GPT_WORD: "GENERATE_AFTER_GPT_WORD", /** 生成洗稿后文案返回数据,前端接收 */ - GENERATE_AFTER_GPT_WORD_RESPONSE: "GENERATE_AFTER_GPT_WORD_RESPONSE" + GENERATE_AFTER_GPT_WORD_RESPONSE: "GENERATE_AFTER_GPT_WORD_RESPONSE", + + //#region 文案改写 + + /** + * AI处理文案 + */ + COPY_WRITING_AI_GENERATION: "COPY_WRITING_AI_GENERATION", + + //#endregion } export default WRITE \ No newline at end of file diff --git a/src/define/enum/option.ts b/src/define/enum/option.ts index e113f8e..2fef9e8 100644 --- a/src/define/enum/option.ts +++ b/src/define/enum/option.ts @@ -1,7 +1,40 @@ -/** option 中的type的类型 */ +/** + * Option Value的数据类型,用于数据的格式化 + */ export enum OptionType { STRING = 'string', NUMBER = 'number', BOOLEAN = 'boolean', JOSN = 'json' +} + +export enum OptionKeyName { + + //#region 文案处理 + + /** + * 文案处理的AI设置 + */ + CW_AISetting = 'CW_AISetting', + + /** + * 文案处理数据界面数据 + */ + CW_AISimpleSetting = 'CW_AISimpleSetting', + + /** + * 格式化的特殊字符数据 + */ + CW_FormatSpecialChar = 'CW_FormatSpecialChar', + + //#endregion + + //#region TTS + + /** + * TTS界面视图数据 + */ + TTS_GlobalSetting = 'TTS_GlobalSetting', + + //#endregion } \ No newline at end of file diff --git a/src/define/gptDefine.js b/src/define/gptDefine.js index 440461a..a2b3c3f 100644 --- a/src/define/gptDefine.js +++ b/src/define/gptDefine.js @@ -8,7 +8,20 @@ import { apiUrl } from './api/apiUrlDefine' export const gptDefine = { // Add properties and methods to the shared object characterSystemContent: `{textContent}\r查看上面的文本,然后扮演一个文本编辑来回答问题。`, - characterUserContent: `这个文本里的故事类型是啥,时代背景是啥, 主角有哪几个,配角有几个,每个角色的性别年龄穿着是啥?没外观描述的直接猜测,尽量精简 格式按照:故事类型:(故事类型)\n时代背景:(时代背景)\n主角名字1:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n主角名字2:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n主角3........\n配角名字1:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n配角名字2:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n配角名字3.... ,不知道的直接猜测设定,不能出不详和未知这两个词,150字内,中文回答。`, + characterUserContent: `这个文本里的故事类型是什么,时代背景是什么, 上面文本中存在哪些场景,主角有哪几个,配角有几个,每个角色的性别年龄穿着是啥?没外观描述的直接猜测,尽量精简 + 格式按照: + 故事类型:(故事类型) + 时代背景:(时代背景) + 主角名字1:(性别,头发颜色,发型,衣服类型,年龄,角色外貌,若未提及则合理推测) + 主角名字2:(性别,头发颜色,发型,衣服类型,年龄,角色外貌,若未提及则合理推测) + 主角3........ + 配角名字1:(性别,头发颜色,发型,衣服类型,年龄,角色外貌,若未提及则合理推测) + 配角名字2:(性别,头发颜色,发型,衣服类型,年龄,角色外貌,若未提及则合理推测) + 配角名字3.... , + 场景1:(地点,环境状况,光线条件,氛围特点,所处时间,若无明确信息则合理推测) + 场景2:(地点,环境状况,光线条件,氛围特点,所处时间,若无明确信息则合理推测) + 场景3...... + 不知道的直接猜测设定,不能出不详和未知这两个词,250字内,中文回答。`, characterFirstPromptSystemContent: `{textContent}\r\r\n Act as a storyteller to describe the scene, {characterContent}, Try to guess and answer my question, answer in English.`, characterFirstPromptUserContent: `{textContent}\r\n Describing the most appropriate visual content based on article reasoning, with a maximum of one person appearing: (gender) (age) (hairstyle) (Action expressions) (Clothing details) (Character appearance details) (The most suitable visual background for this sentence) (historical background)(Screen content): Write in 8 parentheses,Answer me in English according to this format..{wordCount}words`, diff --git a/src/main/IPCEvent/index.js b/src/main/IPCEvent/index.js index f324529..943879e 100644 --- a/src/main/IPCEvent/index.js +++ b/src/main/IPCEvent/index.js @@ -17,6 +17,7 @@ import { TTSIpc } from './ttsIpc' import { DBIpc } from './dbIpc' import { PresetIpc } from './presetIpc' import { TaskIpc } from './taskIpc' +import { OptionsIpc } from './optionsIpc' export async function RegisterIpc(createWindow) { PromptIpc() @@ -38,4 +39,5 @@ export async function RegisterIpc(createWindow) { SystemIpc() BookIpc() TTSIpc() + OptionsIpc() } diff --git a/src/main/IPCEvent/optionsIpc.ts b/src/main/IPCEvent/optionsIpc.ts new file mode 100644 index 0000000..944fca7 --- /dev/null +++ b/src/main/IPCEvent/optionsIpc.ts @@ -0,0 +1,33 @@ +import { ipcMain } from 'electron' +import { DEFINE_STRING } from '../../define/define_string' +import OptionHandle from '../Service/Options/index' +import { OptionType } from '@/define/enum/option' + +function OptionsIpc() { + + /** + * 获取指定的Option,通过key,不存在返回null + */ + ipcMain.handle( + DEFINE_STRING.OPTIONS.GET_OPTION_BY_KEY, + async (_, key: string) => await OptionHandle.GetOptionByKey(key) + ) + + /** + * 修改指定的Option,通过key,不存在则创建 + */ + ipcMain.handle( + DEFINE_STRING.OPTIONS.MODIFY_OPTION_BY_KEY, + async (_, key: string, value: string, type: OptionType) => + await OptionHandle.ModifyOptionByKey(key, value, type) + ) + + /** + * 同步文案处理中的AI设置旧数据到新的数据表中 + */ + ipcMain.handle( + DEFINE_STRING.OPTIONS.INIT_COPY_WRITING_AI_SETTING, + async () => await OptionHandle.InitCopyWritingAISetting() + ) +} +export { OptionsIpc } diff --git a/src/main/IPCEvent/ttsIpc.ts b/src/main/IPCEvent/ttsIpc.ts index 4a44375..9eff588 100644 --- a/src/main/IPCEvent/ttsIpc.ts +++ b/src/main/IPCEvent/ttsIpc.ts @@ -5,19 +5,11 @@ import { TTS } from '../Service/tts' const tts = new TTS() export function TTSIpc() { - // 获取当前的TTS配置数据 - ipcMain.handle(DEFINE_STRING.TTS.GET_TTS_CONFIG, async () => await tts.GetTTSCOnfig()) - - // 保存TTS配置 - ipcMain.handle( - DEFINE_STRING.TTS.SAVE_TTS_CONFIG, - async (event, data) => await tts.SaveTTSConfig(data) - ) // 生成音频 ipcMain.handle( DEFINE_STRING.TTS.GENERATE_AUDIO, - async (event, text) => await tts.GenerateAudio(text) + async (event) => await tts.GenerateAudio() ) // 生成SRT字幕文件 diff --git a/src/main/IPCEvent/writingIpc.ts b/src/main/IPCEvent/writingIpc.ts index 349d013..f3456b0 100644 --- a/src/main/IPCEvent/writingIpc.ts +++ b/src/main/IPCEvent/writingIpc.ts @@ -9,6 +9,8 @@ import { BookPrompt } from '../Service/Book/bookPrompt' let subtitleService = new SubtitleService() const bookPrompt = new BookPrompt(); +import CopyWritingService from '@/main/Service/copywriting/index' + function WritingIpc() { // 监听分镜时间的保存 ipcMain.handle( @@ -69,11 +71,18 @@ function WritingIpc() { async (event, subtitleSetting) => await subtitleService.SaveSubtitleSetting(subtitleSetting) ) + //#region 文案处理 + + /** + * AI处理文案 + */ ipcMain.handle( - DEFINE_STRING.WRITE.ACTION_START, - async (event, aiSetting, word) => await writing.ActionStart(aiSetting, word) + DEFINE_STRING.WRITE.COPY_WRITING_AI_GENERATION, + async (event, ids: string[]) => await CopyWritingService.CopyWritingAIGeneration(ids) ) + //#endregion + //#region 文案洗稿相关 /** 生成洗稿后文案 */ diff --git a/src/main/Public/Translate.js b/src/main/Public/Translate.js index 959c37e..dac636c 100644 --- a/src/main/Public/Translate.js +++ b/src/main/Public/Translate.js @@ -351,27 +351,59 @@ export class Translate { content: translateData }) - let config = { - method: 'post', - maxBodyLength: Infinity, - url: this.translationBusiness, - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - }, - data: JSON.stringify(data) + let content = '' + // 判断整体是不是需要LMS转发 + if (global.config.useTransfer) { + let url = define.lms + '/lms/Forward/SimpleTransfer' + let config = { + method: 'post', + url: url, + maxBodyLength: Infinity, + headers: { + 'Content-Type': 'application/json' + }, + data: JSON.stringify({ + url: this.translationBusiness, + apiKey: token, + dataString: JSON.stringify(data) + }) + } + // 重试机制 + let res = await RetryWithBackoff( + async () => { + return await axios.request(config) + }, + 5, + 2000 + ) + + if (res.data.code != 1) { + throw new Error(res.data.message) + } + content = GetOpenAISuccessResponse(res.data.data) + } else { + let config = { + method: 'post', + maxBodyLength: Infinity, + url: this.translationBusiness, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + data: JSON.stringify(data) + } + let res = await RetryWithBackoff( + async () => { + return await axios.request(config) + }, + 5, + 2000 + ) + // 将返回的数据进行拼接数据处理 + content = GetOpenAISuccessResponse(res.data) } - let res = await RetryWithBackoff( - async () => { - return await axios.request(config) - }, - 5, - 2000 - ) - // 将返回的数据进行拼接数据处理 let res_data = [] - let content = GetOpenAISuccessResponse(res.data) if (to == 'zh') { res_data.push({ diff --git a/src/main/Service/Book/BooKBasic.ts b/src/main/Service/Book/BooKBasic.ts index 66b4f44..dc5ba04 100644 --- a/src/main/Service/Book/BooKBasic.ts +++ b/src/main/Service/Book/BooKBasic.ts @@ -101,9 +101,14 @@ export class BookBasic { try { let book = await this.bookServiceBasic.GetBookDataById(bookId) // 获取所有的小说批次 - let bookTasks = (await this.bookServiceBasic.GetBookTaskData({ + let bookTasksObj = (await this.bookServiceBasic.GetBookTaskData({ bookId: bookId - })).bookTasks; + }, true)); + // 删除之前判断是不是有子批次 没有直接退出 + let bookTasks = bookTasksObj.bookTasks; + if (bookTasks.length == 0) { + return successMessage('未找到小说批次数据,正常退出', 'BookBasic_ResetBookData'); + } // 重置批次任务 for (let i = 0; i < bookTasks.length; i++) { const element = bookTasks[i]; @@ -179,13 +184,18 @@ export class BookBasic { if (resetRes.code == 0) { throw new Error(resetRes.message) } - let bookTasks = (await this.bookServiceBasic.GetBookTaskData({ + let bookTasksObj = (await this.bookServiceBasic.GetBookTaskData({ bookId: bookId - })).bookTasks; - // 删除遗留重置的小说批次任务 - for (let i = 0; i < bookTasks.length; i++) { - const element = bookTasks[i]; - await this.bookServiceBasic.DeleteBookTaskData(element.id); + }, true)) + let bookTasks = bookTasksObj.bookTasks; + + // 有数据才删除 + if (bookTasks.length > 0) { + // 删除遗留重置的小说批次任务 + for (let i = 0; i < bookTasks.length; i++) { + const element = bookTasks[i]; + await this.bookServiceBasic.DeleteBookTaskData(element.id); + } } // 开始删除数据 diff --git a/src/main/Service/Book/bookPrompt.ts b/src/main/Service/Book/bookPrompt.ts index 0306763..bdfe9fb 100644 --- a/src/main/Service/Book/bookPrompt.ts +++ b/src/main/Service/Book/bookPrompt.ts @@ -436,7 +436,7 @@ export class BookPrompt { }) } // 分批次执行异步任务 - let res = await ExecuteConcurrently(tasks, global.config.task_number) + await ExecuteConcurrently(tasks, global.config.task_number) // 执行完毕 return successMessage(null, "推理所有数据完成", 'BookPrompt_OriginalGetPrompt') diff --git a/src/main/Service/Book/bookTask.ts b/src/main/Service/Book/bookTask.ts index b6ba6b3..4a05364 100644 --- a/src/main/Service/Book/bookTask.ts +++ b/src/main/Service/Book/bookTask.ts @@ -227,6 +227,7 @@ export class BookTask { this.bookServiceBasic.transaction((realm) => { for (let i = 0; i < bookTasks.length; i++) { const element = bookTasks[i]; + element.openVideoGenerate = false realm.create('BookTask', element) } for (let i = 0; i < bookTaskDetail.length; i++) { @@ -409,6 +410,7 @@ export class BookTask { suffixPrompt: sourceBookTask.suffixPrompt, version: sourceBookTask.version, imageCategory: sourceBookTask.imageCategory, + openVideoGenerate: sourceBookTask.openVideoGenerate == null ? false : sourceBookTask.openVideoGenerate, } as Book.SelectBookTask addBookTask.push(addOneBookTask) @@ -517,7 +519,7 @@ export class BookTask { return successMessage(returnBookTask, "复制小说任务成功", "BookBasic_CopyNewBookTask") } catch (error) { console.log(error) - throw error + return errorMessage("复制小说任务失败,失败信息如下:" + error.message, "BookBasic_CopyNewBookTask") } } diff --git a/src/main/Service/Book/bookVideo.ts b/src/main/Service/Book/bookVideo.ts index b87c5d5..b8a5dbc 100644 --- a/src/main/Service/Book/bookVideo.ts +++ b/src/main/Service/Book/bookVideo.ts @@ -262,8 +262,9 @@ export class BookVideo { if (repalceObject && repalceObject.length > 0) { await this.jianyingService.ReplaceDraftMaterialImageToVideo(book.name + "_" + element.name, repalceObject); } - return successMessage(result, `${result.join('\n')} ${'\n'} 剪映草稿添加成功`, "BookTask_AddJianyingDraft") } + // 所有的草稿都添加完毕之后开始返回 + return successMessage(result, `${result.join('\n')} ${'\n'} 剪映草稿添加成功`, "BookTask_AddJianyingDraft") } catch (error) { return errorMessage('添加剪映草稿失败,错误信息如下:' + error.toString(), "BookTask_AddJianyingDraft"); } diff --git a/src/main/Service/GPT/gpt.ts b/src/main/Service/GPT/gpt.ts index c8a0f48..340c5b7 100644 --- a/src/main/Service/GPT/gpt.ts +++ b/src/main/Service/GPT/gpt.ts @@ -1,8 +1,9 @@ -import { isEmpty } from "lodash"; +import { isEmpty, method } from "lodash"; import { gptDefine } from "../../../define/gptDefine"; import axios from "axios"; import { RetryWithBackoff } from "../../../define/Tools/common"; import { Book } from "../../../model/book/book"; +import { define } from "@/define/define"; /** * 一些GPT相关的服务都在这边 @@ -11,6 +12,7 @@ export class GptService { gptUrl: string = undefined gptModel: string = undefined gptApiKey: string = undefined + useTransfer: boolean = false //#region GPT 设置 @@ -42,10 +44,12 @@ export class GptService { this.gptUrl = all_options[index].gpt_url; this.gptApiKey = global.config.gpt_key; this.gptModel = global.config.gpt_model; + this.useTransfer = global.config.useTransfer; return { gptUrl: this.gptUrl, gptApiKey: this.gptApiKey, - gptModel: this.gptModel + gptModel: this.gptModel, + useTransfer: this.useTransfer } } @@ -101,6 +105,16 @@ export class GptService { } if (gpt_url.includes("dashscope.aliyuncs.com")) { content = res.data.output.choices[0].message.content; + } else if (this.useTransfer) { + // 是不是有用 LMS 转发 + console.log(res) + let data = res.data; + if (data.code != 1) { + throw new Error(data.message) + } + let aiContentStr = res.data.data; + let aiContent = JSON.parse(aiContentStr); + content = aiContent.choices[0].message.content; } else { content = res.data.choices[0].message.content; } @@ -126,21 +140,43 @@ export class GptService { "messages": message }; - data = this.ModifyData(data, gpt_url); - let config = { - method: 'post', - maxBodyLength: Infinity, - url: gpt_url ? gpt_url : this.gptUrl, - headers: { - 'Authorization': `Bearer ${gpt_key ? gpt_key : this.gptApiKey}`, - 'Content-Type': 'application/json' - }, - data: JSON.stringify(data) - }; + if (this.useTransfer) { + // 转发到LMS中过一遍 + let url = define.lms + "/lms/Forward/SimpleTransfer"; + let config = { + method: 'post', + url: url, + maxBodyLength: Infinity, + headers: { + 'Content-Type': 'application/json' + }, + data: JSON.stringify({ + url: gpt_url ? gpt_url : this.gptUrl, + apiKey: gpt_key ? gpt_key : this.gptApiKey, + dataString: JSON.stringify(data) + }) + } + let res = await axios.request(config); + let content = this.GetResponseContent(res, gpt_url); + return content; + } else { + // 不转发 直接请求原接口 + data = this.ModifyData(data, gpt_url); + let config = { + method: 'post', + maxBodyLength: Infinity, + url: gpt_url ? gpt_url : this.gptUrl, + headers: { + 'Authorization': `Bearer ${gpt_key ? gpt_key : this.gptApiKey}`, + 'Content-Type': 'application/json' + }, + data: JSON.stringify(data) + }; - let res = await axios.request(config); - let content = this.GetResponseContent(res, this.gptUrl); - return content; + let res = await axios.request(config); + let content = this.GetResponseContent(res, this.gptUrl); + return content; + } } catch (error) { throw error; } diff --git a/src/main/Service/Options/index.ts b/src/main/Service/Options/index.ts new file mode 100644 index 0000000..aae795d --- /dev/null +++ b/src/main/Service/Options/index.ts @@ -0,0 +1,41 @@ +import { OptionType } from "@/define/enum/option" +import { OptionServices } from "./optionServices" + +class OptionHandle { + optionServices: OptionServices + constructor() { + this.optionServices = new OptionServices() + } + + //#region 和数据库的option操作 + /** + * 获取指定的Option,通过key,不存在返回null + * @param key 指定的Key的值 + * @returns + */ + GetOptionByKey = async (key: string) => await this.optionServices.GetOptionByKey(key) + + /** + * 修改指定的Option,通过key,不存在则创建 + * @param key 要修改的Key + * @param value 修改Key指定的值 + * @param type 值的类型 + * @returns + */ + ModifyOptionByKey = async (key: string, value: string, type: OptionType) => + await this.optionServices.ModifyOptionByKey(key, value, type) + + //#endregion + + //#region 其他的Option操作 + + /** + * 同步文案处理中的AI设置旧数据到新的数据表中 + * @returns + */ + InitCopyWritingAISetting = async () => await this.optionServices.InitCopyWritingAISetting() + + //#endregion +} + +export default new OptionHandle() diff --git a/src/main/Service/Options/optionServices.ts b/src/main/Service/Options/optionServices.ts new file mode 100644 index 0000000..cea6a63 --- /dev/null +++ b/src/main/Service/Options/optionServices.ts @@ -0,0 +1,116 @@ +import { OptionRealmService } from '@/define/db/service/SoftWare/optionRealmService' +import { OptionKeyName, OptionType } from '@/define/enum/option' +import { ValidateJson } from '@/define/Tools/validate' +import { errorMessage, successMessage } from '@/main/Public/generalTools' +import { ErrorItem, GeneralResponse, SuccessItem } from '@/model/generalResponse' +export class OptionServices { + optionRealmService!: OptionRealmService + constructor() { } + + /** 初始化数据库服务 */ + async InitService() { + if (!this.optionRealmService) { + this.optionRealmService = await OptionRealmService.getInstance() + } + } + + /** + * 获取指定的Option,通过key,不存在返回null + * @param key + * @returns + */ + public async GetOptionByKey(key: string): Promise { + try { + await this.InitService() + let res = this.optionRealmService.GetOptionByKey(key) + return successMessage(res, '获取成功 OptionKey: ' + key, 'OptionOptions.GetOptionByKey') + } catch (error: any) { + return errorMessage( + '获取失败 OptionKey: ' + key + ',失败信息如下 : ' + error.message, + 'OptionOptions.GetOptionByKey' + ) + } + } + + /** + * 修改指定的Option,通过key,不存在则创建 + * @param key + * @param value + */ + public async ModifyOptionByKey( + key: string, + value: string, + type: OptionType + ): Promise { + try { + await this.InitService() + if (type == OptionType.BOOLEAN) { + value = value.toString() + } + let res = this.optionRealmService.ModifyOptionByKey(key, value, type) + return successMessage(res, '修改成功 OptionKey: ' + key, 'OptionOptions.ModifyOptionByKey') + } catch (error: any) { + return errorMessage( + `修改失败 OptionKey: ${key} , 失败信息如下: ${error.message}`, + 'OptionOptions.ModifyOptionByKey' + ) + } + } + + + /** + * 同步文案处理中的AI设置旧数据到新的数据表中 + * @returns + */ + public async InitCopyWritingAISetting(): Promise { + try { + await this.InitService() + + // 没有数据 也没有数据同步 需要初始化 + let aiSetting = { + "laiapi": { + "gpt_url": "https://api.laitool.cc", + "api_key": "你的LAI API的API Key", + "model": "你要使用的API 模型名称,不是令牌名" + } + } + let CW_AISetting = this.optionRealmService.GetOptionByKey(OptionKeyName.CW_AISetting); + if (CW_AISetting != null) { + let CW_AISettingData = CW_AISetting.value as string + + // 判断已有数据能不能格式化,如果可以格式化则不需要初始化 + if (ValidateJson(CW_AISettingData)) { + return successMessage(JSON.parse(CW_AISettingData), "数据已存在,无需再次同步或初始化", "OptionOptions.InitCopyWritingAISetting") + } else { + this.optionRealmService.ModifyOptionByKey(OptionKeyName.CW_AISetting, JSON.stringify(aiSetting), OptionType.JOSN); + return successMessage(aiSetting, "数据已存在,但是数据格式不正确,已重新初始化", "OptionOptions.InitCopyWritingAISetting") + } + } + + + // 同步旧文案处理AI设置 + let software = this.optionRealmService.realm.objects('Software'); + if (software.length > 0) { + // 有数据 同步之前的数据 + let softwareData = software.toJSON()[0] + let SynchronizeAISetting = softwareData["aiSetting"] as string + if (ValidateJson(SynchronizeAISetting)) { + this.optionRealmService.ModifyOptionByKey(OptionKeyName.CW_AISetting, SynchronizeAISetting, OptionType.JOSN); + return successMessage(JSON.parse(SynchronizeAISetting), "同步旧文案处理AI设置数据成功", "OptionOptions.InitCopyWritingAISetting") + } else { + this.optionRealmService.ModifyOptionByKey(OptionKeyName.CW_AISetting, JSON.stringify(aiSetting), OptionType.JOSN); + return successMessage(aiSetting, "旧的文案处理AI设置无效,已重新重置", "OptionOptions.InitCopyWritingAISetting") + } + } + + // 新设置 + this.optionRealmService.ModifyOptionByKey(OptionKeyName.CW_AISetting, JSON.stringify(aiSetting), OptionType.JOSN); + return successMessage(aiSetting, '初始化文案处理AI设置成功', 'OptionOptions.SynchronizeAISettingOldData') + } catch (error: any) { + return errorMessage( + '同步失败,失败信息如下:' + error.message, + 'OptionOptions.SynchronizeAISettingOldData' + ) + } + } +} diff --git a/src/main/Service/SD/sd.ts b/src/main/Service/SD/sd.ts index 1308582..79c1ba4 100644 --- a/src/main/Service/SD/sd.ts +++ b/src/main/Service/SD/sd.ts @@ -17,6 +17,7 @@ import { DEFINE_STRING } from "../../../define/define_string"; import { OtherData, ResponseMessageType } from "../../../define/enum/softwareEnum"; import util from 'util'; import { exec } from 'child_process'; +import { TaskModal } from "@/model/task"; const execAsync = util.promisify(exec) const fspromise = fs.promises diff --git a/src/main/Service/ServiceBasic/bookServiceBasic.ts b/src/main/Service/ServiceBasic/bookServiceBasic.ts index 0004c1a..8081ce1 100644 --- a/src/main/Service/ServiceBasic/bookServiceBasic.ts +++ b/src/main/Service/ServiceBasic/bookServiceBasic.ts @@ -55,7 +55,14 @@ class BookServiceBasic { //#region 批次任务任务 GetBookTaskDataById = async (bookTaskId: string) => await this.bookTaskServiceBasic.GetBookTaskDataById(bookTaskId); - GetBookTaskData = async (bookTaskCondition: Book.QueryBookTaskCondition) => await this.bookTaskServiceBasic.GetBookTaskData(bookTaskCondition); + /** + * 通过查询条件获取小说批次任务数据 + * @param bookTaskCondition 查询的小说条件 + * @param returnEmpry 是不是返回空数据,默认是false,没有数据直接报错,true的话返回空数据 + * @returns + */ + GetBookTaskData = async (bookTaskCondition: Book.QueryBookTaskCondition, returnEmpry: boolean = false) => await this.bookTaskServiceBasic.GetBookTaskData(bookTaskCondition, returnEmpry); + GetMaxBookTaskNo = async (bookId: string) => await this.bookTaskServiceBasic.GetMaxBookTaskNo(bookId); UpdetedBookTaskData = async (bookTaskId: string, data: Book.SelectBookTask) => await this.bookTaskServiceBasic.UpdetedBookTaskData(bookTaskId, data); ResetBookTask = async (bookTaskId: string) => await this.bookTaskServiceBasic.ResetBookTask(bookTaskId); diff --git a/src/main/Service/ServiceBasic/bookTaskServiceBasic.ts b/src/main/Service/ServiceBasic/bookTaskServiceBasic.ts index d2ffe18..ec06fbc 100644 --- a/src/main/Service/ServiceBasic/bookTaskServiceBasic.ts +++ b/src/main/Service/ServiceBasic/bookTaskServiceBasic.ts @@ -33,14 +33,20 @@ export default class BookTaskServiceBasic { /** * 通过查询条件获取小说批次任务数据 - * @param bookTaskCondition 小说批次的查询条件 + * @param bookTaskCondition 查询的小说条件 + * @param returnEmpry 是不是返回空数据,默认是false,没有数据直接报错,true的话返回空数据 + * @returns */ - async GetBookTaskData(bookTaskCondition: Book.QueryBookTaskCondition): Promise<{ bookTasks: Book.SelectBookTask[], total: number }> { - + async GetBookTaskData(bookTaskCondition: Book.QueryBookTaskCondition, returnEmpry: boolean = false): Promise<{ bookTasks: Book.SelectBookTask[], total: number }> { + await this.InitService(); let bookTasks = this.bookTaskService.GetBookTaskData(bookTaskCondition) - if (bookTasks.data.bookTasks.length <= 0 || bookTasks.data.total <= 0) { - throw new Error("未找到对应的小说批次任务数据,请检查") + if (returnEmpry) { + return { bookTasks: [], total: 0 } + } else { + if (bookTasks.data.bookTasks.length <= 0 || bookTasks.data.total <= 0) { + throw new Error("未找到对应的小说批次任务数据,请检查") + } } return bookTasks.data } diff --git a/src/main/Service/Subtitle/subtitleService.ts b/src/main/Service/Subtitle/subtitleService.ts index d2859de..2deb21a 100644 --- a/src/main/Service/Subtitle/subtitleService.ts +++ b/src/main/Service/Subtitle/subtitleService.ts @@ -405,9 +405,8 @@ export class SubtitleService { btd.timeLimit = element.timeLimit } }) - } - + return successMessage(null, "保存文案数据成功", 'SubtitleService_SaveCopywriting') } catch (error) { return errorMessage("保存文案数据失败,失败信息如下:" + error.toString(), 'SubtitleService_SaveCopywriting') } diff --git a/src/main/Service/Translate/Translate.ts b/src/main/Service/Translate/Translate.ts index 892c270..23361c2 100644 --- a/src/main/Service/Translate/Translate.ts +++ b/src/main/Service/Translate/Translate.ts @@ -193,24 +193,51 @@ export class Translate { "content": value.text }); - let config = { - method: 'post', - maxBodyLength: Infinity, - url: this.translationBusiness, - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` - }, - data: JSON.stringify(data) - }; - let res = await RetryWithBackoff(async () => { - return await axios.request(config); - }, 5, 2000) - // let res = await axios.request(config); - // 将返回的数据进行拼接数据处理 + let content = ""; + // 判断整体是不是需要LMS转发 + if (global.config.useTransfer) { + let url = define.lms + "/lms/Forward/SimpleTransfer"; + let config = { + method: 'post', + url: url, + maxBodyLength: Infinity, + headers: { + 'Content-Type': 'application/json' + }, + data: JSON.stringify({ + url: this.translationBusiness, + apiKey: token, + dataString: JSON.stringify(data) + }) + } + // 重试机制 + let res = await RetryWithBackoff(async () => { + return await axios.request(config); + }, 5, 2000) + if (res.data.code != 1) { + throw new Error(res.data.message); + } + content = GetOpenAISuccessResponse(res.data.data); + } else { + let config = { + method: 'post', + maxBodyLength: Infinity, + url: this.translationBusiness, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + data: JSON.stringify(data) + }; + let res = await RetryWithBackoff(async () => { + return await axios.request(config); + }, 5, 2000) + // 将返回的数据进行拼接数据处理 + content = GetOpenAISuccessResponse(res.data); + } let res_data = []; - let content = GetOpenAISuccessResponse(res.data); + res_data.push({ src: value.text, dst: content diff --git a/src/main/Service/copywriting/copywritingAIGenerationService.ts b/src/main/Service/copywriting/copywritingAIGenerationService.ts new file mode 100644 index 0000000..11bd27e --- /dev/null +++ b/src/main/Service/copywriting/copywritingAIGenerationService.ts @@ -0,0 +1,210 @@ +import { OptionKeyName } from "@/define/enum/option"; +import { RetryWithBackoff } from "@/define/Tools/common"; +import { errorMessage, successMessage } from "@/main/Public/generalTools"; +import OptionHandle from "@/main/Service/Options/index"; +import { OptionModel } from "@/model/option/option"; +import { get, isEmpty } from "lodash"; +import { define } from "@/define/define" +import { DEFINE_STRING } from "@/define/define_string"; +import { GetDoubaoErrorResponse, GetKimiErrorResponse, GetOpenAISuccessResponse, GetRixApiErrorResponse } from "@/define/response/openAIResponse"; +import axios from "axios"; + +export class CopywritingAIGenerationService { + + //#region 文案处理相关 + + /** + * AI处理文案 + * @param idS 需要改写的文案ID + */ + async CopyWritingAIGeneration(ids: string[]) { + try { + if (ids.length === 0) { + throw new Error("没有需要处理的文案ID") + } + + // 加载文案处理数据 + let CW_AISimpleSetting = await OptionHandle.GetOptionByKey(OptionKeyName.CW_AISimpleSetting); + if (CW_AISimpleSetting.code !== 1) { + throw new Error("加载文案处理数据失败,失败原因如下:" + CW_AISimpleSetting.message); + } + let CW_AISimpleSettingData = JSON.parse(CW_AISimpleSetting.data.value) as OptionModel.CW_AISimpleSettingModel; + + if (isEmpty(CW_AISimpleSettingData.gptType) || isEmpty(CW_AISimpleSettingData.gptData) || isEmpty(CW_AISimpleSettingData.gptAI)) { + throw new Error("设置数据不完整,请检查提示词类型,提示词预设,请求AI数据是否完整"); + } + + let wordStruct = CW_AISimpleSettingData.wordStruct; + let filterWordStruct = wordStruct.filter((item) => ids.includes(item.id)); + if (filterWordStruct.length === 0) { + throw new Error("没有找到需要处理的文案ID对应的数据,请检查数据是否正确"); + } + + let CW_AISetting = await OptionHandle.GetOptionByKey(OptionKeyName.CW_AISetting); + if (CW_AISetting.code !== 1) { + throw new Error("加载AI设置数据失败,失败原因如下:" + CW_AISetting.message); + } + let CW_AISettingData = JSON.parse(CW_AISetting.data.value); + let aiSetting = get(CW_AISettingData, CW_AISimpleSettingData.gptAI, {}); + for (const aid in aiSetting) { + if (isEmpty(aid)) { + throw new Error('请先设置AI设置') + } + } + + // 开始循环请求AI + for (let ii = 0; ii < filterWordStruct.length; ii++) { + const element = filterWordStruct[ii]; + if (CW_AISimpleSettingData.isStream) { + // 流式请求 + let returnData = await RetryWithBackoff(async () => { + return await this.AIRequestStream(CW_AISimpleSettingData, aiSetting, element, "") + }, 3, 1000) + '\n' + // 这边将数据保存 + element.newWord = returnData + } else { + // 非流式请求 + let returnData = await RetryWithBackoff(async () => { + return await this.AIRequest(CW_AISimpleSettingData, aiSetting, element.oldWord) + }, 3, 1000) + '\n' + // 这边将数据保存 + element.newWord = returnData + console.log(returnData) + // 将非流的数据返回 + global.newWindow[0].win.webContents.send(DEFINE_STRING.GPT.GPT_STREAM_RETURN, { + id: element.id, + newWord: returnData + }) + } + } + + // 处理完毕 返回数据。这边不做任何的保存动作 + return successMessage(wordStruct, "AI处理文案成功", "CopywritingAIGenerationService_CopyWritingAIGeneration") + + } catch (error) { + return errorMessage("AI处理文案失败,失败原因如下:" + error.message, "CopywritingAIGenerationService_CopyWritingAIGeneration") + } + } + + /** + * AI 请求发送 + * @param {*} setting + * @param {*} aiData + * @param {*} word + * @returns + */ + async AIRequest(setting, aiData, word): Promise { + // 开始请求AI + let axiosRes = await axios.post('/lms/Forward/ForwardWord', { + promptTypeId: setting.gptType, + promptId: setting.gptData, + gptUrl: aiData.gpt_url + '/v1/chat/completions', + model: aiData.model, + machineId: global.machineId, + apiKey: aiData.api_key, + word: word + }) + + // 判断返回的状态,如果是失败的话直接返回错误信息 + if (axiosRes.status != 200) { + throw new Error('请求失败') + } + let dataRes = axiosRes.data + if (dataRes.code == 1) { + // 获取成功 + // 解析返回的数据 + return GetOpenAISuccessResponse(dataRes.data); + + } else { + // 系统报错 + if (dataRes.code == 5000) { + throw new Error('系统错误,错误信息如下:' + dataRes.message) + } else { + // 处理不同类型的错误消息 + if (setting.gptAI == 'laiapi') { + throw new Error(GetRixApiErrorResponse(dataRes.data)) + } else if (setting.gptAI == 'kimi') { + throw new Error(GetKimiErrorResponse(dataRes.data)) + } else if (setting.gptAI == 'doubao') { + throw new Error(GetDoubaoErrorResponse(dataRes.data)) + } else { + throw new Error(dataRes.data) + } + } + } + } + + /** + * 流式请求接口 + * @param setting + * @param aiData + * @param word + */ + async AIRequestStream(setting, aiData, wordStruct: OptionModel.CW_AISimpleSettingModel_WordStruct, oldData: string) { + let body = { + promptTypeId: setting.gptType, + promptId: setting.gptData, + gptUrl: aiData.gpt_url, + model: aiData.model, + machineId: global.machineId, + apiKey: aiData.api_key, + word: wordStruct.oldWord, + } + + var myHeaders = new Headers(); + myHeaders.append("User-Agent", "Apifox/1.0.0 (https://apifox.com)"); + myHeaders.append("Content-Type", "application/json"); + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: JSON.stringify(body), + }; + + let resData = ''; + return new Promise((resolve, reject) => { + fetch(define.lms + "/lms/Forward/ForwardWordStream", requestOptions) + .then(response => { + if (!response.body) { + throw new Error('ReadableStream not yet supported in this browser.'); + } + const reader = response.body.getReader(); + return new ReadableStream({ + start(controller) { + function push() { + reader.read().then(({ + done, + value + }) => { + if (done) { + controller.close(); + resolve(resData) + return; + } + // 假设服务器发送的是文本数据 + const text = new TextDecoder().decode(value); + resData += text + // 将数据返回前端 + global.newWindow[0].win.webContents.send(DEFINE_STRING.GPT.GPT_STREAM_RETURN, { + id: wordStruct.id, + newWord: resData + }) + controller.enqueue(value); // 可选:将数据块放入流中 + push(); + }).catch(err => { + controller.error(err); + reject(err) + }); + } + push(); + } + }); + }) + .catch(error => { + reject(error) + }); + }) + } + + //#endregion +} \ No newline at end of file diff --git a/src/main/Service/copywriting/index.ts b/src/main/Service/copywriting/index.ts new file mode 100644 index 0000000..6011af5 --- /dev/null +++ b/src/main/Service/copywriting/index.ts @@ -0,0 +1,24 @@ + + +import { CopywritingAIGenerationService } from "./copywritingAIGenerationService"; + +class CopyWritingService { + + copywritingAIGenerationService: CopywritingAIGenerationService + + constructor() { + this.copywritingAIGenerationService = new CopywritingAIGenerationService(); + } + + //#region 文案处理 + + /** + * AI处理文案 + * @param idS 需要改写的文案ID + */ + CopyWritingAIGeneration = async (idS: string[]) => await this.copywritingAIGenerationService.CopyWritingAIGeneration(idS); + + //#endregion +} + +export default new CopyWritingService(); \ No newline at end of file diff --git a/src/main/Service/tts.ts b/src/main/Service/tts.ts index b07281f..5d07210 100644 --- a/src/main/Service/tts.ts +++ b/src/main/Service/tts.ts @@ -14,12 +14,20 @@ import { tts } from '../../model/tts' import { GeneralResponse } from '../../model/generalResponse' import axios from 'axios' import { GetEdgeTTSRole } from '../../define/tts/ttsDefine' +import { OptionServices } from "@/main/Service/Options/optionServices" +import { OptionKeyName } from '@/define/enum/option' +import { OptionModel } from '@/model/option/option' export class TTS { softService: SoftwareService ttsService: TTSService - constructor() { } + optionServices: OptionServices + + + constructor() { + this.optionServices = new OptionServices() + } /** * 初始化TTS服务 @@ -62,72 +70,12 @@ export class TTS { throw new Error("获取TTS角色配置失败") } if (isEmpty(data[0].value) || !ValidateJson(data[0].value)) { - return successMessage(GetEdgeTTSRole(), "获取远程配置失败,获取默认配音角色", "TTS_GetTTSCOnfig"); // 使用默认值 + return successMessage(GetEdgeTTSRole(), "获取远程配置失败,获取默认配音角色", "TTS_GetTTSOptions"); // 使用默认值 } // 返回远程值 - return successMessage(JSON.parse(data[0].value), '获取TTS配置成功', 'TTS_GetTTSCOnfig') + return successMessage(JSON.parse(data[0].value), '获取TTS配置成功', 'TTS_GetTTSOptions') } catch (error) { - return errorMessage('获取TTS配置失败,错误信息如下:' + error.toString(), 'TTS_GetTTSCOnfig') - } - } - - /** - * 初始化TTS设置 - */ - async InitTTSSetting() { - let defaultData = { - selectModel: 'edge-tts', - edgeTTS: { - value: 'zh-CN-XiaoxiaoNeural', - gender: 'Female', - label: '晓晓', - lang: 'zh-CN', - saveSubtitles: true, - pitch: 0, // 语调 - rate: 10, // 倍速 - volumn: 0 // 音量 - } - } - await this.SaveTTSConfig(defaultData) - return defaultData - } - - /** - * 获取TTS配置 - */ - // @ts-ignore - async GetTTSCOnfig(): Promise { - try { - await this.InitService() - let res = this.softService.GetSoftWarePropertyData('ttsSetting') - let resObj = undefined - if (isEmpty(res)) { - // 没有数据,需要初始化 - resObj = await this.InitTTSSetting() - } else { - let tryParse = ValidateJson(res) - if (!tryParse) { - throw new Error('解析TTS配置失败,数据格式不正确') - } - resObj = JSON.parse(res) - } - return successMessage(resObj, '获取TTS配置成功', 'TTS_GetTTSCOnfig') - } catch (error) { - return errorMessage('获取TTS配置失败,错误信息如下:' + error.toString(), 'TTS_GetTTSCOnfig') - } - } - /** - * 保存TTS配置 - * @param {*} data 要保存的数据 - */ - // @ts-ignore - async SaveTTSConfig(data: TTSSettingModel.TTSSetting) { - try { - await this.InitService() - let res = this.softService.SaveSoftwarePropertyData('ttsSetting', JSON.stringify(data)) - return res - } catch (error) { - return errorMessage('保存TTS配置失败,错误信息如下:' + error.toString(), 'TTS_SaveTTSConfig') + return errorMessage('获取TTS配置失败,错误信息如下:' + error.toString(), 'TTS_GetTTSOptions') } } @@ -138,12 +86,17 @@ export class TTS { * 生成音频 * @param text 要生成的文本 */ - async GenerateAudio(text: string) { + async GenerateAudio() { try { await this.InitService() - let ttsSetting = await this.GetTTSCOnfig() - if (ttsSetting.code === 0) { - return ttsSetting + let TTS_GlobalSetting = await this.optionServices.GetOptionByKey(OptionKeyName.TTS_GlobalSetting); + if (TTS_GlobalSetting.code == 0) { + throw new Error(TTS_GlobalSetting.message); + } + let TTS_GlobalSettingData = JSON.parse(TTS_GlobalSetting.data.value) as OptionModel.TTS_GlobalSettingModel + let text = TTS_GlobalSettingData.ttsText; + if (isEmpty(text)) { + throw new Error('生成音频失败,文本为空') } let res = undefined @@ -156,13 +109,13 @@ export class TTS { await fs.promises.writeFile(textPath, text, 'utf-8') let audioPath = path.join(define.tts_path, `${thisId}/${thisId}.mp3`) - let selectModel = ttsSetting.data.selectModel as TTSSelectModel + let selectModel = TTS_GlobalSettingData.selectModel; let hasSrt = true switch (selectModel) { case TTSSelectModel.edgeTTS: - hasSrt = ttsSetting.data.edgeTTS.saveSubtitles - res = await this.GenerateAudioByEdgeTTS(text, ttsSetting.data.edgeTTS, audioPath) + hasSrt = TTS_GlobalSettingData.edgeTTS.saveSubtitles + res = await this.GenerateAudioByEdgeTTS(text, TTS_GlobalSettingData.edgeTTS, audioPath) break default: throw new Error('未知的TTS模式') @@ -182,7 +135,7 @@ export class TTS { id: thisId, textPath: textPath ? path.relative(define.tts_path, textPath) : null }) - return res + return successMessage(res, '生成音频成功', 'TTS_GenerateAudio') } catch (error) { return errorMessage('生成音频失败,错误信息如下:' + error.toString(), 'TTS_GenerateAudio') } @@ -194,9 +147,9 @@ export class TTS { * @param edgeTTS edgetts的设置 * @returns */ - async GenerateAudioByEdgeTTS(text: string, edgeTTS: TTSSettingModel.EdgeTTSSetting, mp3Path: string) { + async GenerateAudioByEdgeTTS(text: string, edgeTTS: OptionModel.TTS_EdgeTTSModel, mp3Path: string) { try { - const tts = new EdgeTTS({ + const edgeTts = new EdgeTTS({ voice: edgeTTS.value, lang: edgeTTS.lang, outputFormat: 'audio-24khz-96kbitrate-mono-mp3', @@ -204,10 +157,9 @@ export class TTS { pitch: `${edgeTTS.pitch}%`, rate: `${edgeTTS.rate}%`, volume: `${edgeTTS.volumn}%`, - timeout : 100000 + timeout: edgeTTS.timeOut ?? 100000 }) - let ttsRes = await tts.ttsPromise(text, mp3Path) - console.log(ttsRes) + await edgeTts.ttsPromise(text, mp3Path) return { mp3Path: mp3Path, srtJsonPath: mp3Path + '.json' diff --git a/src/main/Service/writing.ts b/src/main/Service/writing.ts index 7ebf00c..d72a094 100644 --- a/src/main/Service/writing.ts +++ b/src/main/Service/writing.ts @@ -5,7 +5,6 @@ import { DEFINE_STRING } from '../../define/define_string' import { PublicMethod } from '../Public/publicMethod' import { define } from '../../define/define' import { get, has, isEmpty } from 'lodash' -import { ClipSetting } from '../../define/setting/clipSetting' import { errorMessage, successMessage } from '../Public/generalTools' import { ServiceBase } from '../../define/db/service/serviceBase' import { @@ -25,7 +24,7 @@ export class Writing extends ServiceBase { constructor(global) { super() this.pm = new PublicMethod(global) - axios.defaults.baseURL = define.serverUrl + axios.defaults.baseURL = define.lms } /** @@ -105,7 +104,7 @@ export class Writing extends ServiceBase { let resData = '\n\n'; return new Promise((resolve, reject) => { - fetch(define.serverUrl + "/lms/Forward/ForwardWordStream", requestOptions) + fetch(define.lms + "/lms/Forward/ForwardWordStream", requestOptions) .then(response => { if (!response.body) { throw new Error('ReadableStream not yet supported in this browser.'); @@ -241,10 +240,6 @@ export class Writing extends ServiceBase { }, 3, 1000) } } - // let tasks = - - // console.log("ActionStart", result); - // ExecuteConcurrently return successMessage(result, "执行文案相关任务成功", 'Writing_ActionStart'); } catch (error) { return errorMessage( diff --git a/src/main/setting/gptSetting.ts b/src/main/setting/gptSetting.ts index 45f7883..4728c70 100644 --- a/src/main/setting/gptSetting.ts +++ b/src/main/setting/gptSetting.ts @@ -17,7 +17,7 @@ export class GptSetting extends ServiceBase { subtitleService: SubtitleService constructor() { super() - axios.defaults.baseURL = define.serverUrl + axios.defaults.baseURL = define.lms this.softWareServiceBasic = new SoftWareServiceBasic(); this.subtitleService = new SubtitleService() } diff --git a/src/model/Setting/softwareSetting.d.ts b/src/model/Setting/softwareSetting.d.ts index 09bc433..79c33b5 100644 --- a/src/model/Setting/softwareSetting.d.ts +++ b/src/model/Setting/softwareSetting.d.ts @@ -21,6 +21,7 @@ declare namespace SoftwareSettingModel { project_name: string = undefined // 项目名称 gpt_business: string = undefined // GPT服务商ID gpt_model: string = undefined // GPT模型 + useTransfer: boolean = false // 是不是使用转发 task_number: number = undefined // 任务数量 theme: string = undefined // 主题 gpt_auto_inference: string = undefined // GPT自动推理模式 diff --git a/src/model/generalResponse.d.ts b/src/model/generalResponse.d.ts index d2be0f8..d27ae98 100644 --- a/src/model/generalResponse.d.ts +++ b/src/model/generalResponse.d.ts @@ -36,3 +36,16 @@ declare namespace GeneralResponse { data?: MJ.MJResponseToFront | Buffer | string | TranslateModel.TranslateResponseMessageModel | ProgressResponse | SubtitleProgressResponse } } + + +export type SuccessItem = { + code: number + message?: string + data: any +} + +export type ErrorItem = { + code: number + message: string + data?: any +} \ No newline at end of file diff --git a/src/model/option/option.d.ts b/src/model/option/option.d.ts index e474f9d..9bf418d 100644 --- a/src/model/option/option.d.ts +++ b/src/model/option/option.d.ts @@ -1,10 +1,116 @@ import { OptionType } from "@/define/enum/option" -declare namespace Option { - /** option的model */ +declare namespace OptionModel { + /** + * Option的模型 + */ type OptionItem = { key: string, value: string, type: OptionType } -} \ No newline at end of file + + //#region 文案处理 + + /** + * 文案处理 AI设置模型 + */ + type CW_AISettingModel = { + /** API key */ + api_key: string, + /** 调用的AI地址,支持 OPEN AI 请求格式的 */ + gpt_url: string, + /** 调用的模型名字 */ + model: string + } + + /** + * 文案处理数据的数据格式数据类型 + */ + type CW_AISimpleSettingModel_WordStruct = { + /** ID */ + id: string, + /** AI改写前的文案 */ + oldWord: string | undefined, + /** AI输出的文案 */ + newWord: string | undefined + /** AI改写前的文案的字数 */ + oldWordCount: number, + /** AI输出的文案的字数 */ + newWordCount: number + } + + /** + * 文案处理 简单AI设置模型 + */ + type CW_AISimpleSettingModel = { + /** 预设的类型 */ + gptType: string | undefined, + /** 选择的预设 */ + gptData: string | undefined, + /** 选择的AI站点,默认LAI API */ + gptAI: string | undefined, + /** 是不是流式请求 */ + isStream: boolean, + /** 是不是对文案内容进行分割 按照设置的 splitNumber,默认为500 */ + isSplit: boolean, + /** 分割字符 */ + splitNumber: number, + /** AI改写前的文案 */ + oldWord: string | undefined, + /** AI输出的文案 */ + newWord: string | undefined, + /** AI改写前的文案的字数 */ + oldWordCount: number, + /** AI输出的文案的字数 */ + newWordCount: number, + /** 文案数据的数据格式数据类型,数组 */ + wordStruct: Array + } + + //#endregion + + + //#region TTS + + /** + * tts所有的配置数据 + */ + type TTS_GlobalSettingModel = { + /** 选择的TTS模型 */ + selectModel: string, + /** TTS模型的数据 */ + edgeTTS: TTS_EdgeTTSModel, + /** 合成语音的文本 */ + ttsText?: string, + /** 保存的音频文件路径 */ + saveAudioPath?: string, + } + + /** EdgeTTS模型的设置 */ + type TTS_EdgeTTSModel = { + /** 选择的TTS模型值 */ + value: string, + /** 选择的TTS模型的性别 */ + gender: string, + /** 显示的名称 */ + label: string, + /** 语言 */ + lang: string, + /** 是否保存字幕 */ + saveSubtitles: boolean, + /** 音调 */ + pitch: number, + /** 语速 */ + rate: number, + /** 音量 */ + volumn: number, + /** 超时时间,单位 毫秒 */ + timeOut: number + } + + + + //#endregion + +} diff --git a/src/preload/index.js b/src/preload/index.js index fdbe79f..1078f9b 100644 --- a/src/preload/index.js +++ b/src/preload/index.js @@ -16,6 +16,7 @@ import { db } from './db' import { translate } from './translate' import { preset } from './preset' import { task } from './task' +import { options } from './options' // Custom APIs for renderer let events = [] @@ -478,6 +479,7 @@ if (process.contextIsolated) { contextBridge.exposeInMainWorld('db', db) contextBridge.exposeInMainWorld('preset', preset) contextBridge.exposeInMainWorld('task', task) + contextBridge.exposeInMainWorld('options', options) contextBridge.exposeInMainWorld('darkMode', { toggle: (value) => ipcRenderer.invoke('dark-mode:toggle', value) }) @@ -502,4 +504,5 @@ if (process.contextIsolated) { window.preset = preset window.task = task window.translate = translate + window.options = options } diff --git a/src/preload/options.ts b/src/preload/options.ts new file mode 100644 index 0000000..bc8f4af --- /dev/null +++ b/src/preload/options.ts @@ -0,0 +1,22 @@ +import { ipcRenderer } from 'electron' +import { DEFINE_STRING } from '../define/define_string' +import { OptionType } from '@/define/enum/option' + +const options = { + /** 通过Key获取指定的option */ + GetOptionByKey: async (key: string) => + await ipcRenderer.invoke(DEFINE_STRING.OPTIONS.GET_OPTION_BY_KEY, key), + + /** + * 修改指定的Option 通过key + */ + ModifyOptionByKey: async (key: string, value: string, type: OptionType) => + await ipcRenderer.invoke(DEFINE_STRING.OPTIONS.MODIFY_OPTION_BY_KEY, key, value, type), + + /** + * 初始化文案处理AI设置 + * @returns + */ + InitCopyWritingAISetting: async () => await ipcRenderer.invoke(DEFINE_STRING.OPTIONS.INIT_COPY_WRITING_AI_SETTING) +} +export { options } diff --git a/src/preload/tts.ts b/src/preload/tts.ts index 2c0f375..3c7ee67 100644 --- a/src/preload/tts.ts +++ b/src/preload/tts.ts @@ -2,14 +2,9 @@ import { ipcRenderer } from 'electron' import { DEFINE_STRING } from '../define/define_string' const tts = { - // 获取当前的TTS配置数据 - GetTTSCOnfig: async () => await ipcRenderer.invoke(DEFINE_STRING.TTS.GET_TTS_CONFIG), - - // 保存TTS配置 - SaveTTSConfig: async (data) => await ipcRenderer.invoke(DEFINE_STRING.TTS.SAVE_TTS_CONFIG, data), // 生成音频 - GenerateAudio: async (text) => await ipcRenderer.invoke(DEFINE_STRING.TTS.GENERATE_AUDIO, text), + GenerateAudio: async () => await ipcRenderer.invoke(DEFINE_STRING.TTS.GENERATE_AUDIO), // 生成SRT字幕 GenerateSrt: async (text) => await ipcRenderer.invoke(DEFINE_STRING.TTS.GENERATE_SRT, text), diff --git a/src/preload/write.ts b/src/preload/write.ts index c607478..6f68b56 100644 --- a/src/preload/write.ts +++ b/src/preload/write.ts @@ -27,10 +27,15 @@ const write = { //#region AI相关的任务 - // 开始执行API相关的一系列任务 - ActionStart(aiSetting, word) { - return ipcRenderer.invoke(DEFINE_STRING.WRITE.ACTION_START, aiSetting, word) + /** + * AI处理文案 + * @param ids 需要改写的文案ID + * @returns + */ + CopyWritingAIGeneration(ids: string[]) { + return ipcRenderer.invoke(DEFINE_STRING.WRITE.COPY_WRITING_AI_GENERATION, ids) }, + //#endregion //#region 文案洗稿相关 diff --git a/src/renderer/src/common/copyWriting.ts b/src/renderer/src/common/copyWriting.ts new file mode 100644 index 0000000..1cf580d --- /dev/null +++ b/src/renderer/src/common/copyWriting.ts @@ -0,0 +1,72 @@ +// @ts-nocheck +import { OptionKeyName, OptionType } from "@/define/enum/option"; +import { useOptionStore } from "@/stores/option"; +import { isEmpty } from "lodash"; +import TextCommon from "./text"; + +/** + * 保存CWAISimpleSetting + */ +async function SaveCWAISimpleSetting() { + let optionStore = useOptionStore(); + let saveRes = await window.options.ModifyOptionByKey(OptionKeyName.CW_AISimpleSetting, JSON.stringify(optionStore.CW_AISimpleSetting), OptionType.JOSN); + if (saveRes.code == 0) { + throw new Error(saveRes.message); + } +} + +/** + * 重新分割或者是合并旧文案 + * @param isSplit 这个默认为 + */ +async function SplitOrMergeOldText(isSplit: boolean = true, inputWord: string | undefined = undefined) { + let optionStore = useOptionStore(); + let wordStr = ""; + + if (inputWord) { + wordStr = inputWord + } else { + let wordStruct = optionStore.CW_AISimpleSetting.wordStruct + if (!wordStruct || wordStruct.length <= 0) { + throw new Error('分割合并文案失败:没有数据(wordStruct)') + } + wordStr = wordStruct.map((item) => item.oldWord).join('\n') + if (isEmpty(wordStr)) { + throw new Error('分割合并文案失败:没有数据(wordStr)') + } + } + // 分割文案 + if (isSplit) { + let splits = TextCommon.SplitTextIntoChunks( + wordStr, + optionStore.CW_AISimpleSetting.splitNumber + ) + + optionStore.CW_AISimpleSetting.wordStruct = [] + + splits.map((item) => { + optionStore.CW_AISimpleSetting.wordStruct.push({ + oldWord: item, + newWord: '', + id: crypto.randomUUID() + }) + }) + await CopyWriting.SaveCWAISimpleSetting() + } else { + optionStore.CW_AISimpleSetting.wordStruct = [] + // 将文案合并为一个文案 + optionStore.CW_AISimpleSetting.wordStruct.push({ + oldWord: wordStr, + newWord: '', + id: crypto.randomUUID() + }) + await CopyWriting.SaveCWAISimpleSetting() + } +} + + +let CopyWriting = { + SaveCWAISimpleSetting, + SplitOrMergeOldText +}; +export default CopyWriting; diff --git a/src/renderer/src/common/initCommon.ts b/src/renderer/src/common/initCommon.ts new file mode 100644 index 0000000..b27f34d --- /dev/null +++ b/src/renderer/src/common/initCommon.ts @@ -0,0 +1,97 @@ +// @ts-nocheck +import { OptionKeyName, OptionType } from '@/define/enum/option' +import { useOptionStore } from '@/stores/option' + +/** + * 初始化特殊字符数据,用于文案的相关处理 + * @returns + */ +async function InitSpecialCharacters() { + let optionStore = useOptionStore() + let specialCharacters = `。,“”‘’!?【】「」《》()…—;,''""!?[]<>()-:;╰*°▽°*╯′,ノ﹏<o‵゚Д゚,ノ,へ ̄╬▔` + + let specialCharactersData = await window.options.GetOptionByKey( + OptionKeyName.CW_FormatSpecialChar + ) + if (specialCharactersData.code == 0) { + // 获取失败 + window.api.showGlobalMessageDialog(specialCharactersData) + return + } + + if (specialCharactersData.data == null) { + // 没有数据初始化 + let saveRes = await window.options.ModifyOptionByKey( + OptionKeyName.CW_FormatSpecialChar, + specialCharacters, + OptionType.STRING + ) + if (saveRes.code == 0) { + window.api.showGlobalMessageDialog(saveRes) + return + } else { + optionStore.CW_FormatSpecialChar = specialCharacters + } + } else { + optionStore.CW_FormatSpecialChar = specialCharactersData.data.value + } +} + + +/** + * 初始化TTS设置 + * @returns + */ +async function InitTTSGlobalSetting() { + debugger + let optionStore = useOptionStore() + let initData = { + selectModel: "edge-tts", + edgeTTS: { + "value": "zh-CN-XiaoyiNeural", + "gender": "Female", + "label": "中文-女-小宜", + "lang": "zh-CN", + "saveSubtitles": true, + "pitch": 0, + "rate": 10, + "volumn": 0, + "timeOut": 120000 + }, + ttsText: "你好,我是你的智能语音助手!", + /** 保存的音频文件路径 */ + saveAudioPath: undefined, + }; + + let TTS_GlobalSetting = await window.options.GetOptionByKey( + OptionKeyName.TTS_GlobalSetting + ) + if (TTS_GlobalSetting.code == 0) { + // 获取失败 + window.api.showGlobalMessageDialog(TTS_GlobalSetting) + return + } + + if (TTS_GlobalSetting.data == null) { + // 没有数据初始化 + let saveRes = await window.options.ModifyOptionByKey( + OptionKeyName.TTS_GlobalSetting, + JSON.stringify(initData), + OptionType.JOSN + ) + if (saveRes.code == 0) { + window.api.showGlobalMessageDialog(saveRes) + return + } else { + optionStore.TTS_GlobalSetting = initData + } + } else { + optionStore.TTS_GlobalSetting = JSON.parse(TTS_GlobalSetting.data.value) + } +} + +let InitCommon = { + InitSpecialCharacters, + InitTTSGlobalSetting +} +export default InitCommon diff --git a/src/renderer/src/common/text.ts b/src/renderer/src/common/text.ts new file mode 100644 index 0000000..1558722 --- /dev/null +++ b/src/renderer/src/common/text.ts @@ -0,0 +1,133 @@ +import { useOptionStore } from "@/stores/option" + +/** + * 格式化文本,通过自定义的特殊字符进行格式化 + * @param oldText 需要格式化的文本 + * @returns 返回格式化后的文本 + */ +async function FormatText(oldText: string): Promise { + // 专用正则转义函数 + function escapeRegExp(char) { + const regexSpecialChars = [ + '\\', + '.', + '*', + '+', + '?', + '^', + '$', + '{', + '}', + '(', + ')', + '[', + ']', + '|', + '/' + ] + return regexSpecialChars.includes(char) ? `\\${char}` : char + } + + try { + + let optionStore = useOptionStore() + // 1. 获取特殊字符数组并过滤数字(可选) + const specialChars = Array.from(optionStore.CW_FormatSpecialChar) + // 如果确定不要数字可以加过滤:.filter(c => !/\d/.test(c)) + + // 2. 处理连字符的特殊情况 + const processedChars = specialChars.map((char) => { + // 优先处理连字符(必须第一个处理) + if (char === '-') return { char, escaped: '\\-' } + return { char, escaped: escapeRegExp(char) } + }) + + // 3. 构建正则表达式字符集 + const regexParts = [] + processedChars.forEach(({ char, escaped }) => { + // 单独处理连字符位置 + if (char === '-') { + regexParts.unshift(escaped) // 将连字符放在字符集开头 + } else { + regexParts.push(escaped) + } + }) + + // 4. 创建正则表达式 + const regex = new RegExp(`[${regexParts.join('')}]`, 'gu') + + // 5. 后续替换和过滤逻辑保持不变... + let content = oldText.replace(regex, '\n') + + const lines = content + .split('\n') + .map((line) => line.trim()) + .filter((line) => line !== '') + + // word.value = lines.join('\n') + let newContent = lines.join('\n') + return newContent + } catch (error) { + throw new Error("格式化文本失败,失败信息如下:" + error.message) + } +} + +/** + * 将文本按最大长度分割成多个块 + * @param text 要分割的文本 + * @param maxLength 每个块的最大长度 + * @returns 分割后的文本块数组 + */ +function SplitTextIntoChunks(text: string, maxLength: number): string[] { + const lines = text.split('\n'); + const result: string[] = []; + let currentBlock: string[] = []; + let currentLength = 0; + + for (const line of lines) { + const lineLength = line.length; + // 计算添加当前行后的新长度(包括换行符) + const newLength = currentLength === 0 + ? lineLength + : currentLength + 1 + lineLength; + + if (newLength > maxLength) { + if (currentBlock.length > 0) { + // 提交当前块并重置 + result.push(currentBlock.join('\n')); + currentBlock = []; + currentLength = 0; + + // 重新尝试添加当前行到新块 + if (lineLength > maxLength) { + // 行单独超过最大长度,直接作为独立块 + result.push(line); + } else { + currentBlock.push(line); + currentLength = lineLength; + } + } else { + // 当前块为空且行超过最大长度,强制作为独立块 + result.push(line); + } + } else { + // 可以安全添加到当前块 + currentBlock.push(line); + currentLength = newLength; + } + } + + // 处理最后一个未提交的块 + if (currentBlock.length > 0) { + result.push(currentBlock.join('\n')); + } + + return result; +} + +let TextCommon = { + FormatText, + SplitTextIntoChunks +} + +export default TextCommon; diff --git a/src/renderer/src/common/ttsCommon.ts b/src/renderer/src/common/ttsCommon.ts new file mode 100644 index 0000000..6dc95a4 --- /dev/null +++ b/src/renderer/src/common/ttsCommon.ts @@ -0,0 +1,20 @@ +// @ts-nocheck +import { OptionKeyName, OptionType } from "@/define/enum/option"; +import { useOptionStore } from "@/stores/option"; + +/** + * 保存TTS的全局视图数据到数据库 + */ +async function SaveTTSGlobalSetting() { + let optionStore = useOptionStore(); + let saveRes = await window.options.ModifyOptionByKey(OptionKeyName.TTS_GlobalSetting, JSON.stringify(optionStore.TTS_GlobalSetting), OptionType.JOSN); + if (saveRes.code == 0) { + throw new Error(saveRes.message); + } +} + +let TTSCommon = { + SaveTTSGlobalSetting +}; + +export default TTSCommon; \ No newline at end of file diff --git a/src/renderer/src/components/Book/Components/ImportWord/ImportWordAndSrt.vue b/src/renderer/src/components/Book/Components/ImportWord/ImportWordAndSrt.vue index e3506d6..f4df65f 100644 --- a/src/renderer/src/components/Book/Components/ImportWord/ImportWordAndSrt.vue +++ b/src/renderer/src/components/Book/Components/ImportWord/ImportWordAndSrt.vue @@ -61,7 +61,7 @@ export default defineComponent({ setup(props) { let hh = props.height let maxHeight = props.height - 80 - + let data = ref(JSON.parse(JSON.stringify(props.initData))) const message = useMessage() let dialog = useDialog() @@ -163,13 +163,77 @@ export default defineComponent({ } }, { - title: '时间范围', + title: () => { + return h('div', { style: 'display: flex; align-items: center' }, [ + h('span', '时间范围'), + h( + NButton, + { + size: 'tiny', + style: 'margin-left: 4px', + strong: true, + secondary: true, + type: 'info', + onClick: () => { + CheckTimeLime() + } + }, + { default: () => '时间轴检查' } + ) + ]) + }, key: 'timeLimit', width: 120 } ] } + /** + * 强制对齐字幕的时间轴 + */ + function CheckTimeLime() { + dialog.create({ + type: 'warning', + title: '警告', + showIcon: true, + content: '确定要检查时间轴吗?该操作会更具对应的字幕进行时间的强制对齐,是不是继续操作?', + style: `width : 400px;`, + maskClosable: false, + positiveText: '确定', + negativeText: '取消', + onPositiveClick: async () => { + // 检查时间轴逻辑 + console.log('CheckTimeLime', data.value) + for (let i = 0; i < data.value.length; i++) { + const element = data.value[i] + if (!element.subValue || element.subValue.length <= 0) { + message.error(`第${i + 1}行字幕为空,请检查`) + return + } + let startTime = element.subValue[0].start_time ?? 0 + let endTime = element.subValue[element.subValue.length - 1].end_time ?? 0 + if (endTime == 0) { + message.error(`第${i + 1}行字幕结束时间为空,请检查`) + return + } + + // 开始写入时间 + data.value[i].start_time = startTime + data.value[i].end_time = endTime + data.value[i].timeLimit = `${startTime} -- ${endTime}` + } + // 提示 + window.api.showGlobalMessageDialog({ + code: 1, + message: '时间轴检查和改写完成,请手动保存!!!' + }) + }, + onNegativeClick: () => { + message.info('取消操作') + } + }) + } + // 设置窗体的高度 function setHeight() { let div = document.getElementById('import_word_and_srt') @@ -210,7 +274,6 @@ export default defineComponent({ style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`, maskClosable: false, onClose: async () => { - console.log(wenkeRef.value.word) let word = wenkeRef.value.data if (word == null || word == '') { @@ -565,7 +628,6 @@ export default defineComponent({ }) } } else if (type.value == 'mj_reverse' || type.value == 'sd_reverse') { - if (data.value.length != reverseManageStore.selectBookTaskDetail.length) { message.error('检测到导入的字幕数据和分镜对不上,请先对齐') return diff --git a/src/renderer/src/components/Book/Components/ManageBook/BookListAction.vue b/src/renderer/src/components/Book/Components/ManageBook/BookListAction.vue index 0fd7eda..1aa4403 100644 --- a/src/renderer/src/components/Book/Components/ManageBook/BookListAction.vue +++ b/src/renderer/src/components/Book/Components/ManageBook/BookListAction.vue @@ -98,7 +98,6 @@ async function ResetBookData(e) { async function DeleteBookData(e) { e.stopPropagation() - message.info('删除小说数据 ' + book.value.id) let da = dialog.warning({ title: '删除小说数据警告', content: @@ -107,7 +106,6 @@ async function DeleteBookData(e) { negativeText: '取消', onPositiveClick: async () => { da?.destroy() - softwareStore.spin.spinning = true softwareStore.spin.tip = '正在删除小说数据。。。' let res = await window.book.DeleteBookData(book.value.id) @@ -116,7 +114,6 @@ async function DeleteBookData(e) { message.error(res.message) return } - // 这边直接把数据删除了就行 reverseManageStore.bookData = reverseManageStore.bookData.filter( (item) => item.id != book.value.id diff --git a/src/renderer/src/components/Book/MJReverse/ManageBookDetailButton.vue b/src/renderer/src/components/Book/MJReverse/ManageBookDetailButton.vue index 3b740d8..39a0332 100644 --- a/src/renderer/src/components/Book/MJReverse/ManageBookDetailButton.vue +++ b/src/renderer/src/components/Book/MJReverse/ManageBookDetailButton.vue @@ -940,7 +940,9 @@ async function ImportWordAndSrtFunc() { subValue: element.subValue ? element.subValue : [], name: element.name + '.png', prompt: element.prompt, - timeLimit: element.timeLimit + timeLimit: element.timeLimit, + start_time: element.startTime, + end_time: element.endTime }) } diff --git a/src/renderer/src/components/Book/Original/OriginalMainButton.vue b/src/renderer/src/components/Book/Original/OriginalMainButton.vue index 4db3a04..1adc063 100644 --- a/src/renderer/src/components/Book/Original/OriginalMainButton.vue +++ b/src/renderer/src/components/Book/Original/OriginalMainButton.vue @@ -134,7 +134,9 @@ async function ImportWord() { subValue: element.subValue ? element.subValue : [], name: element.name + '.png', prompt: element.prompt, - timeLimit: element.timeLimit + timeLimit: element.timeLimit, + start_time: element.startTime, + end_time: element.endTime }) } diff --git a/src/renderer/src/components/CopyWriting/CWInputWord.vue b/src/renderer/src/components/CopyWriting/CWInputWord.vue new file mode 100644 index 0000000..2071bc9 --- /dev/null +++ b/src/renderer/src/components/CopyWriting/CWInputWord.vue @@ -0,0 +1,126 @@ + + + diff --git a/src/renderer/src/components/CopyWriting/CopyWritingContent.vue b/src/renderer/src/components/CopyWriting/CopyWritingContent.vue new file mode 100644 index 0000000..7432d43 --- /dev/null +++ b/src/renderer/src/components/CopyWriting/CopyWritingContent.vue @@ -0,0 +1,452 @@ + + + diff --git a/src/renderer/src/components/CopyWriting/CopyWritingHome.vue b/src/renderer/src/components/CopyWriting/CopyWritingHome.vue new file mode 100644 index 0000000..211e77f --- /dev/null +++ b/src/renderer/src/components/CopyWriting/CopyWritingHome.vue @@ -0,0 +1,106 @@ + + + diff --git a/src/renderer/src/components/CopyWriting/CopyWritingShowAIGenerate.vue b/src/renderer/src/components/CopyWriting/CopyWritingShowAIGenerate.vue new file mode 100644 index 0000000..8b55e8b --- /dev/null +++ b/src/renderer/src/components/CopyWriting/CopyWritingShowAIGenerate.vue @@ -0,0 +1,88 @@ + + + diff --git a/src/renderer/src/components/CopyWriting/CopyWriting.vue b/src/renderer/src/components/CopyWriting/CopyWritingSimpleSetting.vue similarity index 54% rename from src/renderer/src/components/CopyWriting/CopyWriting.vue rename to src/renderer/src/components/CopyWriting/CopyWritingSimpleSetting.vue index ba106cc..3f98e42 100644 --- a/src/renderer/src/components/CopyWriting/CopyWriting.vue +++ b/src/renderer/src/components/CopyWriting/CopyWritingSimpleSetting.vue @@ -1,11 +1,11 @@ diff --git a/src/renderer/src/components/Setting/Setting.vue b/src/renderer/src/components/Setting/Setting.vue index 48d46e8..e237b21 100644 --- a/src/renderer/src/components/Setting/Setting.vue +++ b/src/renderer/src/components/Setting/Setting.vue @@ -112,15 +112,16 @@ v-if="formValue.gpt_business == 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65'" style="width: 120px; margin-left: 30px" path="gpt_key" - label="LAI 站点选择" + label="是否国内转发" > - + v-model:value="formValue.useTransfer" + :options="[ + { label: '是', value: true }, + { label: '否', value: false } + ]" + style="width: 100px" + /> - + - + + + + diff --git a/src/renderer/src/components/TTS/TTSHome.vue b/src/renderer/src/components/TTS/TTSHome.vue index 76a8751..0c108c5 100644 --- a/src/renderer/src/components/TTS/TTSHome.vue +++ b/src/renderer/src/components/TTS/TTSHome.vue @@ -1,66 +1,28 @@ + + diff --git a/src/renderer/src/main.js b/src/renderer/src/main.js index bda0c65..2477472 100644 --- a/src/renderer/src/main.js +++ b/src/renderer/src/main.js @@ -16,7 +16,7 @@ const routes = [ { path: '/gptCopywriting', name: 'gptCopywriting', - component: () => import('./components/CopyWriting/CopyWriting.vue') + component: () => import('./components/CopyWriting/CopyWritingHome.vue') }, { path: '/global_setting', diff --git a/src/stores/option.ts b/src/stores/option.ts new file mode 100644 index 0000000..372f2c1 --- /dev/null +++ b/src/stores/option.ts @@ -0,0 +1,74 @@ +import { OptionKeyName } from "@/define/enum/option"; +import { OptionModel } from "@/model/option/option"; +import { defineStore } from "pinia"; + +export type OptionStoreModel = { + //#region + + /** 文案处理 AI设置 */ + [OptionKeyName.CW_AISetting]: { + laiapi: OptionModel.CW_AISettingModel + }; + /** 文案处理数据界面数据 */ + [OptionKeyName.CW_AISimpleSetting]: OptionModel.CW_AISimpleSettingModel | undefined; + + /** 格式化的特殊字符数据 */ + [OptionKeyName.CW_FormatSpecialChar]: string | undefined; + + //#endregion + + //#region TTS + + /** TTS界面视图数据 */ + [OptionKeyName.TTS_GlobalSetting]: OptionModel.TTS_GlobalSettingModel | undefined; + + //#endregion +} + +export const useOptionStore = defineStore('option', { + state: () => ({ + [OptionKeyName.CW_AISetting]: { + laiapi: { + api_key: '', + gpt_url: '', + model: '' + } + }, + [OptionKeyName.CW_AISimpleSetting]: { + gptType: undefined, + gptData: undefined, + gptAI: 'laiapi', + isStream: false, + isSplit: false, + splitNumber: 500, + oldWord: '', + newWord: '', + oldWordCount: 0, + newWordCount: 0, + wordStruct: [] + }, + [OptionKeyName.CW_FormatSpecialChar]: undefined, + [OptionKeyName.TTS_GlobalSetting]: { + selectModel: "edge-tts", + edgeTTS: { + "value": "zh-CN-XiaoyiNeural", + "gender": "Female", + "label": "中文-女-小宜", + "lang": "zh-CN", + "saveSubtitles": true, + "pitch": 0, + "rate": 10, + "volumn": 0, + timeOut: 120000, + }, + ttsText: "你好,我是你的智能语音助手!", + /** 保存的音频文件路径 */ + saveAudioPath: undefined, + } + + } as unknown as OptionStoreModel), + getters: { + }, + actions: { + } +}); \ No newline at end of file