V 3.2.3
1.优化文案处理逻辑,重构界面 2.修复批量导出草稿只能导出一个的bug 3.添加自动 推理人物 场景 方便快速生成标签 4.(聚合推文) 修复删除数据bug 5.新增推理国内转发接口(包括翻译) 6.新增文案导入时导入SRT后可手动校验一遍时间数据,简化简单过程 7.语音服务那边添加字符不生效,格式化不生效 8.优化语音服务(数据结构优化,可设置合成超时时间)
This commit is contained in:
parent
1ce665a3e5
commit
b0eb7795e4
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "laitool",
|
"name": "laitool",
|
||||||
"version": "3.2.2",
|
"version": "3.2.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "laitool",
|
"name": "laitool",
|
||||||
"version": "3.2.2",
|
"version": "3.2.3",
|
||||||
"description": "An AI tool for image processing, video processing, and other functions.",
|
"description": "An AI tool for image processing, video processing, and other functions.",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "laitool.cn",
|
"author": "laitool.cn",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -6,6 +6,9 @@
|
|||||||
*/
|
*/
|
||||||
export function ValidateJson(str: string): boolean {
|
export function ValidateJson(str: string): boolean {
|
||||||
try {
|
try {
|
||||||
|
if (str == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
JSON.parse(str);
|
JSON.parse(str);
|
||||||
return true
|
return true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { OtherData } from '../../../enum/softwareEnum.js'
|
|||||||
import { BookBackTaskList } from '../../model/Book/BookBackTaskListModel.js'
|
import { BookBackTaskList } from '../../model/Book/BookBackTaskListModel.js'
|
||||||
import { Book } from '../../../../model/book/book.js'
|
import { Book } from '../../../../model/book/book.js'
|
||||||
import { GeneralResponse } from '../../../../model/generalResponse.js'
|
import { GeneralResponse } from '../../../../model/generalResponse.js'
|
||||||
|
import { TaskModal } from '@/model/task.js'
|
||||||
const { v4: uuidv4 } = require('uuid')
|
const { v4: uuidv4 } = require('uuid')
|
||||||
|
|
||||||
export class BookBackTaskListService extends BaseRealmService {
|
export class BookBackTaskListService extends BaseRealmService {
|
||||||
|
|||||||
@ -232,7 +232,8 @@ export class BookService extends BaseRealmService {
|
|||||||
updateTime: new Date(),
|
updateTime: new Date(),
|
||||||
createTime: new Date(),
|
createTime: new Date(),
|
||||||
version: version,
|
version: version,
|
||||||
imageCategory: imageCategory
|
imageCategory: imageCategory,
|
||||||
|
openVideoGenerate: false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加任务
|
// 添加任务
|
||||||
|
|||||||
79
src/define/db/service/SoftWare/optionRealmService.ts
Normal file
79
src/define/db/service/SoftWare/optionRealmService.ts
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -5,6 +5,7 @@ import SETTING from "./settingDefineString"
|
|||||||
import BOOK from "./bookDefineString"
|
import BOOK from "./bookDefineString"
|
||||||
import WRITE from "./writeDefineString"
|
import WRITE from "./writeDefineString"
|
||||||
import DB from "./dbDefineString"
|
import DB from "./dbDefineString"
|
||||||
|
import OPTIONS from "./optionsDefineString"
|
||||||
|
|
||||||
export const DEFINE_STRING = {
|
export const DEFINE_STRING = {
|
||||||
SYSTEM: SYSTEM,
|
SYSTEM: SYSTEM,
|
||||||
@ -14,6 +15,7 @@ export const DEFINE_STRING = {
|
|||||||
SETTING: SETTING,
|
SETTING: SETTING,
|
||||||
WRITE: WRITE,
|
WRITE: WRITE,
|
||||||
DB: DB,
|
DB: DB,
|
||||||
|
OPTIONS:OPTIONS,
|
||||||
SHOW_GLOBAL_MESSAGE: "SHOW_GLOBAL_MESSAGE",
|
SHOW_GLOBAL_MESSAGE: "SHOW_GLOBAL_MESSAGE",
|
||||||
SHOW_GLOBAL_MAIN_NOTIFICATION: 'SHOW_GLOBAL_MAIN_NOTIFICATION',
|
SHOW_GLOBAL_MAIN_NOTIFICATION: 'SHOW_GLOBAL_MAIN_NOTIFICATION',
|
||||||
OPEN_DEV_TOOLS_PASSWORD: 'OPEN_DEV_TOOLS_PASSWORD',
|
OPEN_DEV_TOOLS_PASSWORD: 'OPEN_DEV_TOOLS_PASSWORD',
|
||||||
|
|||||||
19
src/define/define_string/optionsDefineString.ts
Normal file
19
src/define/define_string/optionsDefineString.ts
Normal file
@ -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
|
||||||
@ -1,7 +1,6 @@
|
|||||||
const WRITE = {
|
const WRITE = {
|
||||||
GET_WRITE_CONFIG: 'GET_WRITE_CONFIG',
|
GET_WRITE_CONFIG: 'GET_WRITE_CONFIG',
|
||||||
SAVE_WRITE_CONFIG: 'SAVE_WRITE_CONFIG',
|
SAVE_WRITE_CONFIG: 'SAVE_WRITE_CONFIG',
|
||||||
ACTION_START: 'ACTION_START',
|
|
||||||
GET_SUBTITLE_SETTING: "GET_SUBTITLE_SETTING",
|
GET_SUBTITLE_SETTING: "GET_SUBTITLE_SETTING",
|
||||||
RESET_SUBTITLE_SETTING: "RESET_SUBTITLE_SETTING",
|
RESET_SUBTITLE_SETTING: "RESET_SUBTITLE_SETTING",
|
||||||
SAVE_SUBTITLE_SETTING: "SAVE_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: "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
|
export default WRITE
|
||||||
@ -1,7 +1,40 @@
|
|||||||
/** option 中的type的类型 */
|
/**
|
||||||
|
* Option Value的数据类型,用于数据的格式化
|
||||||
|
*/
|
||||||
export enum OptionType {
|
export enum OptionType {
|
||||||
STRING = 'string',
|
STRING = 'string',
|
||||||
NUMBER = 'number',
|
NUMBER = 'number',
|
||||||
BOOLEAN = 'boolean',
|
BOOLEAN = 'boolean',
|
||||||
JOSN = 'json'
|
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
|
||||||
|
}
|
||||||
@ -8,7 +8,20 @@ import { apiUrl } from './api/apiUrlDefine'
|
|||||||
export const gptDefine = {
|
export const gptDefine = {
|
||||||
// Add properties and methods to the shared object
|
// Add properties and methods to the shared object
|
||||||
characterSystemContent: `{textContent}\r查看上面的文本,然后扮演一个文本编辑来回答问题。`,
|
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.`,
|
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`,
|
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`,
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { TTSIpc } from './ttsIpc'
|
|||||||
import { DBIpc } from './dbIpc'
|
import { DBIpc } from './dbIpc'
|
||||||
import { PresetIpc } from './presetIpc'
|
import { PresetIpc } from './presetIpc'
|
||||||
import { TaskIpc } from './taskIpc'
|
import { TaskIpc } from './taskIpc'
|
||||||
|
import { OptionsIpc } from './optionsIpc'
|
||||||
|
|
||||||
export async function RegisterIpc(createWindow) {
|
export async function RegisterIpc(createWindow) {
|
||||||
PromptIpc()
|
PromptIpc()
|
||||||
@ -38,4 +39,5 @@ export async function RegisterIpc(createWindow) {
|
|||||||
SystemIpc()
|
SystemIpc()
|
||||||
BookIpc()
|
BookIpc()
|
||||||
TTSIpc()
|
TTSIpc()
|
||||||
|
OptionsIpc()
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/main/IPCEvent/optionsIpc.ts
Normal file
33
src/main/IPCEvent/optionsIpc.ts
Normal file
@ -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 }
|
||||||
@ -5,19 +5,11 @@ import { TTS } from '../Service/tts'
|
|||||||
const tts = new TTS()
|
const tts = new TTS()
|
||||||
|
|
||||||
export function TTSIpc() {
|
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(
|
ipcMain.handle(
|
||||||
DEFINE_STRING.TTS.GENERATE_AUDIO,
|
DEFINE_STRING.TTS.GENERATE_AUDIO,
|
||||||
async (event, text) => await tts.GenerateAudio(text)
|
async (event) => await tts.GenerateAudio()
|
||||||
)
|
)
|
||||||
|
|
||||||
// 生成SRT字幕文件
|
// 生成SRT字幕文件
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import { BookPrompt } from '../Service/Book/bookPrompt'
|
|||||||
let subtitleService = new SubtitleService()
|
let subtitleService = new SubtitleService()
|
||||||
const bookPrompt = new BookPrompt();
|
const bookPrompt = new BookPrompt();
|
||||||
|
|
||||||
|
import CopyWritingService from '@/main/Service/copywriting/index'
|
||||||
|
|
||||||
function WritingIpc() {
|
function WritingIpc() {
|
||||||
// 监听分镜时间的保存
|
// 监听分镜时间的保存
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
@ -69,11 +71,18 @@ function WritingIpc() {
|
|||||||
async (event, subtitleSetting) => await subtitleService.SaveSubtitleSetting(subtitleSetting)
|
async (event, subtitleSetting) => await subtitleService.SaveSubtitleSetting(subtitleSetting)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//#region 文案处理
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI处理文案
|
||||||
|
*/
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
DEFINE_STRING.WRITE.ACTION_START,
|
DEFINE_STRING.WRITE.COPY_WRITING_AI_GENERATION,
|
||||||
async (event, aiSetting, word) => await writing.ActionStart(aiSetting, word)
|
async (event, ids: string[]) => await CopyWritingService.CopyWritingAIGeneration(ids)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
//#region 文案洗稿相关
|
//#region 文案洗稿相关
|
||||||
|
|
||||||
/** 生成洗稿后文案 */
|
/** 生成洗稿后文案 */
|
||||||
|
|||||||
@ -351,6 +351,37 @@ export class Translate {
|
|||||||
content: translateData
|
content: translateData
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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 = {
|
let config = {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
maxBodyLength: Infinity,
|
maxBodyLength: Infinity,
|
||||||
@ -369,9 +400,10 @@ export class Translate {
|
|||||||
2000
|
2000
|
||||||
)
|
)
|
||||||
// 将返回的数据进行拼接数据处理
|
// 将返回的数据进行拼接数据处理
|
||||||
|
content = GetOpenAISuccessResponse(res.data)
|
||||||
|
}
|
||||||
|
|
||||||
let res_data = []
|
let res_data = []
|
||||||
let content = GetOpenAISuccessResponse(res.data)
|
|
||||||
|
|
||||||
if (to == 'zh') {
|
if (to == 'zh') {
|
||||||
res_data.push({
|
res_data.push({
|
||||||
|
|||||||
@ -101,9 +101,14 @@ export class BookBasic {
|
|||||||
try {
|
try {
|
||||||
let book = await this.bookServiceBasic.GetBookDataById(bookId)
|
let book = await this.bookServiceBasic.GetBookDataById(bookId)
|
||||||
// 获取所有的小说批次
|
// 获取所有的小说批次
|
||||||
let bookTasks = (await this.bookServiceBasic.GetBookTaskData({
|
let bookTasksObj = (await this.bookServiceBasic.GetBookTaskData({
|
||||||
bookId: bookId
|
bookId: bookId
|
||||||
})).bookTasks;
|
}, true));
|
||||||
|
// 删除之前判断是不是有子批次 没有直接退出
|
||||||
|
let bookTasks = bookTasksObj.bookTasks;
|
||||||
|
if (bookTasks.length == 0) {
|
||||||
|
return successMessage('未找到小说批次数据,正常退出', 'BookBasic_ResetBookData');
|
||||||
|
}
|
||||||
// 重置批次任务
|
// 重置批次任务
|
||||||
for (let i = 0; i < bookTasks.length; i++) {
|
for (let i = 0; i < bookTasks.length; i++) {
|
||||||
const element = bookTasks[i];
|
const element = bookTasks[i];
|
||||||
@ -179,14 +184,19 @@ export class BookBasic {
|
|||||||
if (resetRes.code == 0) {
|
if (resetRes.code == 0) {
|
||||||
throw new Error(resetRes.message)
|
throw new Error(resetRes.message)
|
||||||
}
|
}
|
||||||
let bookTasks = (await this.bookServiceBasic.GetBookTaskData({
|
let bookTasksObj = (await this.bookServiceBasic.GetBookTaskData({
|
||||||
bookId: bookId
|
bookId: bookId
|
||||||
})).bookTasks;
|
}, true))
|
||||||
|
let bookTasks = bookTasksObj.bookTasks;
|
||||||
|
|
||||||
|
// 有数据才删除
|
||||||
|
if (bookTasks.length > 0) {
|
||||||
// 删除遗留重置的小说批次任务
|
// 删除遗留重置的小说批次任务
|
||||||
for (let i = 0; i < bookTasks.length; i++) {
|
for (let i = 0; i < bookTasks.length; i++) {
|
||||||
const element = bookTasks[i];
|
const element = bookTasks[i];
|
||||||
await this.bookServiceBasic.DeleteBookTaskData(element.id);
|
await this.bookServiceBasic.DeleteBookTaskData(element.id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 开始删除数据
|
// 开始删除数据
|
||||||
await this.bookServiceBasic.DeleteBookData(bookId);
|
await this.bookServiceBasic.DeleteBookData(bookId);
|
||||||
|
|||||||
@ -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')
|
return successMessage(null, "推理所有数据完成", 'BookPrompt_OriginalGetPrompt')
|
||||||
|
|
||||||
|
|||||||
@ -227,6 +227,7 @@ export class BookTask {
|
|||||||
this.bookServiceBasic.transaction((realm) => {
|
this.bookServiceBasic.transaction((realm) => {
|
||||||
for (let i = 0; i < bookTasks.length; i++) {
|
for (let i = 0; i < bookTasks.length; i++) {
|
||||||
const element = bookTasks[i];
|
const element = bookTasks[i];
|
||||||
|
element.openVideoGenerate = false
|
||||||
realm.create('BookTask', element)
|
realm.create('BookTask', element)
|
||||||
}
|
}
|
||||||
for (let i = 0; i < bookTaskDetail.length; i++) {
|
for (let i = 0; i < bookTaskDetail.length; i++) {
|
||||||
@ -409,6 +410,7 @@ export class BookTask {
|
|||||||
suffixPrompt: sourceBookTask.suffixPrompt,
|
suffixPrompt: sourceBookTask.suffixPrompt,
|
||||||
version: sourceBookTask.version,
|
version: sourceBookTask.version,
|
||||||
imageCategory: sourceBookTask.imageCategory,
|
imageCategory: sourceBookTask.imageCategory,
|
||||||
|
openVideoGenerate: sourceBookTask.openVideoGenerate == null ? false : sourceBookTask.openVideoGenerate,
|
||||||
} as Book.SelectBookTask
|
} as Book.SelectBookTask
|
||||||
|
|
||||||
addBookTask.push(addOneBookTask)
|
addBookTask.push(addOneBookTask)
|
||||||
@ -517,7 +519,7 @@ export class BookTask {
|
|||||||
return successMessage(returnBookTask, "复制小说任务成功", "BookBasic_CopyNewBookTask")
|
return successMessage(returnBookTask, "复制小说任务成功", "BookBasic_CopyNewBookTask")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
throw error
|
return errorMessage("复制小说任务失败,失败信息如下:" + error.message, "BookBasic_CopyNewBookTask")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -262,8 +262,9 @@ export class BookVideo {
|
|||||||
if (repalceObject && repalceObject.length > 0) {
|
if (repalceObject && repalceObject.length > 0) {
|
||||||
await this.jianyingService.ReplaceDraftMaterialImageToVideo(book.name + "_" + element.name, repalceObject);
|
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) {
|
} catch (error) {
|
||||||
return errorMessage('添加剪映草稿失败,错误信息如下:' + error.toString(), "BookTask_AddJianyingDraft");
|
return errorMessage('添加剪映草稿失败,错误信息如下:' + error.toString(), "BookTask_AddJianyingDraft");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { isEmpty } from "lodash";
|
import { isEmpty, method } from "lodash";
|
||||||
import { gptDefine } from "../../../define/gptDefine";
|
import { gptDefine } from "../../../define/gptDefine";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { RetryWithBackoff } from "../../../define/Tools/common";
|
import { RetryWithBackoff } from "../../../define/Tools/common";
|
||||||
import { Book } from "../../../model/book/book";
|
import { Book } from "../../../model/book/book";
|
||||||
|
import { define } from "@/define/define";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一些GPT相关的服务都在这边
|
* 一些GPT相关的服务都在这边
|
||||||
@ -11,6 +12,7 @@ export class GptService {
|
|||||||
gptUrl: string = undefined
|
gptUrl: string = undefined
|
||||||
gptModel: string = undefined
|
gptModel: string = undefined
|
||||||
gptApiKey: string = undefined
|
gptApiKey: string = undefined
|
||||||
|
useTransfer: boolean = false
|
||||||
|
|
||||||
|
|
||||||
//#region GPT 设置
|
//#region GPT 设置
|
||||||
@ -42,10 +44,12 @@ export class GptService {
|
|||||||
this.gptUrl = all_options[index].gpt_url;
|
this.gptUrl = all_options[index].gpt_url;
|
||||||
this.gptApiKey = global.config.gpt_key;
|
this.gptApiKey = global.config.gpt_key;
|
||||||
this.gptModel = global.config.gpt_model;
|
this.gptModel = global.config.gpt_model;
|
||||||
|
this.useTransfer = global.config.useTransfer;
|
||||||
return {
|
return {
|
||||||
gptUrl: this.gptUrl,
|
gptUrl: this.gptUrl,
|
||||||
gptApiKey: this.gptApiKey,
|
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")) {
|
if (gpt_url.includes("dashscope.aliyuncs.com")) {
|
||||||
content = res.data.output.choices[0].message.content;
|
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 {
|
} else {
|
||||||
content = res.data.choices[0].message.content;
|
content = res.data.choices[0].message.content;
|
||||||
}
|
}
|
||||||
@ -126,6 +140,27 @@ export class GptService {
|
|||||||
"messages": message
|
"messages": message
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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);
|
data = this.ModifyData(data, gpt_url);
|
||||||
let config = {
|
let config = {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@ -141,6 +176,7 @@ export class GptService {
|
|||||||
let res = await axios.request(config);
|
let res = await axios.request(config);
|
||||||
let content = this.GetResponseContent(res, this.gptUrl);
|
let content = this.GetResponseContent(res, this.gptUrl);
|
||||||
return content;
|
return content;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
41
src/main/Service/Options/index.ts
Normal file
41
src/main/Service/Options/index.ts
Normal file
@ -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()
|
||||||
116
src/main/Service/Options/optionServices.ts
Normal file
116
src/main/Service/Options/optionServices.ts
Normal file
@ -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<GeneralResponse.ErrorItem | SuccessItem> {
|
||||||
|
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<ErrorItem | SuccessItem> {
|
||||||
|
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<ErrorItem | SuccessItem> {
|
||||||
|
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'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,7 @@ import { DEFINE_STRING } from "../../../define/define_string";
|
|||||||
import { OtherData, ResponseMessageType } from "../../../define/enum/softwareEnum";
|
import { OtherData, ResponseMessageType } from "../../../define/enum/softwareEnum";
|
||||||
import util from 'util';
|
import util from 'util';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
|
import { TaskModal } from "@/model/task";
|
||||||
const execAsync = util.promisify(exec)
|
const execAsync = util.promisify(exec)
|
||||||
const fspromise = fs.promises
|
const fspromise = fs.promises
|
||||||
|
|
||||||
|
|||||||
@ -55,7 +55,14 @@ class BookServiceBasic {
|
|||||||
//#region 批次任务任务
|
//#region 批次任务任务
|
||||||
|
|
||||||
GetBookTaskDataById = async (bookTaskId: string) => await this.bookTaskServiceBasic.GetBookTaskDataById(bookTaskId);
|
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);
|
GetMaxBookTaskNo = async (bookId: string) => await this.bookTaskServiceBasic.GetMaxBookTaskNo(bookId);
|
||||||
UpdetedBookTaskData = async (bookTaskId: string, data: Book.SelectBookTask) => await this.bookTaskServiceBasic.UpdetedBookTaskData(bookTaskId, data);
|
UpdetedBookTaskData = async (bookTaskId: string, data: Book.SelectBookTask) => await this.bookTaskServiceBasic.UpdetedBookTaskData(bookTaskId, data);
|
||||||
ResetBookTask = async (bookTaskId: string) => await this.bookTaskServiceBasic.ResetBookTask(bookTaskId);
|
ResetBookTask = async (bookTaskId: string) => await this.bookTaskServiceBasic.ResetBookTask(bookTaskId);
|
||||||
|
|||||||
@ -33,15 +33,21 @@ 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();
|
await this.InitService();
|
||||||
let bookTasks = this.bookTaskService.GetBookTaskData(bookTaskCondition)
|
let bookTasks = this.bookTaskService.GetBookTaskData(bookTaskCondition)
|
||||||
|
if (returnEmpry) {
|
||||||
|
return { bookTasks: [], total: 0 }
|
||||||
|
} else {
|
||||||
if (bookTasks.data.bookTasks.length <= 0 || bookTasks.data.total <= 0) {
|
if (bookTasks.data.bookTasks.length <= 0 || bookTasks.data.total <= 0) {
|
||||||
throw new Error("未找到对应的小说批次任务数据,请检查")
|
throw new Error("未找到对应的小说批次任务数据,请检查")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return bookTasks.data
|
return bookTasks.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -405,9 +405,8 @@ export class SubtitleService {
|
|||||||
btd.timeLimit = element.timeLimit
|
btd.timeLimit = element.timeLimit
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return successMessage(null, "保存文案数据成功", 'SubtitleService_SaveCopywriting')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorMessage("保存文案数据失败,失败信息如下:" + error.toString(), 'SubtitleService_SaveCopywriting')
|
return errorMessage("保存文案数据失败,失败信息如下:" + error.toString(), 'SubtitleService_SaveCopywriting')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -193,6 +193,33 @@ export class Translate {
|
|||||||
"content": value.text
|
"content": value.text
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 = {
|
let config = {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
maxBodyLength: Infinity,
|
maxBodyLength: Infinity,
|
||||||
@ -206,11 +233,11 @@ export class Translate {
|
|||||||
let res = await RetryWithBackoff(async () => {
|
let res = await RetryWithBackoff(async () => {
|
||||||
return await axios.request(config);
|
return await axios.request(config);
|
||||||
}, 5, 2000)
|
}, 5, 2000)
|
||||||
// let res = await axios.request(config);
|
|
||||||
// 将返回的数据进行拼接数据处理
|
// 将返回的数据进行拼接数据处理
|
||||||
|
content = GetOpenAISuccessResponse(res.data);
|
||||||
|
}
|
||||||
let res_data = [];
|
let res_data = [];
|
||||||
let content = GetOpenAISuccessResponse(res.data);
|
|
||||||
res_data.push({
|
res_data.push({
|
||||||
src: value.text,
|
src: value.text,
|
||||||
dst: content
|
dst: content
|
||||||
|
|||||||
210
src/main/Service/copywriting/copywritingAIGenerationService.ts
Normal file
210
src/main/Service/copywriting/copywritingAIGenerationService.ts
Normal file
@ -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<string> {
|
||||||
|
// 开始请求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
|
||||||
|
}
|
||||||
24
src/main/Service/copywriting/index.ts
Normal file
24
src/main/Service/copywriting/index.ts
Normal file
@ -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();
|
||||||
@ -14,12 +14,20 @@ import { tts } from '../../model/tts'
|
|||||||
import { GeneralResponse } from '../../model/generalResponse'
|
import { GeneralResponse } from '../../model/generalResponse'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { GetEdgeTTSRole } from '../../define/tts/ttsDefine'
|
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 {
|
export class TTS {
|
||||||
softService: SoftwareService
|
softService: SoftwareService
|
||||||
ttsService: TTSService
|
ttsService: TTSService
|
||||||
|
|
||||||
constructor() { }
|
optionServices: OptionServices
|
||||||
|
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.optionServices = new OptionServices()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化TTS服务
|
* 初始化TTS服务
|
||||||
@ -62,72 +70,12 @@ export class TTS {
|
|||||||
throw new Error("获取TTS角色配置失败")
|
throw new Error("获取TTS角色配置失败")
|
||||||
}
|
}
|
||||||
if (isEmpty(data[0].value) || !ValidateJson(data[0].value)) {
|
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) {
|
} catch (error) {
|
||||||
return errorMessage('获取TTS配置失败,错误信息如下:' + error.toString(), 'TTS_GetTTSCOnfig')
|
return errorMessage('获取TTS配置失败,错误信息如下:' + error.toString(), 'TTS_GetTTSOptions')
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化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<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
|
|
||||||
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')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,12 +86,17 @@ export class TTS {
|
|||||||
* 生成音频
|
* 生成音频
|
||||||
* @param text 要生成的文本
|
* @param text 要生成的文本
|
||||||
*/
|
*/
|
||||||
async GenerateAudio(text: string) {
|
async GenerateAudio() {
|
||||||
try {
|
try {
|
||||||
await this.InitService()
|
await this.InitService()
|
||||||
let ttsSetting = await this.GetTTSCOnfig()
|
let TTS_GlobalSetting = await this.optionServices.GetOptionByKey(OptionKeyName.TTS_GlobalSetting);
|
||||||
if (ttsSetting.code === 0) {
|
if (TTS_GlobalSetting.code == 0) {
|
||||||
return ttsSetting
|
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
|
let res = undefined
|
||||||
|
|
||||||
@ -156,13 +109,13 @@ export class TTS {
|
|||||||
await fs.promises.writeFile(textPath, text, 'utf-8')
|
await fs.promises.writeFile(textPath, text, 'utf-8')
|
||||||
|
|
||||||
let audioPath = path.join(define.tts_path, `${thisId}/${thisId}.mp3`)
|
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
|
let hasSrt = true
|
||||||
switch (selectModel) {
|
switch (selectModel) {
|
||||||
case TTSSelectModel.edgeTTS:
|
case TTSSelectModel.edgeTTS:
|
||||||
hasSrt = ttsSetting.data.edgeTTS.saveSubtitles
|
hasSrt = TTS_GlobalSettingData.edgeTTS.saveSubtitles
|
||||||
res = await this.GenerateAudioByEdgeTTS(text, ttsSetting.data.edgeTTS, audioPath)
|
res = await this.GenerateAudioByEdgeTTS(text, TTS_GlobalSettingData.edgeTTS, audioPath)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error('未知的TTS模式')
|
throw new Error('未知的TTS模式')
|
||||||
@ -182,7 +135,7 @@ export class TTS {
|
|||||||
id: thisId,
|
id: thisId,
|
||||||
textPath: textPath ? path.relative(define.tts_path, textPath) : null
|
textPath: textPath ? path.relative(define.tts_path, textPath) : null
|
||||||
})
|
})
|
||||||
return res
|
return successMessage(res, '生成音频成功', 'TTS_GenerateAudio')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorMessage('生成音频失败,错误信息如下:' + error.toString(), 'TTS_GenerateAudio')
|
return errorMessage('生成音频失败,错误信息如下:' + error.toString(), 'TTS_GenerateAudio')
|
||||||
}
|
}
|
||||||
@ -194,9 +147,9 @@ export class TTS {
|
|||||||
* @param edgeTTS edgetts的设置
|
* @param edgeTTS edgetts的设置
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async GenerateAudioByEdgeTTS(text: string, edgeTTS: TTSSettingModel.EdgeTTSSetting, mp3Path: string) {
|
async GenerateAudioByEdgeTTS(text: string, edgeTTS: OptionModel.TTS_EdgeTTSModel, mp3Path: string) {
|
||||||
try {
|
try {
|
||||||
const tts = new EdgeTTS({
|
const edgeTts = new EdgeTTS({
|
||||||
voice: edgeTTS.value,
|
voice: edgeTTS.value,
|
||||||
lang: edgeTTS.lang,
|
lang: edgeTTS.lang,
|
||||||
outputFormat: 'audio-24khz-96kbitrate-mono-mp3',
|
outputFormat: 'audio-24khz-96kbitrate-mono-mp3',
|
||||||
@ -204,10 +157,9 @@ export class TTS {
|
|||||||
pitch: `${edgeTTS.pitch}%`,
|
pitch: `${edgeTTS.pitch}%`,
|
||||||
rate: `${edgeTTS.rate}%`,
|
rate: `${edgeTTS.rate}%`,
|
||||||
volume: `${edgeTTS.volumn}%`,
|
volume: `${edgeTTS.volumn}%`,
|
||||||
timeout : 100000
|
timeout: edgeTTS.timeOut ?? 100000
|
||||||
})
|
})
|
||||||
let ttsRes = await tts.ttsPromise(text, mp3Path)
|
await edgeTts.ttsPromise(text, mp3Path)
|
||||||
console.log(ttsRes)
|
|
||||||
return {
|
return {
|
||||||
mp3Path: mp3Path,
|
mp3Path: mp3Path,
|
||||||
srtJsonPath: mp3Path + '.json'
|
srtJsonPath: mp3Path + '.json'
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { DEFINE_STRING } from '../../define/define_string'
|
|||||||
import { PublicMethod } from '../Public/publicMethod'
|
import { PublicMethod } from '../Public/publicMethod'
|
||||||
import { define } from '../../define/define'
|
import { define } from '../../define/define'
|
||||||
import { get, has, isEmpty } from 'lodash'
|
import { get, has, isEmpty } from 'lodash'
|
||||||
import { ClipSetting } from '../../define/setting/clipSetting'
|
|
||||||
import { errorMessage, successMessage } from '../Public/generalTools'
|
import { errorMessage, successMessage } from '../Public/generalTools'
|
||||||
import { ServiceBase } from '../../define/db/service/serviceBase'
|
import { ServiceBase } from '../../define/db/service/serviceBase'
|
||||||
import {
|
import {
|
||||||
@ -25,7 +24,7 @@ export class Writing extends ServiceBase {
|
|||||||
constructor(global) {
|
constructor(global) {
|
||||||
super()
|
super()
|
||||||
this.pm = new PublicMethod(global)
|
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';
|
let resData = '\n\n';
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fetch(define.serverUrl + "/lms/Forward/ForwardWordStream", requestOptions)
|
fetch(define.lms + "/lms/Forward/ForwardWordStream", requestOptions)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!response.body) {
|
if (!response.body) {
|
||||||
throw new Error('ReadableStream not yet supported in this browser.');
|
throw new Error('ReadableStream not yet supported in this browser.');
|
||||||
@ -241,10 +240,6 @@ export class Writing extends ServiceBase {
|
|||||||
}, 3, 1000)
|
}, 3, 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// let tasks =
|
|
||||||
|
|
||||||
// console.log("ActionStart", result);
|
|
||||||
// ExecuteConcurrently
|
|
||||||
return successMessage(result, "执行文案相关任务成功", 'Writing_ActionStart');
|
return successMessage(result, "执行文案相关任务成功", 'Writing_ActionStart');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorMessage(
|
return errorMessage(
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export class GptSetting extends ServiceBase {
|
|||||||
subtitleService: SubtitleService
|
subtitleService: SubtitleService
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
axios.defaults.baseURL = define.serverUrl
|
axios.defaults.baseURL = define.lms
|
||||||
this.softWareServiceBasic = new SoftWareServiceBasic();
|
this.softWareServiceBasic = new SoftWareServiceBasic();
|
||||||
this.subtitleService = new SubtitleService()
|
this.subtitleService = new SubtitleService()
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/model/Setting/softwareSetting.d.ts
vendored
1
src/model/Setting/softwareSetting.d.ts
vendored
@ -21,6 +21,7 @@ declare namespace SoftwareSettingModel {
|
|||||||
project_name: string = undefined // 项目名称
|
project_name: string = undefined // 项目名称
|
||||||
gpt_business: string = undefined // GPT服务商ID
|
gpt_business: string = undefined // GPT服务商ID
|
||||||
gpt_model: string = undefined // GPT模型
|
gpt_model: string = undefined // GPT模型
|
||||||
|
useTransfer: boolean = false // 是不是使用转发
|
||||||
task_number: number = undefined // 任务数量
|
task_number: number = undefined // 任务数量
|
||||||
theme: string = undefined // 主题
|
theme: string = undefined // 主题
|
||||||
gpt_auto_inference: string = undefined // GPT自动推理模式
|
gpt_auto_inference: string = undefined // GPT自动推理模式
|
||||||
|
|||||||
13
src/model/generalResponse.d.ts
vendored
13
src/model/generalResponse.d.ts
vendored
@ -36,3 +36,16 @@ declare namespace GeneralResponse {
|
|||||||
data?: MJ.MJResponseToFront | Buffer | string | TranslateModel.TranslateResponseMessageModel | ProgressResponse | SubtitleProgressResponse
|
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
|
||||||
|
}
|
||||||
110
src/model/option/option.d.ts
vendored
110
src/model/option/option.d.ts
vendored
@ -1,10 +1,116 @@
|
|||||||
import { OptionType } from "@/define/enum/option"
|
import { OptionType } from "@/define/enum/option"
|
||||||
|
|
||||||
declare namespace Option {
|
declare namespace OptionModel {
|
||||||
/** option的model */
|
/**
|
||||||
|
* Option的模型
|
||||||
|
*/
|
||||||
type OptionItem = {
|
type OptionItem = {
|
||||||
key: string,
|
key: string,
|
||||||
value: string,
|
value: string,
|
||||||
type: OptionType
|
type: OptionType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#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<CW_AISimpleSettingModel_WordStruct>
|
||||||
|
}
|
||||||
|
|
||||||
|
//#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
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -16,6 +16,7 @@ import { db } from './db'
|
|||||||
import { translate } from './translate'
|
import { translate } from './translate'
|
||||||
import { preset } from './preset'
|
import { preset } from './preset'
|
||||||
import { task } from './task'
|
import { task } from './task'
|
||||||
|
import { options } from './options'
|
||||||
// Custom APIs for renderer
|
// Custom APIs for renderer
|
||||||
|
|
||||||
let events = []
|
let events = []
|
||||||
@ -478,6 +479,7 @@ if (process.contextIsolated) {
|
|||||||
contextBridge.exposeInMainWorld('db', db)
|
contextBridge.exposeInMainWorld('db', db)
|
||||||
contextBridge.exposeInMainWorld('preset', preset)
|
contextBridge.exposeInMainWorld('preset', preset)
|
||||||
contextBridge.exposeInMainWorld('task', task)
|
contextBridge.exposeInMainWorld('task', task)
|
||||||
|
contextBridge.exposeInMainWorld('options', options)
|
||||||
contextBridge.exposeInMainWorld('darkMode', {
|
contextBridge.exposeInMainWorld('darkMode', {
|
||||||
toggle: (value) => ipcRenderer.invoke('dark-mode:toggle', value)
|
toggle: (value) => ipcRenderer.invoke('dark-mode:toggle', value)
|
||||||
})
|
})
|
||||||
@ -502,4 +504,5 @@ if (process.contextIsolated) {
|
|||||||
window.preset = preset
|
window.preset = preset
|
||||||
window.task = task
|
window.task = task
|
||||||
window.translate = translate
|
window.translate = translate
|
||||||
|
window.options = options
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/preload/options.ts
Normal file
22
src/preload/options.ts
Normal file
@ -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 }
|
||||||
@ -2,14 +2,9 @@ import { ipcRenderer } from 'electron'
|
|||||||
import { DEFINE_STRING } from '../define/define_string'
|
import { DEFINE_STRING } from '../define/define_string'
|
||||||
|
|
||||||
const tts = {
|
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字幕
|
// 生成SRT字幕
|
||||||
GenerateSrt: async (text) => await ipcRenderer.invoke(DEFINE_STRING.TTS.GENERATE_SRT, text),
|
GenerateSrt: async (text) => await ipcRenderer.invoke(DEFINE_STRING.TTS.GENERATE_SRT, text),
|
||||||
|
|||||||
@ -27,10 +27,15 @@ const write = {
|
|||||||
|
|
||||||
//#region AI相关的任务
|
//#region AI相关的任务
|
||||||
|
|
||||||
// 开始执行API相关的一系列任务
|
/**
|
||||||
ActionStart(aiSetting, word) {
|
* AI处理文案
|
||||||
return ipcRenderer.invoke(DEFINE_STRING.WRITE.ACTION_START, aiSetting, word)
|
* @param ids 需要改写的文案ID
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
CopyWritingAIGeneration(ids: string[]) {
|
||||||
|
return ipcRenderer.invoke(DEFINE_STRING.WRITE.COPY_WRITING_AI_GENERATION, ids)
|
||||||
},
|
},
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 文案洗稿相关
|
//#region 文案洗稿相关
|
||||||
|
|||||||
72
src/renderer/src/common/copyWriting.ts
Normal file
72
src/renderer/src/common/copyWriting.ts
Normal file
@ -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;
|
||||||
97
src/renderer/src/common/initCommon.ts
Normal file
97
src/renderer/src/common/initCommon.ts
Normal file
@ -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
|
||||||
133
src/renderer/src/common/text.ts
Normal file
133
src/renderer/src/common/text.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import { useOptionStore } from "@/stores/option"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化文本,通过自定义的特殊字符进行格式化
|
||||||
|
* @param oldText 需要格式化的文本
|
||||||
|
* @returns 返回格式化后的文本
|
||||||
|
*/
|
||||||
|
async function FormatText(oldText: string): Promise<string> {
|
||||||
|
// 专用正则转义函数
|
||||||
|
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;
|
||||||
20
src/renderer/src/common/ttsCommon.ts
Normal file
20
src/renderer/src/common/ttsCommon.ts
Normal file
@ -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;
|
||||||
@ -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',
|
key: 'timeLimit',
|
||||||
width: 120
|
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() {
|
function setHeight() {
|
||||||
let div = document.getElementById('import_word_and_srt')
|
let div = document.getElementById('import_word_and_srt')
|
||||||
@ -210,7 +274,6 @@ export default defineComponent({
|
|||||||
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
|
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
|
||||||
maskClosable: false,
|
maskClosable: false,
|
||||||
onClose: async () => {
|
onClose: async () => {
|
||||||
|
|
||||||
console.log(wenkeRef.value.word)
|
console.log(wenkeRef.value.word)
|
||||||
let word = wenkeRef.value.data
|
let word = wenkeRef.value.data
|
||||||
if (word == null || word == '') {
|
if (word == null || word == '') {
|
||||||
@ -565,7 +628,6 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (type.value == 'mj_reverse' || type.value == 'sd_reverse') {
|
} else if (type.value == 'mj_reverse' || type.value == 'sd_reverse') {
|
||||||
|
|
||||||
if (data.value.length != reverseManageStore.selectBookTaskDetail.length) {
|
if (data.value.length != reverseManageStore.selectBookTaskDetail.length) {
|
||||||
message.error('检测到导入的字幕数据和分镜对不上,请先对齐')
|
message.error('检测到导入的字幕数据和分镜对不上,请先对齐')
|
||||||
return
|
return
|
||||||
|
|||||||
@ -98,7 +98,6 @@ async function ResetBookData(e) {
|
|||||||
|
|
||||||
async function DeleteBookData(e) {
|
async function DeleteBookData(e) {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
message.info('删除小说数据 ' + book.value.id)
|
|
||||||
let da = dialog.warning({
|
let da = dialog.warning({
|
||||||
title: '删除小说数据警告',
|
title: '删除小说数据警告',
|
||||||
content:
|
content:
|
||||||
@ -107,7 +106,6 @@ async function DeleteBookData(e) {
|
|||||||
negativeText: '取消',
|
negativeText: '取消',
|
||||||
onPositiveClick: async () => {
|
onPositiveClick: async () => {
|
||||||
da?.destroy()
|
da?.destroy()
|
||||||
|
|
||||||
softwareStore.spin.spinning = true
|
softwareStore.spin.spinning = true
|
||||||
softwareStore.spin.tip = '正在删除小说数据。。。'
|
softwareStore.spin.tip = '正在删除小说数据。。。'
|
||||||
let res = await window.book.DeleteBookData(book.value.id)
|
let res = await window.book.DeleteBookData(book.value.id)
|
||||||
@ -116,7 +114,6 @@ async function DeleteBookData(e) {
|
|||||||
message.error(res.message)
|
message.error(res.message)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 这边直接把数据删除了就行
|
// 这边直接把数据删除了就行
|
||||||
reverseManageStore.bookData = reverseManageStore.bookData.filter(
|
reverseManageStore.bookData = reverseManageStore.bookData.filter(
|
||||||
(item) => item.id != book.value.id
|
(item) => item.id != book.value.id
|
||||||
|
|||||||
@ -940,7 +940,9 @@ async function ImportWordAndSrtFunc() {
|
|||||||
subValue: element.subValue ? element.subValue : [],
|
subValue: element.subValue ? element.subValue : [],
|
||||||
name: element.name + '.png',
|
name: element.name + '.png',
|
||||||
prompt: element.prompt,
|
prompt: element.prompt,
|
||||||
timeLimit: element.timeLimit
|
timeLimit: element.timeLimit,
|
||||||
|
start_time: element.startTime,
|
||||||
|
end_time: element.endTime
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -134,7 +134,9 @@ async function ImportWord() {
|
|||||||
subValue: element.subValue ? element.subValue : [],
|
subValue: element.subValue ? element.subValue : [],
|
||||||
name: element.name + '.png',
|
name: element.name + '.png',
|
||||||
prompt: element.prompt,
|
prompt: element.prompt,
|
||||||
timeLimit: element.timeLimit
|
timeLimit: element.timeLimit,
|
||||||
|
start_time: element.startTime,
|
||||||
|
end_time: element.endTime
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
126
src/renderer/src/components/CopyWriting/CWInputWord.vue
Normal file
126
src/renderer/src/components/CopyWriting/CWInputWord.vue
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
<template>
|
||||||
|
<div class="cw-input-word">
|
||||||
|
<div class="formatting-word">
|
||||||
|
<n-button color="#b6a014" size="small" @click="formatWrite" style="margin-right: 5px"
|
||||||
|
>一键格式化</n-button
|
||||||
|
>
|
||||||
|
<n-popover trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button quaternary circle size="tiny" color="#b6a014" @click="AddSplitChar">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon size="25"> <AddCircleOutline /> </n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
<span>添加分割标识符</span>
|
||||||
|
</n-popover>
|
||||||
|
</div>
|
||||||
|
<n-input
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{
|
||||||
|
minRows: 20,
|
||||||
|
maxRows: 20
|
||||||
|
}"
|
||||||
|
v-model:value="word"
|
||||||
|
showCount
|
||||||
|
placeholder="请输入文案"
|
||||||
|
style="width: 100%; margin-top: 10px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, h } from 'vue'
|
||||||
|
import { NInput, NIcon, NButton, NPopover, useDialog, useMessage } from 'naive-ui'
|
||||||
|
import { AddCircleOutline } from '@vicons/ionicons5'
|
||||||
|
import InitCommon from '../../common/initCommon'
|
||||||
|
import InputDialogContent from '../Original/Components/InputDialogContent.vue'
|
||||||
|
import { useOptionStore } from '@/stores/option'
|
||||||
|
import { OptionKeyName, OptionType } from '@/define/enum/option'
|
||||||
|
import TextCommon from '../../common/text'
|
||||||
|
let optionStore = useOptionStore()
|
||||||
|
|
||||||
|
let dialog = useDialog()
|
||||||
|
let message = useMessage()
|
||||||
|
|
||||||
|
let word = ref('')
|
||||||
|
|
||||||
|
let split_ref = ref(null)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await InitCommon.InitSpecialCharacters()
|
||||||
|
await InitWord()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 整合文案数据
|
||||||
|
*/
|
||||||
|
async function InitWord() {
|
||||||
|
debugger
|
||||||
|
let wordStruct = optionStore.CW_AISimpleSetting.wordStruct
|
||||||
|
if (!wordStruct || wordStruct.length <= 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let wordArr = []
|
||||||
|
for (let i = 0; i < wordStruct.length; i++) {
|
||||||
|
wordArr.push(wordStruct[i].oldWord)
|
||||||
|
}
|
||||||
|
word.value = wordArr.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化文案
|
||||||
|
*/
|
||||||
|
async function formatWrite() {
|
||||||
|
try {
|
||||||
|
let newText = await TextCommon.FormatText(word.value)
|
||||||
|
word.value = newText
|
||||||
|
message.success('格式化成功')
|
||||||
|
} catch (error) {
|
||||||
|
message.error('格式化失败,失败原因:' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加分割符号
|
||||||
|
*/
|
||||||
|
async function AddSplitChar() {
|
||||||
|
// 判断当前数据是不是存在
|
||||||
|
// 处理数据。获取当前的所有的数据
|
||||||
|
let dialogWidth = 400
|
||||||
|
let dialogHeight = 150
|
||||||
|
dialog.create({
|
||||||
|
title: '添加分割符',
|
||||||
|
showIcon: false,
|
||||||
|
closeOnEsc: false,
|
||||||
|
content: () =>
|
||||||
|
h(InputDialogContent, {
|
||||||
|
ref: split_ref,
|
||||||
|
initData: optionStore.CW_FormatSpecialChar,
|
||||||
|
placeholder: '请输入分割符'
|
||||||
|
}),
|
||||||
|
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
|
||||||
|
maskClosable: false,
|
||||||
|
onClose: async () => {
|
||||||
|
optionStore.CW_FormatSpecialChar = split_ref.value.data
|
||||||
|
let saveRes = await window.options.ModifyOptionByKey(
|
||||||
|
OptionKeyName.CW_FormatSpecialChar,
|
||||||
|
optionStore.CW_FormatSpecialChar,
|
||||||
|
OptionType.STRING
|
||||||
|
)
|
||||||
|
if (saveRes.code == 0) {
|
||||||
|
window.api.showGlobalMessageDialog(saveRes)
|
||||||
|
// 报错 不能关闭
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
message.success('数据保存成功')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
word
|
||||||
|
})
|
||||||
|
</script>
|
||||||
452
src/renderer/src/components/CopyWriting/CopyWritingContent.vue
Normal file
452
src/renderer/src/components/CopyWriting/CopyWritingContent.vue
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
<template>
|
||||||
|
<div class="copy-writing-content">
|
||||||
|
<n-data-table
|
||||||
|
:columns="columns"
|
||||||
|
:data="optionStore.CW_AISimpleSetting.wordStruct"
|
||||||
|
:bordered="false"
|
||||||
|
:max-height="maxHeight"
|
||||||
|
scroll-x="1500"
|
||||||
|
style="margin-bottom: 20px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, h, onMounted } from 'vue'
|
||||||
|
import { NDataTable, NInput, NButton, NIcon, useMessage, useDialog, NSpace } from 'naive-ui'
|
||||||
|
import { TrashBinOutline, RefreshOutline } from '@vicons/ionicons5'
|
||||||
|
|
||||||
|
import { useOptionStore } from '@/stores/option'
|
||||||
|
import { useSoftwareStore } from '@/stores/software'
|
||||||
|
let softwareStore = useSoftwareStore()
|
||||||
|
let optionStore = useOptionStore()
|
||||||
|
import CWInputWord from './CWInputWord.vue'
|
||||||
|
import CopyWritingShowAIGenerate from './CopyWritingShowAIGenerate.vue'
|
||||||
|
import { isEmpty } from 'lodash'
|
||||||
|
import CopyWriting from '../../common/copyWriting'
|
||||||
|
import { TimeDelay } from '@/define/Tools/time'
|
||||||
|
|
||||||
|
let message = useMessage()
|
||||||
|
let dialog = useDialog()
|
||||||
|
|
||||||
|
let maxHeight = ref(0)
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
key: 'index',
|
||||||
|
width: 80,
|
||||||
|
render: (_, index) => index + 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: (row, index) => {
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
style: 'display: flex; align-items: center ,justify-content: center;'
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h('div', { style: { marginBottom: '8px' } }, '处理前文本'),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'tiny',
|
||||||
|
strong: true,
|
||||||
|
secondary: true,
|
||||||
|
style: 'margin-left: 5px',
|
||||||
|
type: 'info',
|
||||||
|
onClick: ImportText
|
||||||
|
},
|
||||||
|
{ default: () => '导入文本' }
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'tiny',
|
||||||
|
strong: true,
|
||||||
|
secondary: true,
|
||||||
|
style: 'margin-left: 5px',
|
||||||
|
type: 'info',
|
||||||
|
onClick: TextSplit
|
||||||
|
},
|
||||||
|
{ default: () => '文案分割' }
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
key: 'oldWord',
|
||||||
|
width: 500,
|
||||||
|
render: (row) =>
|
||||||
|
h(NInput, {
|
||||||
|
type: 'textarea',
|
||||||
|
autosize: { minRows: 10, maxRows: 10 },
|
||||||
|
value: row.oldWord,
|
||||||
|
showCount: true,
|
||||||
|
onUpdateValue: (value) => (row.oldWord = value),
|
||||||
|
style: { minWidth: '200px' },
|
||||||
|
placeholder: '请输入文本'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: (row, index) => {
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
style: 'display: flex; align-items: center ,justify-content: center;'
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h('div', { style: { marginBottom: '8px' } }, '处理后文本'),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'tiny',
|
||||||
|
strong: true,
|
||||||
|
secondary: true,
|
||||||
|
style: 'margin-left: 5px',
|
||||||
|
type: 'info',
|
||||||
|
onClick: ShowAIGenerateText
|
||||||
|
},
|
||||||
|
{ default: () => '显示生成文本' }
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'tiny',
|
||||||
|
strong: true,
|
||||||
|
secondary: true,
|
||||||
|
style: 'margin-left: 5px',
|
||||||
|
type: 'info',
|
||||||
|
onClick: CopyGenerationText
|
||||||
|
},
|
||||||
|
{ default: () => '复制生成文本' }
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'tiny',
|
||||||
|
strong: true,
|
||||||
|
secondary: true,
|
||||||
|
style: 'margin-left: 5px',
|
||||||
|
type: 'info',
|
||||||
|
onClick: async () => {
|
||||||
|
// 获取AI生成为空的文本
|
||||||
|
let ids = optionStore.CW_AISimpleSetting.wordStruct
|
||||||
|
.filter((item) => isEmpty(item.newWord))
|
||||||
|
.map((item) => item.id)
|
||||||
|
if (ids <= 0) {
|
||||||
|
message.error('生成失败:不存在未生成的文本')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleGenerate(ids)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ default: () => '生成空文本' }
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'tiny',
|
||||||
|
strong: true,
|
||||||
|
secondary: true,
|
||||||
|
style: 'margin-left: 5px',
|
||||||
|
type: 'error',
|
||||||
|
onClick: ClearAIGeneration
|
||||||
|
},
|
||||||
|
{ default: () => '清空' }
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
key: 'newWord',
|
||||||
|
width: 500,
|
||||||
|
render: (row) =>
|
||||||
|
h(NInput, {
|
||||||
|
type: 'textarea',
|
||||||
|
autosize: { minRows: 10, maxRows: 10 },
|
||||||
|
value: row.newWord,
|
||||||
|
showCount: true,
|
||||||
|
onUpdateValue: (value) => (row.newWord = value),
|
||||||
|
style: { minWidth: '200px' },
|
||||||
|
placeholder: 'AI 改写后的文件'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 120,
|
||||||
|
render: (row) =>
|
||||||
|
h(NSpace, {}, () => [
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'info',
|
||||||
|
onClick: () => handleGenerate([row.id])
|
||||||
|
},
|
||||||
|
() => [h(NIcon, { size: 16, component: RefreshOutline }), ' 生成']
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'error',
|
||||||
|
onClick: () => handleDelete(row.id)
|
||||||
|
},
|
||||||
|
() => [h(NIcon, { size: 16, component: TrashBinOutline }), ' 清空']
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算表格的最大高度
|
||||||
|
*/
|
||||||
|
function calcHeight() {
|
||||||
|
// 视口的高度
|
||||||
|
let height = window.innerHeight
|
||||||
|
maxHeight.value = height - 240
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
calcHeight()
|
||||||
|
})
|
||||||
|
|
||||||
|
function ShowAIGenerateText() {
|
||||||
|
dialog.info({
|
||||||
|
title: 'AI生成文本',
|
||||||
|
content: () => h(CopyWritingShowAIGenerate),
|
||||||
|
style: 'width: 800px;height: 610px;',
|
||||||
|
showIcon: false,
|
||||||
|
maskClosable: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空AI生成的文本
|
||||||
|
*/
|
||||||
|
function ClearAIGeneration() {
|
||||||
|
dialog.warning({
|
||||||
|
title: '温馨提示',
|
||||||
|
content: '确定要清空所有的AI生成文本吗?清空后不可恢复,是否继续?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
optionStore.CW_AISimpleSetting.wordStruct.forEach((item) => {
|
||||||
|
item.newWord = ''
|
||||||
|
})
|
||||||
|
await CopyWriting.SaveCWAISimpleSetting()
|
||||||
|
message.success('清空成功')
|
||||||
|
} catch (error) {
|
||||||
|
message.error('清空失败:' + error.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNegativeClick: () => {
|
||||||
|
message.info('取消清空')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制AI生成的文本 做个简单的拼接
|
||||||
|
*/
|
||||||
|
function CopyGenerationText() {
|
||||||
|
dialog.warning({
|
||||||
|
title: '温馨提示',
|
||||||
|
content:
|
||||||
|
'直接复制会将所有的AI生成后的数据直接进行复制,不会进行格式之类的调整,若有需求可以再下面表格直接修改,或者是再左边的显示生成文本中修改,是否继续复制?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
let wordStruct = optionStore.CW_AISimpleSetting.wordStruct
|
||||||
|
// 循环判断是不是有空的数据,有的话提示
|
||||||
|
let isHaveEmpty = wordStruct.some((item) => {
|
||||||
|
return isEmpty(item.newWord)
|
||||||
|
})
|
||||||
|
if (isHaveEmpty) {
|
||||||
|
message.error('复制失败:存在未生成的文本,请先生成文本')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 获取当前的文本
|
||||||
|
let newWordAll = wordStruct.map((item) => {
|
||||||
|
return item.newWord
|
||||||
|
})
|
||||||
|
let newWordStr = newWordAll.join('\n')
|
||||||
|
// 删除里面的空行
|
||||||
|
let newWord = newWordStr.split('\n').filter((item) => {
|
||||||
|
return !isEmpty(item)
|
||||||
|
})
|
||||||
|
await navigator.clipboard.writeText(newWord.join('\n'))
|
||||||
|
message.success('复制成功')
|
||||||
|
} catch (error) {
|
||||||
|
message.error(
|
||||||
|
'复制失败,请在左边的显示生成文本中进行手动复制,失败信息如图:' + error.message
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
softwareStore.spin.spinning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNegativeClick: () => {
|
||||||
|
message.info('取消删除')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行生成AI后文本的方法
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
function handleGenerate(rowIds) {
|
||||||
|
let da = dialog.warning({
|
||||||
|
title: '温馨提示',
|
||||||
|
content:
|
||||||
|
'确定重新生成当前行的AI生成文本吗?重新生成后会清空之前的生成文本并且不可恢复,是否继续?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
debugger
|
||||||
|
softwareStore.spin.spinning = true
|
||||||
|
softwareStore.spin.tip = '生成中......'
|
||||||
|
let ids = []
|
||||||
|
optionStore.CW_AISimpleSetting.wordStruct.forEach((item) => {
|
||||||
|
if (rowIds.includes(item.id)) {
|
||||||
|
ids.push(item.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
da.destroy()
|
||||||
|
|
||||||
|
let res = await window.write.CopyWritingAIGeneration(ids)
|
||||||
|
|
||||||
|
if (res.code == 0) {
|
||||||
|
message.error(res.message)
|
||||||
|
window.api.showGlobalMessageDialog(res)
|
||||||
|
softwareStore.spin.spinning = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 这边直接保存一下
|
||||||
|
await CopyWriting.SaveCWAISimpleSetting()
|
||||||
|
window.api.showGlobalMessageDialog(res)
|
||||||
|
await TimeDelay(200)
|
||||||
|
} catch (error) {
|
||||||
|
message.error('生成失败:' + error.message)
|
||||||
|
} finally {
|
||||||
|
softwareStore.spin.spinning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNegativeClick: () => {
|
||||||
|
message.info('取消删除')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除当行的AI生成文本
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
const handleDelete = (id) => {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要删除当前行的AI生成文本吗?数据删除后不可恢复,是否继续?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
let index = optionStore.CW_AISimpleSetting.wordStruct.findIndex((item) => item.id == id)
|
||||||
|
if (index == -1) {
|
||||||
|
message.error('删除失败:未找到对应的数据')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
optionStore.CW_AISimpleSetting.wordStruct[index].newWord = ''
|
||||||
|
// 保存数据
|
||||||
|
await CopyWriting.SaveCWAISimpleSetting()
|
||||||
|
message.success('删除成功')
|
||||||
|
},
|
||||||
|
onNegativeClick: () => {
|
||||||
|
message.info('取消删除')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入文本按钮的具体实现
|
||||||
|
*/
|
||||||
|
function ImportText() {
|
||||||
|
let cwInputWordRef = ref(null)
|
||||||
|
dialog.info({
|
||||||
|
title: '导入文本',
|
||||||
|
content: () =>
|
||||||
|
h('div', {}, [
|
||||||
|
h(CWInputWord, { type: 'textarea', ref: cwInputWordRef, placeholder: '请输入文本' })
|
||||||
|
]),
|
||||||
|
style: 'width: 800px;height: 610px;',
|
||||||
|
showIcon: false,
|
||||||
|
maskClosable: false,
|
||||||
|
positiveText: '导入',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
let inputWord = cwInputWordRef.value.word
|
||||||
|
if (isEmpty(inputWord)) {
|
||||||
|
message.error('导入失败:文本不能为空')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 判断当前是不是又数据存在,存在的话提示
|
||||||
|
if (
|
||||||
|
optionStore.CW_AISimpleSetting.wordStruct &&
|
||||||
|
optionStore.CW_AISimpleSetting.wordStruct.length > 0
|
||||||
|
) {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content:
|
||||||
|
'当前已经存在数据,继续操作会删除之前的数据,包括生成之后的数据,若只是简单调整数据,可在外面显示的表格中进行直接修改,是否继续?',
|
||||||
|
positiveText: '导入',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
await CopyWriting.SplitOrMergeOldText(
|
||||||
|
optionStore.CW_AISimpleSetting.isSplit,
|
||||||
|
inputWord
|
||||||
|
)
|
||||||
|
message.success('更新导入成功')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
await CopyWriting.SplitOrMergeOldText(optionStore.CW_AISimpleSetting.isSplit, inputWord)
|
||||||
|
await CopyWriting.SaveCWAISimpleSetting()
|
||||||
|
message.info(inputWord)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
message.error('导入失败:' + err.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNegativeClick: () => {
|
||||||
|
message.info('取消导入')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文案分割按钮的具体实现
|
||||||
|
*/
|
||||||
|
function TextSplit() {
|
||||||
|
dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content:
|
||||||
|
'确定要将当前文本按照设定的单次最大次数进行分割吗?分割后会清空已生成的内容,是否继续?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
await CopyWriting.SplitOrMergeOldText(true)
|
||||||
|
message.success('分割成功')
|
||||||
|
} catch (err) {
|
||||||
|
message.error('分割失败:' + err.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNegativeClick: () => {
|
||||||
|
message.info('取消分割')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
106
src/renderer/src/components/CopyWriting/CopyWritingHome.vue
Normal file
106
src/renderer/src/components/CopyWriting/CopyWritingHome.vue
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<template>
|
||||||
|
<div class="copy-writing-home">
|
||||||
|
<CopyWritingSimpleSetting />
|
||||||
|
<CopyWritingContent />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { useSoftwareStore } from '@/stores/software'
|
||||||
|
import { TimeDelay } from '@/define/Tools/time'
|
||||||
|
import { useMessage } from 'naive-ui'
|
||||||
|
import { useOptionStore } from '@/stores/option'
|
||||||
|
import CopyWritingSimpleSetting from './CopyWritingSimpleSetting.vue'
|
||||||
|
import CopyWritingContent from './CopyWritingContent.vue'
|
||||||
|
import { OptionKeyName, OptionType } from '@/define/enum/option'
|
||||||
|
|
||||||
|
let softwareStore = useSoftwareStore()
|
||||||
|
let message = useMessage()
|
||||||
|
let optionStore = useOptionStore()
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
softwareStore.spin.spinning = true
|
||||||
|
softwareStore.spin.tip = '数据加载中'
|
||||||
|
|
||||||
|
// 这边同步数据和加载文案处理AI设置数据
|
||||||
|
await InitCopyWritingAISetting()
|
||||||
|
// 初始化界面数据
|
||||||
|
await InitCopyWritingData()
|
||||||
|
|
||||||
|
await TimeDelay(1000)
|
||||||
|
} catch (error) {
|
||||||
|
window.api.showGlobalMessageDialog({
|
||||||
|
code: 0,
|
||||||
|
message: '数据加载失败,请切换左侧菜单重试,错误信息:' + error.message
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
softwareStore.spin.spinning = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化文案处理AI设置
|
||||||
|
*/
|
||||||
|
async function InitCopyWritingAISetting() {
|
||||||
|
try {
|
||||||
|
let initRes = await window.options.InitCopyWritingAISetting()
|
||||||
|
if (initRes.code == 0) {
|
||||||
|
window.api.showGlobalMessageDialog(initRes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
optionStore.CW_AISetting = initRes.data
|
||||||
|
message.success('同步/初始化/加载 文案处理AI设置成功')
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('初始化文案处理AI设置失败,错误信息:' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化文案处理界面数据
|
||||||
|
*/
|
||||||
|
async function InitCopyWritingData() {
|
||||||
|
try {
|
||||||
|
let initRes = await window.options.GetOptionByKey(OptionKeyName.CW_AISimpleSetting)
|
||||||
|
if (initRes.code == 0) {
|
||||||
|
window.api.showGlobalMessageDialog(initRes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(initRes)
|
||||||
|
if (initRes.data == null) {
|
||||||
|
// 初始化数据
|
||||||
|
optionStore.CW_AISimpleSetting = {
|
||||||
|
gptType: undefined,
|
||||||
|
gptData: undefined,
|
||||||
|
gptAI: 'laiapi',
|
||||||
|
isStream: false,
|
||||||
|
isSplit: false,
|
||||||
|
splitNumber: 500,
|
||||||
|
oldWord: '',
|
||||||
|
newWord: '',
|
||||||
|
oldWordCount: 0,
|
||||||
|
newWordCount: 0,
|
||||||
|
wordStruct: []
|
||||||
|
}
|
||||||
|
// 保存到数据库
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
message.success('未找到文案处理界面数据,已初始化数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
optionStore.CW_AISimpleSetting = JSON.parse(initRes.data.value)
|
||||||
|
}
|
||||||
|
message.success('同步/初始化/加载 文案处理界面数据成功')
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('初始化文案处理界面数据失败,错误信息:' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<div class="copy-writing-show">
|
||||||
|
<div>
|
||||||
|
<div style="display: flex; align-items: center; flex-direction: row; gap: 10px">
|
||||||
|
<n-button type="info" size="small" @click="CopyNewData"> 复制 </n-button>
|
||||||
|
<n-button type="info" size="small" @click="FormatOutput"> 一键格式化 </n-button>
|
||||||
|
<span style="font-size: 16px; color: red">
|
||||||
|
注意:这边的格式化不一定会完全格式化,需要自己手动检查
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<n-input
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{
|
||||||
|
minRows: 18,
|
||||||
|
maxRows: 18
|
||||||
|
}"
|
||||||
|
style="margin-top: 10px"
|
||||||
|
v-model:value="word"
|
||||||
|
placeholder="请输入内容"
|
||||||
|
:rows="4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 20px; color: red">
|
||||||
|
注意:当前弹窗的修改和格式化只在改界面有效,关闭或重新打开会重新加载同步外部表格数据,之前修改的数据会试下
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { NButton, NInput, useMessage } from 'naive-ui'
|
||||||
|
import { useOptionStore } from '@/stores/option'
|
||||||
|
import { isEmpty } from 'lodash'
|
||||||
|
let optionStore = useOptionStore()
|
||||||
|
|
||||||
|
let word = ref('')
|
||||||
|
let message = useMessage()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
word.value = optionStore.CW_AISimpleSetting.wordStruct.map((item) => item.newWord).join('\n')
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制新数据
|
||||||
|
*/
|
||||||
|
async function CopyNewData() {
|
||||||
|
try {
|
||||||
|
let copyData = word.value
|
||||||
|
await navigator.clipboard.writeText(copyData)
|
||||||
|
message.success('复制成功')
|
||||||
|
} catch (error) {
|
||||||
|
message.error('复制失败,错误信息:' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化输出
|
||||||
|
*/
|
||||||
|
async function FormatOutput() {
|
||||||
|
let splitData = word.value.split('\n').filter((item) => {
|
||||||
|
return !isEmpty(item)
|
||||||
|
})
|
||||||
|
let isNumberedFormat = (str) => {
|
||||||
|
return /^\d+\./.test(str)
|
||||||
|
}
|
||||||
|
let isTextFormat = (str) => {
|
||||||
|
return /^【文本】/.test(str)
|
||||||
|
}
|
||||||
|
let type = undefined
|
||||||
|
|
||||||
|
splitData = splitData.map((item) => {
|
||||||
|
if (isNumberedFormat(item)) {
|
||||||
|
type = 'startNumber'
|
||||||
|
return item.replace(/^\d+\./, '')
|
||||||
|
} else if (isTextFormat(item)) {
|
||||||
|
type = 'startText'
|
||||||
|
return item.replace('&【', '\n【')
|
||||||
|
} else {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (type == 'startNumber') {
|
||||||
|
word.value = splitData.join('\n')
|
||||||
|
} else {
|
||||||
|
word.value = splitData.join('\n\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="min-width: 800px; overflow: auto">
|
<div style="min-width: 800px; overflow: auto">
|
||||||
<div style="width: 100%">
|
<div style="width: 100%">
|
||||||
<n-form ref="formRef" :model="formValue" inline label-placement="left">
|
<n-form ref="formRef" :model="optionStore.CW_AISimpleSetting" inline label-placement="left">
|
||||||
<n-form-item label="选择类型" path="gptType">
|
<n-form-item label="选择类型" path="gptType">
|
||||||
<n-select
|
<n-select
|
||||||
style="width: 160px"
|
style="width: 160px"
|
||||||
v-model:value="formValue.gptType"
|
v-model:value="optionStore.CW_AISimpleSetting.gptType"
|
||||||
@update:value="UpdateSelectPromptType"
|
@update:value="UpdateSelectPromptType"
|
||||||
:options="gptTypeOptions"
|
:options="gptTypeOptions"
|
||||||
placeholder="请选择提示词类型"
|
placeholder="请选择提示词类型"
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<n-form-item label="选择预设" path="gptData">
|
<n-form-item label="选择预设" path="gptData">
|
||||||
<n-select
|
<n-select
|
||||||
style="width: 200px"
|
style="width: 200px"
|
||||||
v-model:value="formValue.gptData"
|
v-model:value="optionStore.CW_AISimpleSetting.gptData"
|
||||||
:options="gptDataOptions"
|
:options="gptDataOptions"
|
||||||
placeholder="请选择提示词数据"
|
placeholder="请选择提示词数据"
|
||||||
>
|
>
|
||||||
@ -24,7 +24,7 @@
|
|||||||
<n-form-item label="选择请求AI" path="gptAI">
|
<n-form-item label="选择请求AI" path="gptAI">
|
||||||
<n-select
|
<n-select
|
||||||
style="width: 160px"
|
style="width: 160px"
|
||||||
v-model:value="formValue.gptAI"
|
v-model:value="optionStore.CW_AISimpleSetting.gptAI"
|
||||||
:options="gptOptions"
|
:options="gptOptions"
|
||||||
placeholder="请选择AI"
|
placeholder="请选择AI"
|
||||||
>
|
>
|
||||||
@ -34,100 +34,80 @@
|
|||||||
></n-button>
|
></n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item>
|
<n-form-item>
|
||||||
<n-button type="primary" @click="ActionStart">开始生成</n-button>
|
<n-button type="primary" @click="SaveData">保存数据</n-button>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<n-button type="primary" @click="ActionStart">开始 AI 生成</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
<n-form :model="formValue" inline label-placement="left">
|
<n-form :model="optionStore.CW_AISimpleSetting" inline label-placement="left">
|
||||||
<n-form-item path="isStream">
|
<n-form-item path="isStream">
|
||||||
<n-checkbox label="是否流式发送" v-model:checked="formValue.isStream" />
|
<n-checkbox
|
||||||
|
label="是否流式发送"
|
||||||
|
v-model:checked="optionStore.CW_AISimpleSetting.isStream"
|
||||||
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item path="isSplit">
|
<n-form-item path="isSplit">
|
||||||
<n-checkbox label="是否拆分发送" v-model:checked="formValue.isSplit" />
|
<n-checkbox
|
||||||
|
label="是否拆分发送"
|
||||||
|
v-model:checked="optionStore.CW_AISimpleSetting.isSplit"
|
||||||
|
@update:checked="handleCheckedChange"
|
||||||
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="每次发送字符" path="splitNumber">
|
<n-form-item label="单次最大字符数" path="splitNumber">
|
||||||
<n-input-number
|
<n-input-number
|
||||||
v-model:value="formValue.splitNumber"
|
v-model:value="optionStore.CW_AISimpleSetting.splitNumber"
|
||||||
:min="1"
|
:min="1"
|
||||||
:show-button="false"
|
:show-button="false"
|
||||||
:max="99999"
|
:max="99999"
|
||||||
></n-input-number>
|
></n-input-number>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item path="splitNumber">
|
<n-form-item path="splitNumber">
|
||||||
<div style="color: red">注意:爆款开头不要分段发送</div>
|
<div style="color: red; font-size: 20px">注意:爆款开头不要拆分发送</div>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; width: 100%">
|
|
||||||
<div style="flex: 1; margin-right: 10px">
|
|
||||||
<n-input
|
|
||||||
type="textarea"
|
|
||||||
:autosize="{ minRows: 30, maxRows: 30 }"
|
|
||||||
v-model:value="oldWord"
|
|
||||||
placeholder="请输入内容"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div style="flex: 1">
|
|
||||||
<n-input
|
|
||||||
type="textarea"
|
|
||||||
:autosize="{ minRows: 30, maxRows: 30 }"
|
|
||||||
v-model:value="newWord"
|
|
||||||
placeholder="请输入内容"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; justify-content: flex-end; align-items: center; margin-right: 20px">
|
|
||||||
<n-button type="primary" @click="FormatOutput()"> 格式化 </n-button>
|
|
||||||
<div style="color: red; margin-left: 5px">
|
|
||||||
注意:由于GPT输出的格式化有太多的不确定,不一定可以完全格式,需要手动检查
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
|
import { ref, onMounted, onUnmounted, toRaw, h } from 'vue'
|
||||||
import {
|
import {
|
||||||
useMessage,
|
useMessage,
|
||||||
useDialog,
|
useDialog,
|
||||||
NForm,
|
NForm,
|
||||||
NFormItem,
|
NFormItem,
|
||||||
NInput,
|
|
||||||
NSelect,
|
NSelect,
|
||||||
NButton,
|
NButton,
|
||||||
NIcon,
|
NIcon,
|
||||||
NCheckbox,
|
NCheckbox,
|
||||||
NInputNumber
|
NInputNumber
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { SettingsOutline } from '@vicons/ionicons5'
|
import { Copy, SettingsOutline } from '@vicons/ionicons5'
|
||||||
import ManageAISetting from './ManageAISetting.vue'
|
import ManageAISetting from './ManageAISetting.vue'
|
||||||
import { useSoftwareStore } from '../../../../stores/software'
|
import { useSoftwareStore } from '../../../../stores/software'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
import { DEFINE_STRING } from '../../../../define/define_string'
|
import { DEFINE_STRING } from '../../../../define/define_string'
|
||||||
|
import { useOptionStore } from '@/stores/option'
|
||||||
|
import { OptionKeyName, OptionType } from '@/define/enum/option'
|
||||||
|
import CopyWriting from '../../common/copyWriting'
|
||||||
|
import { TimeDelay } from '@/define/Tools/time'
|
||||||
|
|
||||||
|
let optionStore = useOptionStore()
|
||||||
|
let softwareStore = useSoftwareStore()
|
||||||
|
|
||||||
let message = useMessage()
|
let message = useMessage()
|
||||||
let dialog = useDialog()
|
let dialog = useDialog()
|
||||||
let formValue = ref({
|
|
||||||
gptType: undefined,
|
|
||||||
gptData: undefined,
|
|
||||||
gptAI: undefined,
|
|
||||||
isStream: true,
|
|
||||||
isSplit: false,
|
|
||||||
splitNumber: 1000
|
|
||||||
})
|
|
||||||
let oldWord = ref('')
|
|
||||||
let newWord = ref('')
|
|
||||||
let gptTypeOptions = ref([])
|
let gptTypeOptions = ref([])
|
||||||
let gptDataOptions = ref([])
|
let gptDataOptions = ref([])
|
||||||
let gptAllData = undefined
|
let gptAllData = undefined
|
||||||
let formRef = ref(null)
|
let formRef = ref(null)
|
||||||
let gptOptions = ref([
|
let gptOptions = ref([{ label: 'LAI API', value: 'laiapi' }])
|
||||||
{ label: 'LAI API', value: 'laiapi' },
|
|
||||||
{ label: 'KIMI', value: 'kimi' }
|
|
||||||
])
|
|
||||||
let softwareStore = useSoftwareStore()
|
|
||||||
let allPromptDataOptions = ref([])
|
let allPromptDataOptions = ref([])
|
||||||
|
|
||||||
// 加载服务端数据
|
/**
|
||||||
|
* 加载远程提示词数据,包括提示词预设等
|
||||||
|
*/
|
||||||
async function InitServerGptOptions() {
|
async function InitServerGptOptions() {
|
||||||
let gptRes = await window.gpt.InitServerGptOptions()
|
let gptRes = await window.gpt.InitServerGptOptions()
|
||||||
if (gptRes.code == 0) {
|
if (gptRes.code == 0) {
|
||||||
@ -168,7 +148,13 @@ function debounce(func, wait) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let UpdateWord = debounce((value) => {
|
let UpdateWord = debounce((value) => {
|
||||||
newWord.value = value
|
debugger
|
||||||
|
let index = optionStore.CW_AISimpleSetting.wordStruct.findIndex((item) => item.id == value.id)
|
||||||
|
if (index == -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
optionStore.CW_AISimpleSetting.wordStruct[index].newWord = value.newWord
|
||||||
|
// newWord.value += value
|
||||||
}, 300)
|
}, 300)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@ -183,46 +169,38 @@ onUnmounted(() => {
|
|||||||
window.api.removeEventListen(DEFINE_STRING.GPT.GPT_STREAM_RETURN)
|
window.api.removeEventListen(DEFINE_STRING.GPT.GPT_STREAM_RETURN)
|
||||||
})
|
})
|
||||||
|
|
||||||
let ruleObj = (errorMessage) => {
|
async function handleCheckedChange(checked) {
|
||||||
return [
|
softwareStore.spin.spinning = true
|
||||||
{
|
softwareStore.spin.tip = '数据处理中......'
|
||||||
required: true,
|
try {
|
||||||
validator(rule, value) {
|
if (checked) {
|
||||||
if (value == null || value == '') return new Error(errorMessage)
|
await CopyWriting.SplitOrMergeOldText(true)
|
||||||
|
} else {
|
||||||
|
await CopyWriting.SplitOrMergeOldText(false)
|
||||||
|
}
|
||||||
|
await TimeDelay(500)
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.message)
|
||||||
|
} finally {
|
||||||
|
softwareStore.spin.spinning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存数据
|
||||||
|
*/
|
||||||
|
async function SaveData() {
|
||||||
|
let res = await window.options.ModifyOptionByKey(
|
||||||
|
OptionKeyName.CW_AISimpleSetting,
|
||||||
|
JSON.stringify(optionStore.CW_AISimpleSetting),
|
||||||
|
OptionType.JOSN
|
||||||
|
)
|
||||||
|
if (res.code == 0) {
|
||||||
|
window.api.showGlobalMessageDialog(res)
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
message.success('数据保存成功')
|
||||||
return true
|
return true
|
||||||
},
|
|
||||||
trigger: ['input', 'blur', 'change']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
async function FormatOutput() {
|
|
||||||
let splitData = newWord.value.split('\n').filter((item) => {
|
|
||||||
return !isEmpty(item)
|
|
||||||
})
|
|
||||||
let isNumberedFormat = (str) => {
|
|
||||||
return /^\d+\./.test(str)
|
|
||||||
}
|
|
||||||
let isTextFormat = (str) => {
|
|
||||||
return /^【文本】/.test(str)
|
|
||||||
}
|
|
||||||
let type = undefined
|
|
||||||
|
|
||||||
splitData = splitData.map((item) => {
|
|
||||||
if (isNumberedFormat(item)) {
|
|
||||||
type = 'startNumber'
|
|
||||||
return item.replace(/^\d+\./, '')
|
|
||||||
} else if (isTextFormat(item)) {
|
|
||||||
type = 'startText'
|
|
||||||
return item.replace('&【', '\n【')
|
|
||||||
} else {
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (type == 'startNumber') {
|
|
||||||
newWord.value = splitData.join('\n')
|
|
||||||
} else {
|
|
||||||
newWord.value = splitData.join('\n\n')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,13 +211,12 @@ async function AISetting() {
|
|||||||
// 判断当前数据是不是存在
|
// 判断当前数据是不是存在
|
||||||
// 处理数据。获取当前的所有的数据
|
// 处理数据。获取当前的所有的数据
|
||||||
let dialogWidth = 800
|
let dialogWidth = 800
|
||||||
let dialogHeight = 600
|
|
||||||
dialog.create({
|
dialog.create({
|
||||||
title: 'AI设置',
|
title: 'AI设置',
|
||||||
showIcon: false,
|
showIcon: false,
|
||||||
closeOnEsc: false,
|
closeOnEsc: false,
|
||||||
content: () => h(ManageAISetting, { type: formValue.value.gptAI }),
|
content: () => h(ManageAISetting, { type: optionStore.CW_AISimpleSetting.gptAI }),
|
||||||
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
|
style: `width : ${dialogWidth}px`,
|
||||||
maskClosable: false,
|
maskClosable: false,
|
||||||
onClose: async () => {}
|
onClose: async () => {}
|
||||||
})
|
})
|
||||||
@ -249,25 +226,30 @@ async function AISetting() {
|
|||||||
* 开始执行GPT
|
* 开始执行GPT
|
||||||
*/
|
*/
|
||||||
async function ActionStart() {
|
async function ActionStart() {
|
||||||
// 检查是不是有数据
|
try {
|
||||||
if (
|
|
||||||
isEmpty(formValue.value.gptType) ||
|
|
||||||
isEmpty(formValue.value.gptData) ||
|
|
||||||
isEmpty(formValue.value.gptAI)
|
|
||||||
) {
|
|
||||||
message.error('请选择完整的数据')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
softwareStore.spin.spinning = true
|
softwareStore.spin.spinning = true
|
||||||
softwareStore.spin.tip = '生成中......'
|
softwareStore.spin.tip = '生成中......'
|
||||||
let res = await window.write.ActionStart(toRaw(formValue.value), oldWord.value)
|
let ids = []
|
||||||
softwareStore.spin.spinning = false
|
optionStore.CW_AISimpleSetting.wordStruct.forEach((item) => {
|
||||||
|
ids.push(item.id)
|
||||||
|
})
|
||||||
|
let res = await window.write.CopyWritingAIGeneration(ids)
|
||||||
|
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
message.error(res.message)
|
message.error(res.message)
|
||||||
|
window.api.showGlobalMessageDialog(res)
|
||||||
softwareStore.spin.spinning = false
|
softwareStore.spin.spinning = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
newWord.value = res.data
|
// 这边直接保存一下
|
||||||
|
await CopyWriting.SaveCWAISimpleSetting()
|
||||||
|
window.api.showGlobalMessageDialog(res)
|
||||||
|
await TimeDelay(200)
|
||||||
|
} catch (error) {
|
||||||
|
message.error('生成失败:' + error.message)
|
||||||
|
} finally {
|
||||||
|
softwareStore.spin.spinning = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3,56 +3,22 @@
|
|||||||
<n-card title="LAI API 设置">
|
<n-card title="LAI API 设置">
|
||||||
<div style="display: flex">
|
<div style="display: flex">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="aiSetting.laiapi.gpt_url"
|
v-model:value="optionStore.CW_AISetting.laiapi.gpt_url"
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="请输入GPT URL"
|
placeholder="请输入GPT URL"
|
||||||
/>
|
/>
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="aiSetting.laiapi.api_key"
|
v-model:value="optionStore.CW_AISetting.laiapi.api_key"
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="请输入API KEY"
|
placeholder="请输入API KEY"
|
||||||
/>
|
/>
|
||||||
<n-input v-model:value="aiSetting.laiapi.model" type="text" placeholder="请输入Model" />
|
|
||||||
</div>
|
|
||||||
</n-card>
|
|
||||||
<!-- <n-card title="豆包设置">
|
|
||||||
<div style="display: flex">
|
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="aiSetting.doubao.gpt_url"
|
v-model:value="optionStore.CW_AISetting.laiapi.model"
|
||||||
type="text"
|
type="text"
|
||||||
style="margin-right: 10px"
|
placeholder="请输入Model"
|
||||||
placeholder="请输入豆包的调用网址"
|
|
||||||
/>
|
/>
|
||||||
<n-input
|
|
||||||
v-model:value="aiSetting.doubao.api_key"
|
|
||||||
type="text"
|
|
||||||
style="margin-right: 10px"
|
|
||||||
placeholder="请输入豆包的APIKEY"
|
|
||||||
/>
|
|
||||||
<n-input
|
|
||||||
v-model:value="aiSetting.doubao.model"
|
|
||||||
type="text"
|
|
||||||
placeholder="请输入豆包的模型"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</n-card> -->
|
|
||||||
<n-card title="KIMI设置">
|
|
||||||
<div style="display: flex">
|
|
||||||
<n-input
|
|
||||||
v-model:value="aiSetting.kimi.gpt_url"
|
|
||||||
type="text"
|
|
||||||
style="margin-right: 10px"
|
|
||||||
placeholder="请输入KIMI的网址"
|
|
||||||
/>
|
|
||||||
<n-input
|
|
||||||
v-model:value="aiSetting.kimi.api_key"
|
|
||||||
type="text"
|
|
||||||
style="margin-right: 10px"
|
|
||||||
placeholder="请输入KIMI的APIKEY"
|
|
||||||
/>
|
|
||||||
<n-input v-model:value="aiSetting.kimi.model" type="text" placeholder="请输入KIMI的模型" />
|
|
||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
<div style="display: flex; justify-content: flex-end; margin: 20px">
|
<div style="display: flex; justify-content: flex-end; margin: 20px">
|
||||||
@ -61,75 +27,46 @@
|
|||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
|
import { useMessage, useDialog, NCard, NSpace, NInput, NSelect, NButton } from 'naive-ui'
|
||||||
import { useMessage, NCard, NSpace, NInput, NSelect, NButton } from 'naive-ui'
|
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
|
import { useOptionStore } from '@/stores/option'
|
||||||
|
import { OptionKeyName, OptionType } from '@/define/enum/option'
|
||||||
|
let optionStore = useOptionStore()
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { NCard, NSpace, NInput, NSelect, NButton },
|
|
||||||
props: ['type'],
|
|
||||||
setup(props) {
|
|
||||||
let message = useMessage()
|
let message = useMessage()
|
||||||
let aiSetting = ref({
|
let dialog = useDialog()
|
||||||
laiapi: {
|
|
||||||
gpt_url: undefined,
|
|
||||||
api_key: undefined,
|
|
||||||
model: undefined
|
|
||||||
},
|
|
||||||
kimi: {
|
|
||||||
gpt_url: undefined,
|
|
||||||
api_key: undefined,
|
|
||||||
model: undefined
|
|
||||||
},
|
|
||||||
doubao: { gpt_url: undefined, api_key: undefined, model: undefined }
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
|
|
||||||
// 获取AIsetting
|
|
||||||
let res = await window.gpt.GetAISetting()
|
|
||||||
if (res.code == 0) {
|
|
||||||
message.error(res.message)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
aiSetting.value = res.data
|
|
||||||
})
|
|
||||||
|
|
||||||
function checkValue(obj) {
|
|
||||||
for (const d in obj) {
|
|
||||||
if (isEmpty(d)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
async function SaveAISetting() {
|
async function SaveAISetting() {
|
||||||
// 判断当前是选择是哪个AI,判断是不是有填写
|
let da = dialog.warning({
|
||||||
let checkRes = true
|
title: '提示',
|
||||||
if (props.type == 'laiapi') {
|
content: '确认保存AI设置?这边不会检测数据的可用性,请确保数据填写正确!!!',
|
||||||
checkRes = checkValue(Object.values(aiSetting.value.laiapi))
|
positiveText: '确认',
|
||||||
} else if (props.type == 'kimi') {
|
negativeText: '取消',
|
||||||
checkRes = checkValue(Object.values(aiSetting.value.kimi))
|
onNegativeClick: () => {
|
||||||
} else if (props.type == 'doubao') {
|
message.info('用户取消操作')
|
||||||
checkRes = checkValue(Object.values(aiSetting.value.doubao))
|
return true
|
||||||
}
|
},
|
||||||
|
onPositiveClick: async () => {
|
||||||
if (!checkRes) {
|
da.destroy()
|
||||||
|
// 保存数据
|
||||||
|
let aiSetting = optionStore.CW_AISetting.laiapi
|
||||||
|
if (isEmpty(aiSetting.gpt_url) || isEmpty(aiSetting.api_key) || isEmpty(aiSetting.model)) {
|
||||||
message.error('请填写完整选择的AI相关的设置')
|
message.error('请填写完整选择的AI相关的设置')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 直接保存
|
||||||
let res = await window.gpt.SaveAISetting(toRaw(aiSetting.value))
|
let res = await window.options.ModifyOptionByKey(
|
||||||
|
OptionKeyName.CW_AISetting,
|
||||||
|
JSON.stringify(optionStore.CW_AISetting),
|
||||||
|
OptionType.JSON
|
||||||
|
)
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
message.error(res.message)
|
window.api.showGlobalMessageDialog(res)
|
||||||
return
|
} else {
|
||||||
|
message.success('保存AI设置成功')
|
||||||
}
|
}
|
||||||
message.success('保存成功')
|
|
||||||
}
|
|
||||||
|
|
||||||
return { aiSetting, SaveAISetting }
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -112,15 +112,16 @@
|
|||||||
v-if="formValue.gpt_business == 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65'"
|
v-if="formValue.gpt_business == 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65'"
|
||||||
style="width: 120px; margin-left: 30px"
|
style="width: 120px; margin-left: 30px"
|
||||||
path="gpt_key"
|
path="gpt_key"
|
||||||
label="LAI 站点选择"
|
label="是否国内转发"
|
||||||
>
|
>
|
||||||
<n-select
|
<n-select
|
||||||
v-model:value="formValue.laiApiSelect"
|
v-model:value="formValue.useTransfer"
|
||||||
placeholder="选择LAIAPI的请求站点"
|
:options="[
|
||||||
style="width: 200px"
|
{ label: '是', value: true },
|
||||||
:options="laiApiOptions"
|
{ label: '否', value: false }
|
||||||
>
|
]"
|
||||||
</n-select>
|
style="width: 100px"
|
||||||
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item style="margin-left: 30px" path="gpt_model" label="GPT模型">
|
<n-form-item style="margin-left: 30px" path="gpt_model" label="GPT模型">
|
||||||
<n-select
|
<n-select
|
||||||
@ -225,6 +226,7 @@ let formValue = ref({
|
|||||||
character_select_model: window.config.character_select_model,
|
character_select_model: window.config.character_select_model,
|
||||||
window_wh_bm_remember: window.config.window_wh_bm_remember,
|
window_wh_bm_remember: window.config.window_wh_bm_remember,
|
||||||
laiApiSelect: window.config.laiApiSelect ? window.config.laiApiSelect : LaiAPIType.MAIN,
|
laiApiSelect: window.config.laiApiSelect ? window.config.laiApiSelect : LaiAPIType.MAIN,
|
||||||
|
useTransfer: window.config.useTransfer ?? false,
|
||||||
hdScale: window.config.hdScale ?? 2,
|
hdScale: window.config.hdScale ?? 2,
|
||||||
defaultImageMode: window.config.defaultImageMode ?? 'mj',
|
defaultImageMode: window.config.defaultImageMode ?? 'mj',
|
||||||
defaultVideoMode: window.config.defaultVideoMode ?? ImageToVideoModels.RUNWAY
|
defaultVideoMode: window.config.defaultVideoMode ?? ImageToVideoModels.RUNWAY
|
||||||
|
|||||||
@ -2,53 +2,56 @@
|
|||||||
<div>
|
<div>
|
||||||
<n-form
|
<n-form
|
||||||
label-placement="left"
|
label-placement="left"
|
||||||
label-width="auto"
|
label-width="100"
|
||||||
require-mark-placement="right-hanging"
|
require-mark-placement="right-hanging"
|
||||||
:model="settingStore.ttsSetting.edgeTTS"
|
:model="optionStore.TTS_GlobalSetting.selectModel"
|
||||||
>
|
>
|
||||||
<n-form-item label="合成角色">
|
<n-form-item label="合成角色">
|
||||||
<n-select
|
<n-select
|
||||||
@update:value="ChangeCharacter"
|
@update:value="ChangeCharacter"
|
||||||
v-model:value="settingStore.ttsSetting.edgeTTS.value"
|
v-model:value="optionStore.TTS_GlobalSetting.edgeTTS.value"
|
||||||
:options="roleOptions"
|
:options="roleOptions"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="音量">
|
<n-form-item label="音量">
|
||||||
<n-input-number
|
<n-input-number
|
||||||
v-model:value="settingStore.ttsSetting.edgeTTS.volumn"
|
v-model:value="optionStore.TTS_GlobalSetting.edgeTTS.volumn"
|
||||||
:min="0"
|
:min="-100"
|
||||||
:max="100"
|
:max="100"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="语速">
|
<n-form-item label="语速">
|
||||||
<n-input-number v-model:value="settingStore.ttsSetting.edgeTTS.rate" :min="0" :max="100" />
|
<n-input-number
|
||||||
|
v-model:value="optionStore.TTS_GlobalSetting.edgeTTS.rate"
|
||||||
|
:min="-100"
|
||||||
|
:max="100"
|
||||||
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="语调">
|
<n-form-item label="语调">
|
||||||
<n-input-number v-model:value="settingStore.ttsSetting.edgeTTS.pitch" :min="0" :max="100" />
|
<n-input-number
|
||||||
|
v-model:value="optionStore.TTS_GlobalSetting.edgeTTS.pitch"
|
||||||
|
:min="-100"
|
||||||
|
:max="100"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="超时时间(ms)">
|
||||||
|
<n-input-number
|
||||||
|
v-model:value="optionStore.TTS_GlobalSetting.edgeTTS.timeOut"
|
||||||
|
:min="0"
|
||||||
|
:max="10000000"
|
||||||
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, defineComponent, watch, inject } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import {
|
import { useMessage, NForm, NFormItem, NInputNumber, NSelect } from 'naive-ui'
|
||||||
useMessage,
|
import { useOptionStore } from '@/stores/option'
|
||||||
NForm,
|
|
||||||
NFormItem,
|
|
||||||
NInputNumber,
|
|
||||||
NSelect,
|
|
||||||
NButton,
|
|
||||||
NIcon,
|
|
||||||
NPopover,
|
|
||||||
NCheckbox
|
|
||||||
} from 'naive-ui'
|
|
||||||
import { GetEdgeTTSRole } from '../../../../define/tts/ttsDefine'
|
|
||||||
import { ReaderOutline } from '@vicons/ionicons5'
|
|
||||||
import { useSettingStore } from '../../../../stores/setting'
|
|
||||||
|
|
||||||
let message = useMessage()
|
let message = useMessage()
|
||||||
let settingStore = useSettingStore()
|
let optionStore = useOptionStore()
|
||||||
|
|
||||||
async function SwitchTTSOptions(key) {
|
async function SwitchTTSOptions(key) {
|
||||||
console.log('SwitchTTSOptions', key)
|
console.log('SwitchTTSOptions', key)
|
||||||
@ -73,9 +76,9 @@ onMounted(async () => {
|
|||||||
*/
|
*/
|
||||||
function ChangeCharacter(value) {
|
function ChangeCharacter(value) {
|
||||||
let role = roleOptions.value.find((item) => item.value === value)
|
let role = roleOptions.value.find((item) => item.value === value)
|
||||||
settingStore.ttsSetting.edgeTTS.value = role.value
|
optionStore.TTS_GlobalSetting.edgeTTS.value = role.value
|
||||||
settingStore.ttsSetting.edgeTTS.label = role.label
|
optionStore.TTS_GlobalSetting.edgeTTS.label = role.label
|
||||||
settingStore.ttsSetting.edgeTTS.lang = role.lang
|
optionStore.TTS_GlobalSetting.edgeTTS.lang = role.lang
|
||||||
settingStore.ttsSetting.edgeTTS.gender = role.gender
|
optionStore.TTS_GlobalSetting.edgeTTS.gender = role.gender
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,66 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="display: flex; min-width: 900px; overflow: auto">
|
<div style="display: flex; min-width: 900px; overflow: auto">
|
||||||
<div class="text-input">
|
<TTSTextInput />
|
||||||
<n-input
|
|
||||||
v-model:value="text"
|
|
||||||
type="textarea"
|
|
||||||
placeholder="请输入配音的文本内容"
|
|
||||||
:autosize="{
|
|
||||||
minRows: 30,
|
|
||||||
maxRows: 30
|
|
||||||
}"
|
|
||||||
show-count
|
|
||||||
></n-input>
|
|
||||||
<div class="tts-options">
|
|
||||||
<n-button
|
|
||||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
|
||||||
size="small"
|
|
||||||
@click="FormatWordString"
|
|
||||||
>
|
|
||||||
格式化文档
|
|
||||||
</n-button>
|
|
||||||
<n-popover trigger="hover">
|
|
||||||
<template #trigger>
|
|
||||||
<n-button quaternary circle color="#b6a014" @click="ModifySplitChar">
|
|
||||||
<template #icon>
|
|
||||||
<n-icon size="25"> <AddCircleOutline /> </n-icon>
|
|
||||||
</template>
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
<span>添加分割标识符</span>
|
|
||||||
</n-popover>
|
|
||||||
<n-button
|
|
||||||
style="margin-right: 10px"
|
|
||||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
|
||||||
size="small"
|
|
||||||
@click="ClearText"
|
|
||||||
>
|
|
||||||
清空内容
|
|
||||||
</n-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="audio-setting">
|
<div class="audio-setting">
|
||||||
<div class="param-setting">
|
<div class="param-setting">
|
||||||
<n-form label-placement="left">
|
<n-form label-placement="left">
|
||||||
<n-form-item label="选择配音渠道">
|
<n-form-item label="选择配音渠道">
|
||||||
<n-select
|
<n-select
|
||||||
placeholder="请选择配音渠道"
|
placeholder="请选择配音渠道"
|
||||||
v-model:value="settingStore.ttsSetting.selectModel"
|
v-model:value="optionStore.TTS_GlobalSetting.selectModel"
|
||||||
:options="ttsOptions"
|
:options="ttsOptions"
|
||||||
></n-select>
|
></n-select>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
<EdgeTTS v-if="settingStore.ttsSetting.selectModel == 'edge-tts'" />
|
<EdgeTTS v-if="optionStore.TTS_GlobalSetting.selectModel == 'edge-tts'" />
|
||||||
<AzureTTS
|
<!-- <AzureTTS
|
||||||
:azureTTS="settingStore.ttsSetting.azureTTS"
|
:azureTTS="settingStore.ttsSetting.azureTTS"
|
||||||
v-else-if="settingStore.ttsSetting.selectModel == 'azure-tts'"
|
v-else-if="optionStore.TTS_GlobalSetting.selectModel == 'azure-tts'"
|
||||||
/>
|
/> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="autio-button">
|
<div class="autio-button">
|
||||||
<n-button
|
<n-button
|
||||||
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
@click="SaveTTSConfig"
|
@click="SaveTTSGlobalSetting"
|
||||||
>
|
>
|
||||||
保存配置信息
|
保存配置信息
|
||||||
</n-button>
|
</n-button>
|
||||||
@ -80,17 +42,21 @@
|
|||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; align-items: center">
|
<div style="display: flex; align-items: center">
|
||||||
<audio ref="audio" :src="audioUrl" controls style="width: 100%"></audio>
|
<audio
|
||||||
|
ref="audio"
|
||||||
|
:src="optionStore.TTS_GlobalSetting.saveAudioPath"
|
||||||
|
controls
|
||||||
|
style="width: 100%"
|
||||||
|
></audio>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, reactive, h } from 'vue'
|
import { ref, onMounted, toRaw, h } from 'vue'
|
||||||
import {
|
import {
|
||||||
useMessage,
|
useMessage,
|
||||||
NInput,
|
|
||||||
NSelect,
|
NSelect,
|
||||||
NFormItem,
|
NFormItem,
|
||||||
NForm,
|
NForm,
|
||||||
@ -101,156 +67,93 @@ import {
|
|||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import EdgeTTS from './EdgeTTS.vue'
|
import EdgeTTS from './EdgeTTS.vue'
|
||||||
import AzureTTS from './AzureTTS.vue'
|
import AzureTTS from './AzureTTS.vue'
|
||||||
import { GetTTSSelect } from '../../../../define/tts/ttsDefine'
|
|
||||||
import { useSoftwareStore } from '../../../../stores/software'
|
import { useSoftwareStore } from '../../../../stores/software'
|
||||||
import { AddCircleOutline } from '@vicons/ionicons5'
|
|
||||||
import InputDialogContent from '../Original/Components/InputDialogContent.vue'
|
|
||||||
import { useSettingStore } from '../../../../stores/setting'
|
import { useSettingStore } from '../../../../stores/setting'
|
||||||
import TTSHistory from './TTSHistory.vue'
|
import TTSHistory from './TTSHistory.vue'
|
||||||
import { FormatWord } from '../../../../define/Tools/write'
|
import TTSTextInput from './TTSTextInput.vue'
|
||||||
|
import { useOptionStore } from '@/stores/option'
|
||||||
|
import { TimeDelay } from '@/define/Tools/time'
|
||||||
|
import InitCommon from '../../common/initCommon'
|
||||||
|
import TTSCommon from '../../common/ttsCommon'
|
||||||
|
let optionStore = useOptionStore()
|
||||||
|
|
||||||
let message = useMessage()
|
let message = useMessage()
|
||||||
let dialog = useDialog()
|
let dialog = useDialog()
|
||||||
|
|
||||||
let softwareStore = useSoftwareStore()
|
let softwareStore = useSoftwareStore()
|
||||||
let text = ref('你好,我是你的智能语音助手')
|
|
||||||
let ttsOptions = ref([
|
let ttsOptions = ref([
|
||||||
{
|
{
|
||||||
label: 'EdgeTTS(免费)',
|
label: 'EdgeTTS(免费)',
|
||||||
value: 'edge-tts'
|
value: 'edge-tts'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
let splitRef = ref(null)
|
|
||||||
let settingStore = useSettingStore()
|
let settingStore = useSettingStore()
|
||||||
let writeSetting = ref({
|
|
||||||
split_char: '。,“”‘’!?【】《》()…—:;.,\'\'""!?[]<>()...-:;',
|
|
||||||
merge_count: 3,
|
|
||||||
merge_char: ',',
|
|
||||||
end_char: '。'
|
|
||||||
})
|
|
||||||
let audioUrl = ref(null)
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
softwareStore.spin.spinning = true
|
await InitTTSGlobalSetting()
|
||||||
softwareStore.spin.tip = '正在加载,请稍等...'
|
})
|
||||||
try {
|
|
||||||
// 加载服务端的TTS配置(目前的TTS配置是全局的)
|
|
||||||
let res = await window.tts.GetTTSCOnfig()
|
|
||||||
if (res.code == 0) {
|
|
||||||
message.error(res.message)
|
|
||||||
} else {
|
|
||||||
settingStore.ttsSetting = res.data
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载文字设置
|
/**
|
||||||
let writeSettingRes = await window.write.GetWriteCOnfig()
|
* 初始化TTS配置信息
|
||||||
if (writeSettingRes.code == 0) {
|
*/
|
||||||
message.error(writeSettingRes.message)
|
async function InitTTSGlobalSetting() {
|
||||||
} else {
|
try {
|
||||||
writeSetting.value = writeSettingRes.data
|
softwareStore.spin.spinning = true
|
||||||
}
|
softwareStore.spin.tip = '正在加载数据,请稍等...'
|
||||||
|
await InitCommon.InitTTSGlobalSetting()
|
||||||
|
|
||||||
|
await TimeDelay(300)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('加载失败,失败原因:' + error.toString())
|
window.api.showGlobalMessageDialog({
|
||||||
|
code: 0,
|
||||||
|
message: '加载或者是初始化TTS信息失败,失败原因:' + error.toString()
|
||||||
|
})
|
||||||
} finally {
|
} finally {
|
||||||
softwareStore.spin.spinning = false
|
softwareStore.spin.spinning = false
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改分割符
|
|
||||||
*/
|
|
||||||
async function ModifySplitChar() {
|
|
||||||
// 判断当前数据是不是存在
|
|
||||||
// 处理数据。获取当前的所有的数据
|
|
||||||
let dialogWidth = 400
|
|
||||||
let dialogHeight = 150
|
|
||||||
dialog.create({
|
|
||||||
title: '添加分割符',
|
|
||||||
showIcon: false,
|
|
||||||
closeOnEsc: false,
|
|
||||||
content: () =>
|
|
||||||
h(InputDialogContent, {
|
|
||||||
ref: splitRef,
|
|
||||||
initData: writeSetting.value.split_char,
|
|
||||||
placeholder: '请输入分割符'
|
|
||||||
}),
|
|
||||||
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
|
|
||||||
maskClosable: false,
|
|
||||||
onClose: async () => {
|
|
||||||
writeSetting.value.split_char = splitRef.value.data
|
|
||||||
// 保存数据
|
|
||||||
let saveRes = await window.write.SaveWriteConfig(toRaw(writeSetting.value))
|
|
||||||
if (saveRes.code == 0) {
|
|
||||||
message.error(saveRes.message)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
message.success('分隔符保存成功')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析/格式化文档
|
* 调用方法保存全部配置信息
|
||||||
*/
|
*/
|
||||||
async function FormatWordString() {
|
async function SaveTTSGlobalSetting() {
|
||||||
try {
|
try {
|
||||||
let wordSrr = FormatWord(text.value)
|
await TTSCommon.SaveTTSGlobalSetting()
|
||||||
text.value = wordSrr.join('\n')
|
message.success('保存配置信息成功')
|
||||||
message.success('文本格式化成功')
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('文本格式化失败,失败原因:' + error.toString())
|
message.error('保存配置信息失败,失败原因:' + error.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除文本内容
|
|
||||||
*/
|
|
||||||
async function ClearText() {
|
|
||||||
text.value = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存TTS配置信息
|
|
||||||
*/
|
|
||||||
async function SaveTTSConfig() {
|
|
||||||
let saveRes = await window.tts.SaveTTSConfig(toRaw(settingStore.ttsSetting))
|
|
||||||
if (saveRes.code == 0) {
|
|
||||||
message.error(saveRes.message)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
message.success('TTS配置保存成功')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始合成音频
|
* 开始合成音频
|
||||||
*/
|
*/
|
||||||
async function GenerateAudio() {
|
async function GenerateAudio() {
|
||||||
if (text.value == '') {
|
try {
|
||||||
|
if (optionStore.TTS_GlobalSetting.ttsText == '') {
|
||||||
message.error('文本内容不能为空')
|
message.error('文本内容不能为空')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
softwareStore.spin.spinning = true
|
softwareStore.spin.spinning = true
|
||||||
softwareStore.spin.tip = '正在合成音频,请稍等...'
|
softwareStore.spin.tip = '正在合成音频,请稍等...'
|
||||||
|
|
||||||
// 保存配置信息
|
// 保存配置信息
|
||||||
let saveRes = await window.tts.SaveTTSConfig(toRaw(settingStore.ttsSetting))
|
await TTSCommon.SaveTTSGlobalSetting()
|
||||||
if (saveRes.code == 0) {
|
|
||||||
softwareStore.spin.spinning = false
|
|
||||||
message.error(saveRes.message)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开始真正的合成音频
|
// 开始真正的合成音频
|
||||||
let generateRes = await window.tts.GenerateAudio(text.value)
|
let generateRes = await window.tts.GenerateAudio()
|
||||||
if (generateRes.code == 0) {
|
if (generateRes.code == 1) {
|
||||||
softwareStore.spin.spinning = false
|
optionStore.TTS_GlobalSetting.saveAudioPath = generateRes.data.mp3Path
|
||||||
message.error(generateRes.message)
|
// 合成完毕保存数据
|
||||||
return
|
await TTSCommon.SaveTTSGlobalSetting()
|
||||||
}
|
}
|
||||||
|
window.api.showGlobalMessageDialog(generateRes)
|
||||||
audioUrl.value = generateRes.mp3Path
|
} catch (error) {
|
||||||
|
message.error('合成音频失败,失败原因:' + error.toString())
|
||||||
|
} finally {
|
||||||
softwareStore.spin.spinning = false
|
softwareStore.spin.spinning = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 显示配音的历史记录
|
// 显示配音的历史记录
|
||||||
function ShowHistory() {
|
function ShowHistory() {
|
||||||
@ -273,19 +176,8 @@ function ShowHistory() {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
.text-input {
|
|
||||||
flex: 4;
|
|
||||||
height: 100%;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
.audio-setting {
|
.audio-setting {
|
||||||
flex: 2;
|
flex: 2;
|
||||||
margin: 0 20px;
|
margin: 0 20px;
|
||||||
}
|
}
|
||||||
.tts-options {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-right: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
145
src/renderer/src/components/TTS/TTSTextInput.vue
Normal file
145
src/renderer/src/components/TTS/TTSTextInput.vue
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<div class="text-input">
|
||||||
|
<n-input
|
||||||
|
v-model:value="optionStore.TTS_GlobalSetting.ttsText"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="请输入配音的文本内容"
|
||||||
|
:autosize="{
|
||||||
|
minRows: 30,
|
||||||
|
maxRows: 30
|
||||||
|
}"
|
||||||
|
show-count
|
||||||
|
></n-input>
|
||||||
|
<div class="tts-options">
|
||||||
|
<n-button :color="softwareStore.SoftColor.BROWN_YELLOW" size="small" @click="formatWrite">
|
||||||
|
格式化文档
|
||||||
|
</n-button>
|
||||||
|
<n-popover trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button quaternary circle color="#b6a014" @click="AddSplitChar">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon size="25"> <AddCircleOutline /> </n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
<span>添加分割标识符</span>
|
||||||
|
</n-popover>
|
||||||
|
<n-button
|
||||||
|
style="margin-right: 10px"
|
||||||
|
:color="softwareStore.SoftColor.BROWN_YELLOW"
|
||||||
|
size="small"
|
||||||
|
@click="ClearText"
|
||||||
|
>
|
||||||
|
清空内容
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, h } from 'vue'
|
||||||
|
import { NInput, NButton, NPopover, NIcon, useMessage, useDialog } from 'naive-ui'
|
||||||
|
import { AddCircleOutline } from '@vicons/ionicons5'
|
||||||
|
import InitCommon from '../../common/initCommon'
|
||||||
|
import TextCommon from '../../common/text'
|
||||||
|
import InputDialogContent from '../Original/Components/InputDialogContent.vue'
|
||||||
|
|
||||||
|
import { useSoftwareStore } from '@/stores/software'
|
||||||
|
import { useOptionStore } from '@/stores/option'
|
||||||
|
import { OptionKeyName, OptionType } from '@/define/enum/option'
|
||||||
|
import TTSCommon from '../../common/ttsCommon'
|
||||||
|
|
||||||
|
let softwareStore = useSoftwareStore()
|
||||||
|
let optionStore = useOptionStore()
|
||||||
|
|
||||||
|
let dialog = useDialog()
|
||||||
|
let message = useMessage()
|
||||||
|
|
||||||
|
let split_ref = ref(null)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await InitCommon.InitSpecialCharacters()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加分割符号
|
||||||
|
*/
|
||||||
|
async function AddSplitChar() {
|
||||||
|
// 判断当前数据是不是存在
|
||||||
|
// 处理数据。获取当前的所有的数据
|
||||||
|
let dialogWidth = 400
|
||||||
|
let dialogHeight = 150
|
||||||
|
dialog.create({
|
||||||
|
title: '添加分割符',
|
||||||
|
showIcon: false,
|
||||||
|
closeOnEsc: false,
|
||||||
|
content: () =>
|
||||||
|
h(InputDialogContent, {
|
||||||
|
ref: split_ref,
|
||||||
|
initData: optionStore.CW_FormatSpecialChar,
|
||||||
|
placeholder: '请输入分割符'
|
||||||
|
}),
|
||||||
|
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
|
||||||
|
maskClosable: false,
|
||||||
|
onClose: async () => {
|
||||||
|
optionStore.CW_FormatSpecialChar = split_ref.value.data
|
||||||
|
let saveRes = await window.options.ModifyOptionByKey(
|
||||||
|
OptionKeyName.CW_FormatSpecialChar,
|
||||||
|
optionStore.CW_FormatSpecialChar,
|
||||||
|
OptionType.STRING
|
||||||
|
)
|
||||||
|
if (saveRes.code == 0) {
|
||||||
|
window.api.showGlobalMessageDialog(saveRes)
|
||||||
|
// 报错 不能关闭
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
message.success('数据保存成功')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化文案
|
||||||
|
*/
|
||||||
|
async function formatWrite() {
|
||||||
|
try {
|
||||||
|
let newText = await TextCommon.FormatText(optionStore.TTS_GlobalSetting.ttsText)
|
||||||
|
optionStore.TTS_GlobalSetting.ttsText = newText
|
||||||
|
await TTSCommon.SaveTTSGlobalSetting()
|
||||||
|
message.success('格式化成功')
|
||||||
|
} catch (error) {
|
||||||
|
message.error('格式化失败,失败原因:' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空数据库信息
|
||||||
|
*/
|
||||||
|
async function ClearText() {
|
||||||
|
try {
|
||||||
|
optionStore.TTS_GlobalSetting.ttsText = ''
|
||||||
|
optionStore.TTS_GlobalSetting.saveAudioPath = ''
|
||||||
|
await TTSCommon.SaveTTSGlobalSetting()
|
||||||
|
message.success('清空成功')
|
||||||
|
} catch (error) {
|
||||||
|
message.error('清空失败,失败原因:' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.text-input {
|
||||||
|
flex: 4;
|
||||||
|
height: 100%;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.tts-options {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-right: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -16,7 +16,7 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: '/gptCopywriting',
|
path: '/gptCopywriting',
|
||||||
name: 'gptCopywriting',
|
name: 'gptCopywriting',
|
||||||
component: () => import('./components/CopyWriting/CopyWriting.vue')
|
component: () => import('./components/CopyWriting/CopyWritingHome.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/global_setting',
|
path: '/global_setting',
|
||||||
|
|||||||
74
src/stores/option.ts
Normal file
74
src/stores/option.ts
Normal file
@ -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: {
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user