524 lines
14 KiB
TypeScript
524 lines
14 KiB
TypeScript
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";
|
|
|
|
/**
|
|
* 聊天控制器接口 - 定义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;
|
|
console.log("ChatController: View bound successfully");
|
|
}
|
|
|
|
/**
|
|
* 解绑View
|
|
*/
|
|
public unbindView(): void {
|
|
this.callback = null;
|
|
console.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();
|
|
|
|
console.log(`ChatController initialized with role ${roleId}`);
|
|
} else {
|
|
const error = new Error(`Invalid roleId: ${roleId}`);
|
|
this.handleError(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 切换到指定角色
|
|
* @param roleId 角色ID
|
|
* @returns 是否切换成功
|
|
*/
|
|
public switchRole(categoryId: string, roleId: number): boolean {
|
|
if (!this.chatModel.switchToRole(categoryId, roleId)) {
|
|
console.error(`Failed to switch to role ${roleId}`);
|
|
return false;
|
|
}
|
|
|
|
// 更新AI服务的当前角色
|
|
ChatAIService.Instance.setCurrentRole(roleId);
|
|
|
|
// 从ChatHistoryManager加载对话记录到ChatModel中
|
|
this.loadDialogsFromHistory(roleId);
|
|
|
|
// 同步对话数据到DialogManager
|
|
//this.syncDialogData();
|
|
|
|
console.log(`ChatController switched to role ${roleId}`);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 发送消息给AI并处理回复
|
|
* @param message 用户消息内容
|
|
* @returns Promise<string | null> AI的回复,失败时返回null
|
|
*/
|
|
public async sendMessage(message: string): Promise<boolean> {
|
|
if (!this.validateSendMessage(message)) {
|
|
return false;
|
|
}
|
|
|
|
// 检查聊天次数限制
|
|
if (!this.canSendMessage()) {
|
|
console.warn("ChatController: Cannot send message - chat limit reached");
|
|
this.callback?.onChatLimitReached();
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const roleId = this.chatModel.getCurrentRoleId();
|
|
if (!roleId) {
|
|
console.error("Role ID is not available in ChatModel");
|
|
TipsPanel.show(LanguageUtils.getText("chat_error_code_1004"));
|
|
}
|
|
|
|
// 通知界面消息发送开始
|
|
this.callback?.onMessageSent(message);
|
|
|
|
// 显示加载中的对话
|
|
//this.dialogManager?.addLoadingDialog();
|
|
|
|
console.log(`Sending message to role ${roleId}: ${message}`);
|
|
// 发送消息给AI服务
|
|
const response = await ChatAIService.Instance.sendMessage(
|
|
roleId,
|
|
message
|
|
);
|
|
|
|
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);
|
|
|
|
// 增加聊天次数计数
|
|
//this.chatModel.incrementChatCount();
|
|
|
|
// 注意:情绪状态将通过异步事件更新,不在这里同步获取
|
|
|
|
console.log(`Response received from role ${roleId}: ${response}`);
|
|
return true;
|
|
} else {
|
|
// 移除加载中的对话
|
|
//this.dialogManager?.removeLoadingDialog();
|
|
|
|
const error = new Error("AI返回了空响应");
|
|
this.handleError(error);
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
// 移除加载中的对话
|
|
//this.dialogManager?.removeLoadingDialog();
|
|
|
|
ErrorHandler.Instance.handleApiError(
|
|
error,
|
|
"ChatController.sendMessage",
|
|
{
|
|
roleId: this.chatModel.getCurrentRoleId(),
|
|
message: message.substring(0, 100) + "...",
|
|
}
|
|
);
|
|
this.handleError(error as Error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取当前角色的情绪状态
|
|
* @returns VideoEmotion 当前情绪状态
|
|
*/
|
|
public getCurrentEmotion(): VideoEmotion {
|
|
return this.chatModel.getCurrentEmotion();
|
|
}
|
|
|
|
/**
|
|
* 清除当前角色的聊天历史
|
|
*/
|
|
public clearChatHistory(): void {
|
|
const roleId = this.chatModel.getCurrentRoleId();
|
|
if (!roleId) {
|
|
console.warn("Cannot clear history: roleId is null");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 清除AI服务中的历史记录
|
|
ChatAIService.Instance.clearChatHistory(roleId);
|
|
|
|
// 清除模型中的对话记录
|
|
this.chatModel.clearDialogs();
|
|
|
|
console.log(`Chat history cleared for role ${roleId}`);
|
|
} catch (error) {
|
|
console.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);
|
|
|
|
console.log(`Chat history cleared for role ${roleId}`);
|
|
} catch (error) {
|
|
console.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;
|
|
console.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) {
|
|
console.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);
|
|
});
|
|
|
|
console.log(
|
|
`Loaded ${chatHistory.length} messages from ChatHistoryManager for role ${targetRoleId}`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 同步ChatModel的对话数据到DialogManager
|
|
* 用于确保DialogManager和ChatModel的数据一致性
|
|
*/
|
|
public syncDialogData(): void {
|
|
if (!this.dialogManager || !this.chatModel.validate()) {
|
|
console.warn(
|
|
"Cannot sync dialog data: missing DialogManager or invalid ChatModel"
|
|
);
|
|
return;
|
|
}
|
|
|
|
const dialogs = this.chatModel.getDialogs();
|
|
this.dialogManager.syncFromChatModel(dialogs);
|
|
console.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);
|
|
|
|
console.log(`All data cleared for role ${roleId}`);
|
|
} catch (error) {
|
|
console.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) {
|
|
console.warn("Invalid emotion update data:", data);
|
|
return;
|
|
}
|
|
|
|
const { roleId, emotion } = data;
|
|
|
|
// 只处理当前角色的情绪更新
|
|
if (roleId === this.chatModel.getCurrentRoleId()) {
|
|
console.log(
|
|
`ChatController: Emotion updated for role ${roleId}: ${VideoEmotion[emotion]}`
|
|
);
|
|
|
|
// 更新模型中的情绪状态
|
|
this.chatModel.setCurrentEmotion(emotion);
|
|
|
|
// 通知界面情绪更新
|
|
this.callback?.onEmotionUpdated(emotion);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 统一错误处理
|
|
* @param error 错误对象
|
|
*/
|
|
private handleError(error: Error): void {
|
|
console.error("ChatController error:", error);
|
|
this.callback?.onError(error);
|
|
}
|
|
}
|