import { OperateBookType, PromptMergeType } from '@/define/enum/bookEnum' import { Book } from '@/define/model/book/book' import { errorMessage, SendReturnMessage, successMessage } from '@/public/generalTools' import { isEmpty } from 'lodash' 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 { aiPrompts } from '@/define/data/aiData/aiPrompt' import { ValidateJsonAndParse } from '@/define/Tools/validate' import { BookTask } from '@/define/model/book/bookTask' import { SDServiceHandle } from '../../sd/sdServiceHandle' import { aiHandle } from '../../ai' 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} 操作结果,成功或失败的标准化响应 * * @throws 如果找不到指定分镜数据或操作类型未知,将抛出异常 * * @example * // 为整个小说任务生成提示词,不覆盖已有数据 * const result = await bookPromptHandle.OriginalGetAiPrompt( * "task-123", * OperateBookType.BOOKTASK, * false * ); */ OriginalGetAiPrompt = async ( id: string, operateBookType: OperateBookType, coverData: boolean ): Promise => { 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) 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) if (singleBookTaskDetail == null) { throw new Error('没有找到要推理的分镜数据,请检查ID是否正确') } bookTask = await this.bookTaskService.GetBookTaskDataById( singleBookTaskDetail.bookTaskId as string ) bookTaskDetails = [singleBookTaskDetail] } else if (operateBookType == OperateBookType.UNDERBOOKTASK) { let singleBookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(id) if (singleBookTaskDetail == null) { throw new Error('没有找到要推理的分镜数据,请检查ID是否正确') } bookTask = await this.bookTaskService.GetBookTaskDataById( singleBookTaskDetail.bookTaskId as string ) // 获取全部的分镜数据 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('未知的操作类型') } if (!allBookTaskDetails || allBookTaskDetails.length <= 0) { allBookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ bookTaskId: bookTask.id }) } if (bookTaskDetails.length <= 0) { throw new Error('没有找到要推理的分镜数据') } let generalSettingOption = this.optionRealmService.GetOptionByKey( OptionKeyName.Software.GeneralSetting ) let generalSetting = optionSerialization( generalSettingOption, '‘设置 -> 通用设置’' ) let tasks = [] as Array<() => Promise> // 获取当前task的角色和场景数据 let autoAnalyzeCharacter = bookTask.autoAnalyzeCharacter ?? '{}' let autoAnalyzeCharacterData = ValidateJsonAndParse(autoAnalyzeCharacter) let characterData = autoAnalyzeCharacterData[PresetCategory.Character] ?? [] let sceneData = autoAnalyzeCharacterData[PresetCategory.Scene] ?? [] let characterString = '' let sceneString = '' let characterAndScene = '' if (characterData.length > 0) { characterString = characterData.map((item) => item.name + ':' + item.prompt).join('\n') characterAndScene = '角色设定:' + '\n' + characterString } if (sceneData.length > 0) { sceneString = sceneData.map((item) => item.name + ':' + item.prompt).join('\n') characterAndScene = characterAndScene + '\n' + '场景设定:' + '\n' + sceneString } // 添加异步任务 for (let i = 0; i < bookTaskDetails.length; i++) { const element = bookTaskDetails[i] tasks.push(async () => { let content = await this.aiReasonCommon.OriginalInferencePrompt( element, allBookTaskDetails, 15, // 上下文关联行数 characterAndScene ) // 修改推理出来的数据 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, '推理所有数据完成', 'BookPrompt_OriginalGetPrompt') } catch (error: any) { // 处理错误,返回错误信息 return errorMessage( `获取小说子任务详细数据失败,失败原因如下:${error.message}`, 'BookPromptHandle_OriginalGetAiPrompt' ) } } AIStoryboardMerge = async (bookTaskId: string, type: BookTask.StoryboardMergeType) => { try { let res = await aiHandle.AIStoryboardMerge(bookTaskId, type) return successMessage(res, 'AI分镜头合并成功', 'BookPromptHandle_AIStoryboardMerge') } catch (error: any) { return errorMessage( `AI分镜头合并失败,失败原因如下:${error.message}`, '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} 操作结果,成功或失败的标准化响应 * * @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 => { 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('未知的合并模式,请检查') } } catch (error: any) { return errorMessage( '合并提示词失败,错误信息如下:' + error.message, 'ReverseBook_MergePrompt' ) } } /** * 自动分析书籍任务中的角色或场景 * * 该方法使用AI技术分析书籍任务中的所有分镜文本,自动识别并提取角色或场景信息。 * 处理过程包括合并所有分镜文本、调用AI分析、解析返回结果并格式化为结构化数据。 * 分析结果会更新到书籍任务的autoAnalyzeCharacter字段中,以JSON格式存储。 * * @param {string} bookTaskId - 要分析的书籍任务ID * @param {PresetCategory} type - 分析类型,必须是PresetCategory.Character(角色)或PresetCategory.Scene(场景) * * @returns {Promise} * 成功时返回分析得到的角色或场景数据对象数组,失败时返回错误信息 * * @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) => { try { if (type != PresetCategory.Character && type != PresetCategory.Scene) { throw new Error('分析的类型只能是角色或场景,请检查') } await this.InitBookBasicHandle() let bookTask = await this.bookTaskService.GetBookTaskDataById(bookTaskId) let bookTaskDetails = await this.bookTaskDetailService.GetBookTaskDetailDataByCondition({ bookTaskId: bookTaskId }) if (bookTaskDetails.length <= 0) { throw new Error('没有找到要分析的分镜数据,请先导入文案或者时srt!') } let words = bookTaskDetails .map((item) => { return item.afterGpt }) .join('\r\n') let systemContent = '' let userContent = '' if (type == PresetCategory.Character) { systemContent = aiPrompts.NanFengCharacterSystemContent userContent = aiPrompts.NanFengCharacterUserContent } else if (type == PresetCategory.Scene) { systemContent = aiPrompts.NanFengSceneSystemContent userContent = aiPrompts.NanFengSceneUserContent } let message = [ { role: 'system', content: this.aiReasonCommon.replaceObject(systemContent, { textContent: words }) }, { role: 'user', content: this.aiReasonCommon.replaceObject(userContent, { textContent: words }) } ] await this.aiReasonCommon.GetAISetting() let content = await this.aiReasonCommon.FetchGpt(message) let autoAnalyzeCharacter = bookTask.autoAnalyzeCharacter ?? '{}' let autoAnalyzeCharacterData = ValidateJsonAndParse(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 < 3) { continue } let tempData = { no: Number(splitData[0]), id: crypto.randomUUID(), name: splitData[1], prompt: splitData[2] } 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, '自动分析角色或场景成功', 'ReverseBook_AutoAnalyzeCharacterOrScene' ) } catch (error: any) { return errorMessage( '自动分析角色或场景失败,错误信息如下:' + error.message, 'ReverseBook_AutoAnalyzeCharacterOrScene' ) } } }