import { ChatAIService } from "./ChatAIService"; import { EmotionAIService } from "./EmotionAIService"; import { DialogManager } from "../manager/DialogManager"; import { VideoEmotion } from "../../schema/schema"; import { ErrorHandler, ErrorType } from "../utils/ErrorHandler"; import { ChatModel } from "../data/ChatModel"; import { ChatHistoryManager } from "../manager/ChatHistoryManager"; import Utils from "../../Main/Common/Utils"; import { InnerMsgCode } from "../../Main/Config/InnerMsgCode"; import { TipsPanel } from "../ui/panels/TipsPanel"; import LanguageUtils from "../../Main/Common/LanguageUtils"; import { logger } from "db://assets/Scripts/Main/Common/Logger"; import { GirlService } from "../network/services/GirlService"; import { DataManager, DataId } from "../data/DataManager"; import { GirlData } from "../data/GirlData"; import proto from "db://assets/Scripts/proto/proto.pb.js"; /** * 聊天控制器接口 - 定义Panel和Controller之间的通信协议 */ export interface IChatPanelCallback { /** * 消息发送开始回调 * @param message 用户发送的消息 */ onMessageSent(message: string): void; /** * 接收到AI回复回调 * @param response AI的回复内容 */ onMessageReceived(response: string): void; /** * 情绪状态更新回调 * @param emotion 更新后的情绪状态 */ onEmotionUpdated(emotion: VideoEmotion): void; /** * 对话更新回调 */ onDialogUpdated(isPlayer: boolean): void; /** * 聊天次数用尽回调 */ onChatLimitReached(): void; /** * 错误处理回调 * @param error 错误信息 */ onError(error: Error): void; } /** * 聊天控制器类 (MVP中的Presenter) - 单例模式 * * 负责处理聊天相关的业务逻辑协调,包括: * - 协调Model和View之间的交互 * - 处理用户交互和业务逻辑 * - 管理AI服务调用 * - 处理情绪状态更新 * - 错误处理和状态管理 * * @example * ```typescript * const controller = ChatController.Instance; * controller.bindView(panelCallback); * controller.initialize(10001); * const response = await controller.sendMessage("Hello"); * ``` */ export class ChatController { private static _instance: ChatController; private chatModel: ChatModel = new ChatModel(); private callback: IChatPanelCallback | null = null; private dialogManager: DialogManager | null = null; /** * 私有构造函数,防止外部直接实例化 */ private constructor() { // 注册情绪更新事件监听器 Utils.addInnerEL( InnerMsgCode.Chat_EmotionUpdated, this, this.onEmotionUpdated ); } /** * 获取单例实例 * @returns ChatController单例实例 */ public static get Instance(): ChatController { if (!this._instance) { this._instance = new ChatController(); } return this._instance; } /** * 绑定View到Controller * @param callback View的回调接口实现 */ public bindView(callback: IChatPanelCallback): void { this.callback = callback; logger.log("ChatController: View bound successfully"); } /** * 解绑View */ public unbindView(): void { this.callback = null; logger.log("ChatController: View unbound"); } /** * 初始化聊天控制器 * @param roleId 角色ID */ public initialize(categoryId: string, roleId: number): void { this.dialogManager = DialogManager.getInstance(); // 初始化或切换到指定角色 if (!this.chatModel.initializeRole(categoryId, roleId)) { const error = new Error( `Failed to initialize ChatModel with roleId: ${roleId}` ); this.handleError(error); return; } // 设置当前聊天的角色ID到AI服务 if (roleId && roleId > 0) { ChatAIService.Instance.setCurrentRole(roleId); // 从ChatHistoryManager加载对话记录到ChatModel中 this.loadDialogsFromHistory(roleId); // 同步到DialogManager //this.syncDialogData(); logger.log(`ChatController initialized with role ${roleId}`); } else { const error = new Error(`Invalid roleId: ${roleId}`); this.handleError(error); } } /** * 切换到指定角色 * @param roleId 角色ID * @returns 是否切换成功 */ public async switchRole( categoryId: string, roleId: number ): Promise { if (!this.chatModel.switchToRole(categoryId, roleId)) { logger.error(`Failed to switch to role ${roleId}`); return false; } // 更新AI服务的当前角色 ChatAIService.Instance.setCurrentRole(roleId); // 从ChatHistoryManager加载对话记录到ChatModel中 this.loadDialogsFromHistory(roleId); // 同步对话数据到DialogManager //this.syncDialogData(); logger.log(`ChatController switched to role ${roleId}`); return true; } /** * 发送消息给AI并处理回复 * @param message 用户消息内容 * @returns Promise AI的回复,失败时返回null */ public async sendMessage(message: string): Promise { if (!this.validateSendMessage(message)) { return false; } // 检查聊天次数限制 if (!this.canSendMessage()) { logger.warn("ChatController: Cannot send message - chat limit reached"); this.callback?.onChatLimitReached(); return false; } try { const roleId = this.chatModel.getCurrentRoleId(); if (!roleId) { logger.error("Role ID is not available in ChatModel"); TipsPanel.show(LanguageUtils.getText("chat_error_code_1004")); } // 通知界面消息发送开始 this.callback?.onMessageSent(message); // 显示加载中的对话 //this.dialogManager?.addLoadingDialog(); logger.log(`Sending message to role ${roleId}: ${message}`); // 发送消息给AI服务 const response = await ChatAIService.Instance.sendMessage( roleId, message ); // 检查View是否仍然绑定,如果已解绑则不处理响应(用户可能已退出Panel) if (!this.callback) { logger.log("ChatController: View已解绑,忽略AI响应"); return false; } if (response) { // 添加用户消息到模型 this.chatModel.addDialog(true, message); // 更新对话显示 - 用户消息 this.dialogManager?.updateDialog(true, message, true); this.callback?.onDialogUpdated(true); // 添加AI回复到模型 (保持完整消息) this.chatModel.addDialog(false, response); // 更新对话显示 - AI回复 (使用分段显示) this.dialogManager?.updateDialogWithSegments(false, response); this.callback?.onDialogUpdated(false); // 通知界面收到回复 this.callback?.onMessageReceived(response); // 注意:情绪状态将通过异步事件更新,不在这里同步获取 logger.log(`Response received from role ${roleId}: ${response}`); return true; } else { return false; } } catch (error) { ErrorHandler.Instance.handleApiError( error, "ChatController.sendMessage", { roleId: this.chatModel.getCurrentRoleId(), message: message.substring(0, 100) + "...", } ); // 只有当View仍然绑定时才调用错误处理回调 if (this.callback) { this.handleError(error as Error); } else { logger.log("ChatController: View已解绑,不显示错误提示"); } return false; } } /** * 获取当前角色的情绪状态 * @returns VideoEmotion 当前情绪状态 */ public getCurrentEmotion(): VideoEmotion { return this.chatModel.getCurrentEmotion(); } /** * 清除当前角色的聊天历史 */ public clearChatHistory(): void { const roleId = this.chatModel.getCurrentRoleId(); if (!roleId) { logger.warn("Cannot clear history: roleId is null"); return; } try { // 清除AI服务中的历史记录 ChatAIService.Instance.clearChatHistory(roleId); // 清除模型中的对话记录 this.chatModel.clearDialogs(); logger.log(`Chat history cleared for role ${roleId}`); } catch (error) { logger.error("Failed to clear chat history:", error); this.handleError(error as Error); } } /** * 清除指定角色的聊天历史 * @param roleId 角色ID */ public clearRoleChatHistory(roleId: number): void { try { // 清除AI服务中的历史记录 ChatAIService.Instance.clearChatHistory(roleId); // 清除模型中的对话记录 this.chatModel.clearDialogs(roleId); logger.log(`Chat history cleared for role ${roleId}`); } catch (error) { logger.error(`Failed to clear chat history for role ${roleId}:`, error); this.handleError(error as Error); } } /** * 获取当前角色ID * @returns 当前角色ID */ public getCurrentRoleId(): number | null { return this.chatModel.getCurrentRoleId(); } /** * 销毁控制器,清理资源 */ public destroy(): void { this.chatModel.reset(); this.callback = null; this.dialogManager = null; logger.log("ChatController destroyed"); } /** * 验证发送消息的参数 * @param message 消息内容 * @returns 验证是否通过 */ private validateSendMessage(message: string): boolean { if (!this.chatModel.validate()) { const error = new Error("ChatModel未正确初始化"); this.handleError(error); return false; } if (!message || message.trim() === "") { const error = new Error("消息内容不能为空"); this.handleError(error); return false; } if (!this.callback) { const error = new Error("回调接口未设置"); this.handleError(error); return false; } return true; } /** * 获取ChatModel实例(供其他组件访问,谨慎使用) * @returns ChatModel实例 */ public getChatModel(): ChatModel { return this.chatModel; } /** * 从ChatHistoryManager加载对话记录到ChatModel中 * @param roleId 角色ID,不传则使用当前角色 */ public loadDialogsFromHistory(roleId?: number): void { const targetRoleId = roleId || this.chatModel.getCurrentRoleId(); if (!targetRoleId) { logger.warn("Cannot load dialogs: no active role"); return; } // 从ChatHistoryManager加载聊天记录 const chatHistory = ChatHistoryManager.Instance.loadHistory(targetRoleId); // 清空ChatModel中的对话记录 this.chatModel.clearDialogs(targetRoleId); // 将ChatHistoryManager的记录转换为Dialog格式并添加到ChatModel chatHistory.forEach((message) => { const isPlayer = message.role === "user"; const content = message.parts.map((part) => part.text).join(""); this.chatModel.addDialog(isPlayer, content, targetRoleId); }); logger.log( `Loaded ${chatHistory.length} messages from ChatHistoryManager for role ${targetRoleId}` ); } /** * 同步ChatModel的对话数据到DialogManager * 用于确保DialogManager和ChatModel的数据一致性 */ public syncDialogData(): void { if (!this.dialogManager || !this.chatModel.validate()) { logger.warn( "Cannot sync dialog data: missing DialogManager or invalid ChatModel" ); return; } const dialogs = this.chatModel.getDialogs(); this.dialogManager.syncFromChatModel(dialogs); logger.log( `Synced ${dialogs.length} dialogs from ChatModel to DialogManager` ); } /** * 获取对话统计信息 * @param roleId 角色ID,不传则使用当前角色 */ public getDialogStats(roleId?: number): any { const targetRoleId = roleId || this.chatModel.getCurrentRoleId(); if (!targetRoleId || !this.chatModel.validate(targetRoleId)) { return null; } return { roleId: targetRoleId, dialogCount: this.chatModel.getDialogCount(targetRoleId), lastDialog: this.chatModel.getLastDialog(targetRoleId), currentEmotion: this.chatModel.getCurrentEmotion(targetRoleId), }; } /** * 获取所有缓存角色的统计信息 */ public getAllRolesStats(): any { const allRoleIds = this.chatModel.getAllRoleIds(); const stats = { totalCachedRoles: this.chatModel.getCachedRoleCount(), currentRoleId: this.chatModel.getCurrentRoleId(), roles: {} as any, }; for (const roleId of allRoleIds) { stats.roles[roleId] = this.getDialogStats(roleId); } return stats; } /** * 清除指定角色的所有数据 * @param roleId 角色ID */ public clearRoleData(roleId: number): void { try { // 清除AI服务中的历史记录 ChatAIService.Instance.clearChatHistory(roleId); // 清除模型中的角色数据 this.chatModel.clearRoleData(roleId); logger.log(`All data cleared for role ${roleId}`); } catch (error) { logger.error(`Failed to clear all data for role ${roleId}:`, error); this.handleError(error as Error); } } /** * 检查是否有指定角色的数据 * @param roleId 角色ID */ public hasRoleData(categoryId: string, roleId: number): boolean { return this.chatModel.hasRoleData(roleId); } /** * 设置最大缓存角色数量 * @param maxCount 最大缓存数量 */ public setMaxCachedRoles(maxCount: number): void { this.chatModel.setMaxCachedRoles(maxCount); } /** * 检查是否可以发送消息(基于聊天次数限制) * @returns 是否可以发送消息 */ public canSendMessage(): boolean { return this.chatModel.canChat(); } /** * 处理情绪更新事件 * @param data 情绪更新数据 { roleId: number, emotion: VideoEmotion } */ private onEmotionUpdated(data: any): void { if (!data || !data.roleId || data.emotion === undefined) { logger.warn("Invalid emotion update data:", data); return; } const { roleId, emotion } = data; // 只处理当前角色的情绪更新 if (roleId === this.chatModel.getCurrentRoleId()) { logger.log( `ChatController: Emotion updated for role ${roleId}: ${VideoEmotion[emotion]}` ); // 更新模型中的情绪状态 this.chatModel.setCurrentEmotion(emotion); // 通知界面情绪更新 this.callback?.onEmotionUpdated(emotion); } } /** * 统一错误处理 * @param error 错误对象 */ private handleError(error: Error): void { logger.error("ChatController error:", error); this.callback?.onError(error); } }