import { VideoEmotion, purchase } from "../../schema/schema"; import { Dialog } from "./DialogData"; import { ConfigManager } from "../manager/ConfigManager"; import { DataId, DataManager } from "./DataManager"; import { GirlData } from "./GirlData"; /** * 单个角色的聊天数据结构 */ export interface RoleChatData { categoryId: string; girlId: number; dialogs: Dialog[]; currentEmotion: VideoEmotion; lastActiveTime: Date; } /** * 聊天数据模型类 (多角色管理器) * * 负责管理多个角色的聊天相关数据,包括: * - 多个角色的数据和配置 * - 各角色独立的对话历史记录 * - 各角色独立的情绪状态 * - 视频配置管理 * - 角色切换和数据持久化 * * 这是MVP架构中的Model层,专注于多角色数据管理和业务逻辑 */ export class ChatModel { // 存储所有角色的数据 private rolesData: Map = new Map(); // 当前活跃的角色ID private currentGirlId: number | null = null; // 最大缓存角色数量(内存管理) private maxCachedRoles: number = 10; /** * 初始化或切换到指定角色 * @param girlId 角色ID * @returns 是否成功 */ public initializeRole(categoryId: string, girlId: number): boolean { if (girlId <= 0) { console.error("ChatModel: Invalid roleId provided"); return false; } // 如果角色数据已存在,直接切换 if (this.rolesData.has(girlId)) { this.currentGirlId = girlId; this.updateLastActiveTime(girlId); console.log(`ChatModel: Switched to existing role ${girlId}`); return true; } // 创建新的角色数据 const newRoleData = this.createRoleData(categoryId, girlId); if (newRoleData) { // 内存管理:如果超过最大缓存数量,清理最久未使用的角色 this.manageMemory(); this.rolesData.set(girlId, newRoleData); this.currentGirlId = girlId; console.log(`ChatModel: Initialized new role ${girlId}`); return true; } return false; } /** * 检查是否有指定角色的数据 * @param roleId 角色ID */ public hasRoleData(roleId: number): boolean { return this.rolesData.has(roleId); } /** * 切换到指定角色(不存在则初始化) * @param roleId 角色ID * @returns 是否成功 */ public switchToRole(categoryId: string, roleId: number): boolean { return this.initializeRole(categoryId, roleId); } /** * 创建新的角色数据 * @param girlId 角色ID * @returns 角色数据对象 */ private createRoleData( categoryId: string, girlId: number ): RoleChatData | null { try { // 加载角色基础数据 const girlData = DataManager.I.getDataById(DataId.Girl); const chatTotalCount = girlData.getChatTotalCount(categoryId, girlId); //const roleData = ConfigManager.tables.TbGirls.get(roleId); //const roleDetail = ConfigManager.tables.TbGirlsDetail.get(roleId); if (!girlData) { console.error(`ChatModel: Role data not found for roleId ${girlId}`); return null; } //Todo:未接入后端数据 待补充 // 创建角色数据对象 const newRoleData: RoleChatData = { girlId: girlId, categoryId: categoryId, dialogs: [], currentEmotion: VideoEmotion.calm_down, lastActiveTime: new Date(), }; return newRoleData; } catch (error) { console.error( `ChatModel: Failed to create role data for ${girlId}:`, error ); return null; } } /** * 内存管理:清理最久未使用的角色数据 */ private manageMemory(): void { if (this.rolesData.size < this.maxCachedRoles) { return; } // 找出最久未使用的角色 let oldestRoleId: number | null = null; let oldestTime: Date = new Date(); for (const [roleId, roleData] of this.rolesData.entries()) { if (roleData.lastActiveTime < oldestTime) { oldestTime = roleData.lastActiveTime; oldestRoleId = roleId; } } if (oldestRoleId !== null) { this.rolesData.delete(oldestRoleId); console.log(`ChatModel: Cleaned up unused role ${oldestRoleId} data`); } } /** * 更新角色最后活跃时间 * @param roleId 角色ID */ private updateLastActiveTime(roleId: number): void { const roleData = this.rolesData.get(roleId); if (roleData) { roleData.lastActiveTime = new Date(); } } /** * 获取当前活跃角色ID */ public getCurrentRoleId(): number | null { return this.currentGirlId; } /** * 获取当前角色数据 */ public getCurrentRoleData(): RoleChatData | null { if (!this.currentGirlId) { return null; } return this.rolesData.get(this.currentGirlId) || null; } /** * 获取对话记录 * @param roleId 角色ID,不传则使用当前角色 */ public getDialogs(roleId?: number): Dialog[] { const targetRoleId = roleId || this.currentGirlId; if (!targetRoleId) { return []; } const roleData = this.rolesData.get(targetRoleId); return roleData ? [...roleData.dialogs] : []; // 返回副本避免外部修改 } /** * 添加对话记录 * @param isPlayer 是否为玩家消息 * @param content 消息内容 * @param roleId 角色ID,不传则使用当前角色 */ public addDialog(isPlayer: boolean, content: string, roleId?: number): void { const targetRoleId = roleId || this.currentGirlId; if (!targetRoleId) { console.warn("ChatModel: Cannot add dialog - no active role"); return; } if (!content || content.trim() === "") { console.warn("ChatModel: Cannot add empty dialog content"); return; } const roleData = this.rolesData.get(targetRoleId); if (!roleData) { console.warn(`ChatModel: Role data not found for roleId ${targetRoleId}`); return; } const dialog: Dialog = { isPlayer: isPlayer, content: content.trim(), }; roleData.dialogs.push(dialog); this.updateLastActiveTime(targetRoleId); console.log( `ChatModel: Dialog added for role ${targetRoleId} (isPlayer: ${isPlayer}, content length: ${content.length})` ); } /** * 清空对话记录 * @param roleId 角色ID,不传则使用当前角色 */ public clearDialogs(roleId?: number): void { const targetRoleId = roleId || this.currentGirlId; if (!targetRoleId) { console.warn("ChatModel: Cannot clear dialogs - no active role"); return; } const roleData = this.rolesData.get(targetRoleId); if (roleData) { roleData.dialogs = []; this.updateLastActiveTime(targetRoleId); console.log(`ChatModel: Dialogs cleared for role ${targetRoleId}`); } } /** * 获取当前情绪状态 * @param roleId 角色ID,不传则使用当前角色 */ public getCurrentEmotion(roleId?: number): VideoEmotion { const targetRoleId = roleId || this.currentGirlId; if (!targetRoleId) { return VideoEmotion.calm_down; } const roleData = this.rolesData.get(targetRoleId); return roleData ? roleData.currentEmotion : VideoEmotion.calm_down; } /** * 设置当前情绪状态 * @param emotion 新的情绪状态 * @param roleId 角色ID,不传则使用当前角色 */ public setCurrentEmotion(emotion: VideoEmotion, roleId?: number): void { const targetRoleId = roleId || this.currentGirlId; if (!targetRoleId) { console.warn("ChatModel: Cannot set emotion - no active role"); return; } const roleData = this.rolesData.get(targetRoleId); if (!roleData) { console.warn(`ChatModel: Role data not found for roleId ${targetRoleId}`); return; } if (roleData.currentEmotion !== emotion) { const oldEmotion = roleData.currentEmotion; roleData.currentEmotion = emotion; this.updateLastActiveTime(targetRoleId); console.log( `ChatModel: Role ${targetRoleId} emotion changed from ${VideoEmotion[oldEmotion]} to ${VideoEmotion[emotion]}` ); } } /** * 根据情绪获取对应的视频(如果有多个视频,随机返回一个) * @param emotion 目标情绪 * @param roleId 角色ID,不传则使用当前角色 * @returns 匹配的视频url */ public getVideoByEmotion(emotion: VideoEmotion, roleId?: number): string { const targetRoleId = roleId || this.currentGirlId; if (!targetRoleId) { return null; } const roleData = this.rolesData.get(targetRoleId); if (!roleData) { return null; } const girlData = DataManager.I.getDataById(DataId.Girl); const allvideoIDs = girlData.getAllDisplayGrilVideoId( roleData.categoryId, roleData.girlId ); let targetIds: number[] = []; for (let i = 0; i < allvideoIDs.length; i++) { const element = allvideoIDs[i]; if ( girlData.getGrilVideoEmotion( roleData.categoryId, roleData.girlId, element ) === emotion ) { targetIds.push(allvideoIDs[i]); } } if (targetIds.length === 0) { return null; } // 如果只有一个匹配的视频,直接返回 if (targetIds.length === 1) { return girlData.getGrilVideoPath( roleData.categoryId, roleData.girlId, targetIds[0] ); } // 如果有多个匹配的视频,随机选择一个 const randomIndex = Math.floor(Math.random() * targetIds.length); return girlData.getGrilVideoPath( roleData.categoryId, roleData.girlId, targetIds[randomIndex] ); } /** * 获取默认视频(第一个视频或平静状态视频) * @param girlId 角色ID,不传则使用当前角色 */ public getDefaultVideo(): string { const girlData = DataManager.I.getDataById(DataId.Girl); const roleData = this.rolesData.get(this.currentGirlId); const allvideoIDs = girlData.getAllDisplayGrilVideoId( roleData.categoryId, roleData.girlId ); let targetId: number = -1; for (let i = 0; i < allvideoIDs.length; i++) { const element = allvideoIDs[i]; if ( girlData.getGrilVideoEmotion( roleData.categoryId, roleData.girlId, element ) === VideoEmotion.calm_down ) { targetId = element; } } if (targetId == -1) { return null; } else { return girlData.getGrilVideoPath( roleData.categoryId, roleData.girlId, targetId ); } } /** * 获取对话数量 * @param roleId 角色ID,不传则使用当前角色 */ public getDialogCount(roleId?: number): number { const targetRoleId = roleId || this.currentGirlId; if (!targetRoleId) { return 0; } const roleData = this.rolesData.get(targetRoleId); return roleData ? roleData.dialogs.length : 0; } /** * 获取最后一条对话 * @param roleId 角色ID,不传则使用当前角色 */ public getLastDialog(roleId?: number): Dialog | null { const targetRoleId = roleId || this.currentGirlId; if (!targetRoleId) { return null; } const roleData = this.rolesData.get(targetRoleId); if (!roleData || roleData.dialogs.length === 0) { return null; } return roleData.dialogs[roleData.dialogs.length - 1]; } /** * 重置所有数据到初始状态 */ public reset(): void { this.rolesData.clear(); this.currentGirlId = null; console.log( "ChatModel: All role data cleared and model reset to initial state" ); } /** * 重置指定角色的数据 * @param roleId 角色ID */ public resetRole(roleId: number): void { if (this.rolesData.has(roleId)) { this.rolesData.delete(roleId); // 如果删除的是当前角色,清空当前角色ID if (this.currentGirlId === roleId) { this.currentGirlId = null; } console.log(`ChatModel: Role ${roleId} data cleared`); } } /** * 验证当前模型状态 * @param roleId 角色ID,不传则使用当前角色 */ public validate(roleId?: number): boolean { const targetRoleId = roleId || this.currentGirlId; if (!targetRoleId) { console.error("ChatModel: No active role"); return false; } const roleData = this.rolesData.get(targetRoleId); if (!roleData) { console.error(`ChatModel: Role data not found for ${targetRoleId}`); return false; } return true; } /** * 获取所有角色数据摘要 */ public getAllRolesData(): Map { return new Map(this.rolesData); } /** * 获取缓存的角色数量 */ public getCachedRoleCount(): number { return this.rolesData.size; } /** * 获取所有角色ID列表 */ public getAllRoleIds(): number[] { return Array.from(this.rolesData.keys()); } /** * 清除指定角色的数据(外部调用接口) * @param roleId 角色ID */ public clearRoleData(roleId: number): void { this.resetRole(roleId); } /** * 设置最大缓存角色数量 * @param maxCount 最大缓存数量 */ public setMaxCachedRoles(maxCount: number): void { if (maxCount > 0) { this.maxCachedRoles = maxCount; console.log(`ChatModel: Max cached roles set to ${maxCount}`); // 如果当前缓存超过新限制,清理多余的 this.manageMemory(); } } /** * 获取默认聊天次数限制 * TODO: 以后从后端获取,目前默认返回10 * @returns 默认聊天次数限制 */ private getDefaultChatLimit(): number { return 1; } /** * 检查角色是否还有剩余聊天次数 * @param roleId 角色ID,不传则使用当前角色 * @returns 是否可以继续聊天 */ public canChat(roleId?: number): boolean { const girlData = DataManager.I.getDataById(DataId.Girl); const roleData = this.rolesData.get(this.currentGirlId); return ( girlData.getChatRemainCount(roleData.categoryId, roleData.girlId) > 0 ); } }