416 lines
16 KiB
TypeScript
Raw Normal View History

2025-08-19 14:33:59 +08:00
import { OperateBookType, PromptMergeType } from '@/define/enum/bookEnum'
import { Book } from '@/define/model/book/book'
import { errorMessage, SendReturnMessage, successMessage } from '@/public/generalTools'
import { cloneDeep, isEmpty } from 'lodash'
2025-08-19 14:33:59 +08:00
import { AiReasonCommon } from '../../aiReason/aiReasonCommon'
import { DEFINE_STRING } from '@/define/ipcDefineString'
import { GeneralResponse } from '@/define/model/generalResponse'
import { ExecuteConcurrently } from '@/define/Tools/common'
import { OptionKeyName } from '@/define/enum/option'
import { optionSerialization } from '../../option/optionSerialization'
import { SettingModal } from '@/define/model/setting'
import { MJServiceHandle } from '@/main/service/mj/mjServiceHandle'
import { BookBasicHandle } from './bookBasicHandle'
import { PresetCategory } from '@/define/data/presetData'
import { ValidateJsonAndParse } from '@/define/Tools/validate'
import { BookTask } from '@/define/model/book/bookTask'
import { SDServiceHandle } from '../../sd/sdServiceHandle'
import { aiHandle } from '../../ai'
2025-09-04 16:58:42 +08:00
import { AICharacterAnalyseRequestData } from '@/define/data/aiData/aiPrompt/CharacterAndScene/aiCharacterAnalyseRequestData'
import { AISceneAnalyseRequestData } from '@/define/data/aiData/aiPrompt/CharacterAndScene/aiSceneAnalyseRequestData'
import { t } from '@/i18n'
import { OptionRealmService } from '@/define/db/service/optionService'
import { PresetModel } from '@/define/model/preset'
import { AiInferenceModelModel } from '@/define/data/aiData/aiData'
2025-08-19 14:33:59 +08:00
export class BookPromptHandle extends BookBasicHandle {
aiReasonCommon: AiReasonCommon
mjServiceHandle: MJServiceHandle
sdServiceHandle: SDServiceHandle
constructor() {
super()
this.aiReasonCommon = new AiReasonCommon()
this.mjServiceHandle = new MJServiceHandle()
this.sdServiceHandle = new SDServiceHandle()
}
/**
* AI提示词
*
* 使AI推理为每个分镜生成提示词
*
* 1. BOOKTASK
* 2. BOOKTASKDETAIL
* 3. UNDERBOOKTASK
*
*
*
* @param {string} id - operateBookType不同ID或分镜ID
* @param {OperateBookType} operateBookType -
* @param {boolean} coverData -
* true-, false-
* @returns {Promise<SuccessItem | ErrorItem>}
*
* @throws
*
* @example
* // 为整个小说任务生成提示词,不覆盖已有数据
* const result = await bookPromptHandle.OriginalGetAiPrompt(
* "task-123",
* OperateBookType.BOOKTASK,
* false
* );
*/
OriginalGetAiPrompt = async (
id: string,
operateBookType: OperateBookType,
coverData: boolean
): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> => {
try {
let bookTask: Book.SelectBookTask = {} as Book.SelectBookTask
let bookTaskDetails: Book.SelectBookTaskDetail[] = []
let allBookTaskDetails: Book.SelectBookTaskDetail[] = []
await this.InitBookBasicHandle()
if (operateBookType == OperateBookType.BOOKTASK) {
bookTask = await this.bookTaskService.GetBookTaskDataById(id, true)
2025-08-19 14:33:59 +08:00
allBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
bookTaskId: id
})
if (!coverData) {
// 不覆盖数据,只推理空白提示词
bookTaskDetails = allBookTaskDetails.filter((item) => isEmpty(item.gptPrompt))
} else {
// 不覆盖,就是全部
bookTaskDetails = allBookTaskDetails
}
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
let singleBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id, true)
2025-08-19 14:33:59 +08:00
bookTask = await this.bookTaskService.GetBookTaskDataById(
singleBookTaskDetail.bookTaskId as string,
true
2025-08-19 14:33:59 +08:00
)
bookTaskDetails = [singleBookTaskDetail]
} else if (operateBookType == OperateBookType.UNDERBOOKTASK) {
let singleBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id, true)
2025-08-19 14:33:59 +08:00
bookTask = await this.bookTaskService.GetBookTaskDataById(
singleBookTaskDetail.bookTaskId as string,
true
2025-08-19 14:33:59 +08:00
)
// 获取全部的分镜数据
allBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
bookTaskId: bookTask.id
})
// 只要当前行往下的数据
for (let i = 0; i < allBookTaskDetails.length; i++) {
const element = allBookTaskDetails[i]
if (bookTaskDetails == undefined) {
bookTaskDetails = []
}
if (i + 1 >= (singleBookTaskDetail.no as number)) {
bookTaskDetails.push(element)
}
}
} else {
throw new Error(t('未知操作'))
2025-08-19 14:33:59 +08:00
}
if (!allBookTaskDetails || allBookTaskDetails.length <= 0) {
allBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
bookTaskId: bookTask.id
})
}
if (bookTaskDetails.length <= 0) {
throw new Error(t('没有找到要推理的分镜数据'))
2025-08-19 14:33:59 +08:00
}
let generalSettingOption = this.optionRealmService.GetOptionByKey(
OptionKeyName.Software.GeneralSetting
)
let generalSetting = optionSerialization<SettingModal.GeneralSettings>(
generalSettingOption,
t('设置 -> 通用设置')
2025-08-19 14:33:59 +08:00
)
let tasks = [] as Array<() => Promise<any>>
// 获取当前task的角色和场景数据
let autoAnalyzeCharacter = bookTask.autoAnalyzeCharacter ?? '{}'
let autoAnalyzeCharacterData =
ValidateJsonAndParse<BookTask.BookTaskCharacterAndScene>(autoAnalyzeCharacter)
let characterData = autoAnalyzeCharacterData[PresetCategory.Character] ?? []
let sceneData = autoAnalyzeCharacterData[PresetCategory.Scene] ?? []
let characterString = ''
let sceneString = ''
2025-09-04 16:58:42 +08:00
2025-08-19 14:33:59 +08:00
if (characterData.length > 0) {
characterString = characterData.map((item) => item.name + '' + item.prompt).join('\n')
2025-09-04 16:58:42 +08:00
characterString = '角色设定:' + '\n' + characterString
2025-08-19 14:33:59 +08:00
}
if (sceneData.length > 0) {
sceneString = sceneData.map((item) => item.name + '' + item.prompt).join('\n')
2025-09-04 16:58:42 +08:00
sceneString = '场景设定:' + '\n' + sceneString
2025-08-19 14:33:59 +08:00
}
// 获取自定义的提示词数据
let optionsData: AiInferenceModelModel[] = [];
const optionRealmService = await OptionRealmService.getInstance();
let customInferencePreset = optionRealmService.GetOptionByKey(OptionKeyName.InferenceAI.CustomInferencePreset);
if (customInferencePreset != null && customInferencePreset.value != null && !isEmpty(customInferencePreset.value)) {
let customInferencePresetData = optionSerialization<PresetModel.AIPresetTemplate[]>(customInferencePreset, t('设置 -> 推理设置 -> 自定义预设'), []);
for (let i = 0; i < customInferencePresetData.length; i++) {
const element = customInferencePresetData[i];
optionsData.push({
value: element.id,
label: element.name,
hasExample: element.hasExample,
mustCharacter: element.mustCharacter,
requestBody: element.requestBody,
allAndExampleContent: null
})
}
}
2025-08-19 14:33:59 +08:00
// 添加异步任务
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i]
tasks.push(async () => {
let content = await this.aiReasonCommon.OriginalInferencePrompt(
element,
allBookTaskDetails,
15, // 上下文关联行数
2025-09-04 16:58:42 +08:00
characterString,
sceneString, optionsData
2025-08-19 14:33:59 +08:00
)
2025-09-04 16:58:42 +08:00
console.log(element.afterGpt, content)
2025-08-19 14:33:59 +08:00
// 修改推理出来的数据
await this.bookTaskDetailService.ModifyBookTaskDetailById(element.id as string, {
gptPrompt: content
})
// 每次完成,都要向前端返回信息
SendReturnMessage(
{
code: 1,
id: element.id as string,
data: {
content: content,
progress: {
current: i,
total: bookTaskDetails.length
} as GeneralResponse.ProgressResponse
}
},
DEFINE_STRING.BOOK.ORIGINAL_GET_AI_PROMPT_RETURN
)
})
}
// 分批次执行异步任务
await ExecuteConcurrently(tasks, global.am.isPro ? (generalSetting.concurrency ?? 1) : 1)
// 执行完毕
return successMessage(null, t("推理所有数据完成"), 'BookPrompt_OriginalGetPrompt')
2025-08-19 14:33:59 +08:00
} catch (error: any) {
// 处理错误,返回错误信息
return errorMessage(
t("推理所有数据失败,{error}", {
error: error.message
}),
2025-08-19 14:33:59 +08:00
'BookPromptHandle_OriginalGetAiPrompt'
)
}
}
/**
* AI分镜头合并
* @param bookTaskId ID
* @param type
* @returns
* @throws
* @example
* const result = await bookPromptHandle.AIStoryboardMerge("task-123", BookTask.StoryboardMergeType.MJ);
* if (result.code === 1) {
* // 合并成功
* } else {
* // 合并失败
* }
*/
2025-08-19 14:33:59 +08:00
AIStoryboardMerge = async (bookTaskId: string, type: BookTask.StoryboardMergeType) => {
try {
let res = await aiHandle.AIStoryboardMerge(bookTaskId, type)
return successMessage(res, t('AI分镜头合并成功'), 'BookPromptHandle_AIStoryboardMerge')
2025-08-19 14:33:59 +08:00
} catch (error: any) {
return errorMessage(
t('AI分镜头合并成功', {
error: error.message
}),
2025-08-19 14:33:59 +08:00
'BookPromptHandle_AIStoryboardMerge'
)
}
}
/**
*
*
* (MJ或SD)
*
* - MJ_MERGE: 使用MidJourney规则合并提示词
* - SD_MERGE: 使用Stable Diffusion规则合并提示词
*
* operateBookType参数决定
* - BOOKTASK: 处理整个小说任务中的所有分镜
* - BOOKTASKDETAIL: 仅处理单个指定分镜
* - UNDERBOOKTASK: 处理指定分镜及其后续所有分镜
*
* @param {string} id - operateBookType不同ID或分镜ID
* @param {PromptMergeType} type - 使
* @param {OperateBookType} operateBookType -
* @returns {Promise<SuccessItem | ErrorItem>}
*
* @throws {Error}
*
* @example
* // 使用MidJourney规则合并单个分镜的提示词
* const result = await bookPromptHandle.MergePrompt(
* "detail-123",
* PromptMergeType.MJ_MERGE,
* OperateBookType.BOOKTASKDETAIL
* );
*/
MergePrompt = async (
id: string,
type: PromptMergeType,
operateBookType: OperateBookType
): Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> => {
try {
if (type == PromptMergeType.MJ_MERGE) {
return await this.mjServiceHandle.MergeMJPrompt(id, operateBookType)
} else if (type == PromptMergeType.SD_MERGE) {
return await this.sdServiceHandle.MergeSDPrompt(id, operateBookType)
} else {
throw new Error(t('未知的合并模式,请检查'))
2025-08-19 14:33:59 +08:00
}
} catch (error: any) {
return errorMessage(
t("合并提示词失败,{error}", {
error: error.message
}),
2025-08-19 14:33:59 +08:00
'ReverseBook_MergePrompt'
)
}
}
/**
*
*
* 使AI技术分析书籍任务中的所有分镜文本
* AI分析
* autoAnalyzeCharacter字段中JSON格式存储
*
* @param {string} bookTaskId - ID
* @param {PresetCategory} type - PresetCategory.Character()PresetCategory.Scene()
*
* @returns {Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem>}
*
*
* @throws {Error}
*
* @example
* // 分析书籍任务中的角色
* const characterResult = await bookPromptHandle.AutoAnalyzeCharacterOrScene(
* "task-123",
* PresetCategory.Character
* );
*
* // 分析书籍任务中的场景
* const sceneResult = await bookPromptHandle.AutoAnalyzeCharacterOrScene(
* "task-123",
* PresetCategory.Scene
* );
*/
AutoAnalyzeCharacterOrScene = async (bookTaskId: string, type: PresetCategory): Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> => {
2025-08-19 14:33:59 +08:00
try {
if (type != PresetCategory.Character && type != PresetCategory.Scene) {
throw new Error(t('分析的类型只能是角色或场景,请检查'))
2025-08-19 14:33:59 +08:00
}
await this.InitBookBasicHandle()
let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId, true)
2025-08-19 14:33:59 +08:00
let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({
bookTaskId: bookTaskId
})
if (bookTaskDetails.length <= 0) {
throw new Error(t('没有找到要分析的分镜数据请先导入文案或者时srt'))
2025-08-19 14:33:59 +08:00
}
let words = bookTaskDetails
.map((item) => {
return item.afterGpt
})
.join('\r\n')
2025-09-04 16:58:42 +08:00
let requestData: OpenAIRequest.Request
2025-08-19 14:33:59 +08:00
if (type == PresetCategory.Character) {
requestData = cloneDeep(AICharacterAnalyseRequestData)
2025-08-19 14:33:59 +08:00
} else if (type == PresetCategory.Scene) {
requestData = cloneDeep(AISceneAnalyseRequestData)
2025-09-04 16:58:42 +08:00
} else {
throw new Error(t('未知的分析类型,请检查'))
2025-08-19 14:33:59 +08:00
}
2025-09-04 16:58:42 +08:00
requestData.messages = this.aiReasonCommon.replaceMessageObject(requestData.messages, {
textContent: words
})
2025-08-19 14:33:59 +08:00
await this.aiReasonCommon.GetAISetting()
2025-09-04 16:58:42 +08:00
delete requestData.model
let content = await this.aiReasonCommon.FetchGpt(requestData.messages, requestData)
2025-08-19 14:33:59 +08:00
let autoAnalyzeCharacter = bookTask.autoAnalyzeCharacter ?? '{}'
let autoAnalyzeCharacterData =
ValidateJsonAndParse<BookTask.BookTaskCharacterAndScene>(autoAnalyzeCharacter)
let returnData = content.split('\n').filter((item) => !isEmpty(item))
let newData: BookTask.BookTaskCharacterAndSceneObject[] = []
for (let i = 0; i < returnData.length; i++) {
const element = returnData[i]
let splitData = element.split('')
if (splitData.length < 2) {
2025-08-19 14:33:59 +08:00
continue
}
let tempData = {
no: i + 1,
2025-08-19 14:33:59 +08:00
id: crypto.randomUUID(),
name: splitData[0],
prompt: splitData[1]
2025-08-19 14:33:59 +08:00
} as BookTask.BookTaskCharacterAndSceneObject
newData.push(tempData)
}
if (type == PresetCategory.Character) {
autoAnalyzeCharacterData[PresetCategory.Character] = newData
} else if (type == PresetCategory.Scene) {
// 场景数据
autoAnalyzeCharacterData[PresetCategory.Scene] = newData
}
// 重新写入
await this.bookTaskService.ModifyBookTaskDataById(bookTaskId, {
autoAnalyzeCharacter: JSON.stringify(autoAnalyzeCharacterData)
})
return successMessage(
autoAnalyzeCharacterData,
t('自动分析角色或场景成功'),
2025-08-19 14:33:59 +08:00
'ReverseBook_AutoAnalyzeCharacterOrScene'
)
} catch (error: any) {
return errorMessage(
t("自动分析角色或场景失败,{error}", {
error: error.message
}),
2025-08-19 14:33:59 +08:00
'ReverseBook_AutoAnalyzeCharacterOrScene'
)
}
}
}