380 lines
15 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 { 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<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)
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<SettingModal.GeneralSettings>(
generalSettingOption,
'‘设置 -> 通用设置’'
)
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 = ''
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<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('未知的合并模式,请检查')
}
} 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<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) => {
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<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 < 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'
)
}
}
}