- MVP架构重构聊天逻辑

- 优化聊天气泡体验
- 修复prompt bug
- 实现聊天次数限制功能(未接服务端)
- 实现情绪切视频功能
This commit is contained in:
2025-08-27 16:12:19 +08:00
parent 9004f77e8d
commit e57818d498
27 changed files with 1713 additions and 687 deletions
+377 -56
View File
@@ -2,8 +2,11 @@ import { ChatAIService } from "./ChatAIService";
import { EmotionAIService } from "./EmotionAIService";
import { DialogManager } from "../manager/DialogManager";
import { VideoEmotion } from "../../schema/schema";
import ConfigManager from "../manager/ConfigManager";
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";
/**
* 聊天控制器接口 - 定义Panel和Controller之间的通信协议
@@ -32,6 +35,11 @@ export interface IChatPanelCallback {
*/
onDialogUpdated(): void;
/**
* 聊天次数用尽回调
*/
onChatLimitReached(): void;
/**
* 错误处理回调
* @param error 错误信息
@@ -40,40 +48,93 @@ export interface IChatPanelCallback {
}
/**
* 聊天控制器类
* 聊天控制器类 (MVP中的Presenter) - 单例模式
*
* 负责处理聊天相关的所有业务逻辑,包括:
* - 角色数据管理
* - 消息发送和接收
* - 情绪状态管理
* - 与AI服务的交互
* - 对话历史管理
* 负责处理聊天相关的业务逻辑协调,包括:
* - 协调Model和View之间的交互
* - 处理用户交互和业务逻辑
* - 管理AI服务调用
* - 处理情绪状态更新
* - 错误处理和状态管理
*
* @example
* ```typescript
* const controller = new ChatController();
* controller.initialize(10001, panelCallback);
* const controller = ChatController.Instance;
* controller.bindView(panelCallback);
* controller.initialize(10001);
* const response = await controller.sendMessage("Hello");
* ```
*/
export class ChatController {
private roleId: number | null = null;
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
* @param callback 回调接口实现
*/
public initialize(roleId: number, callback: IChatPanelCallback): void {
this.roleId = roleId;
this.callback = callback;
public initialize(roleId: number): void {
this.dialogManager = DialogManager.getInstance();
// 设置当前聊天的角色ID
// 初始化或切换到指定角色
if (!this.chatModel.initializeRole(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}`);
@@ -81,63 +142,110 @@ export class ChatController {
}
}
/**
* 切换到指定角色
* @param roleId 角色ID
* @returns 是否切换成功
*/
public switchRole(roleId: number): boolean {
if (!this.chatModel.switchToRole(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<string | null> {
public async sendMessage(message: string): Promise<boolean> {
if (!this.validateSendMessage(message)) {
return null;
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) {
throw new Error("Role ID is not available in ChatModel");
}
// 通知界面消息发送开始
this.callback?.onMessageSent(message);
// 添加用户消息到模型
this.chatModel.addDialog(true, message);
// 更新对话显示 - 用户消息
this.dialogManager?.updateDialog(true, message, true);
this.callback?.onDialogUpdated();
console.log(`Sending message to role ${this.roleId}: ${message}`);
// 显示加载中的对话
this.dialogManager?.addLoadingDialog();
console.log(`Sending message to role ${roleId}: ${message}`);
// 发送消息给AI服务
const response = await ChatAIService.Instance.sendMessage(this.roleId!, message);
const response = await ChatAIService.Instance.sendMessage(roleId, message);
if (response) {
// 更新对话显示 - AI回复
this.dialogManager?.updateDialog(false, response);
// 移除加载中的对话
this.dialogManager?.removeLoadingDialog();
// 添加AI回复到模型 (保持完整消息)
this.chatModel.addDialog(false, response);
// 更新对话显示 - AI回复 (使用分段显示)
this.dialogManager?.updateDialogWithSegments(false, response);
this.callback?.onDialogUpdated();
// 通知界面收到回复
this.callback?.onMessageReceived(response);
// 获取更新后的情绪状态
try {
const currentEmotion = EmotionAIService.Instance.getCurrentEmotion(this.roleId!);
console.log(`Current emotion for role ${this.roleId}: ${VideoEmotion[currentEmotion]}`);
// 通知界面情绪更新
this.callback?.onEmotionUpdated(currentEmotion);
} catch (emotionError) {
console.warn("Failed to get current emotion:", emotionError);
// 情绪获取失败不影响聊天功能
}
// 增加聊天次数计数
this.chatModel.incrementChatCount();
console.log(`Response received from role ${this.roleId}: ${response}`);
return response;
// 注意:情绪状态将通过异步事件更新,不在这里同步获取
console.log(`Response received from role ${roleId}: ${response}`);
return true;
} else {
// 移除加载中的对话
this.dialogManager?.removeLoadingDialog();
const error = new Error("AI返回了空响应");
this.handleError(error);
return null;
return false;
}
} catch (error) {
// 移除加载中的对话
this.dialogManager?.removeLoadingDialog();
ErrorHandler.Instance.handleApiError(error, "ChatController.sendMessage", {
roleId: this.roleId,
roleId: this.chatModel.getCurrentRoleId(),
message: message.substring(0, 100) + "..."
});
this.handleError(error as Error);
return null;
return false;
}
}
@@ -146,10 +254,7 @@ export class ChatController {
* @returns VideoEmotion 当前情绪状态
*/
public getCurrentEmotion(): VideoEmotion {
if (!this.roleId) {
return VideoEmotion.calm_down;
}
return EmotionAIService.Instance.getCurrentEmotion(this.roleId);
return this.chatModel.getCurrentEmotion();
}
/**
@@ -157,20 +262,17 @@ export class ChatController {
* @returns 角色数据对象,失败时返回null
*/
public getRoleData(): any {
if (!this.roleId) {
if (!this.chatModel.validate()) {
return null;
}
try {
const roleData = ConfigManager.tables.TbGirls.get(this.roleId);
const roleDetail = ConfigManager.tables.TbGirlsDetail.get(this.roleId);
return {
basic: roleData,
detail: roleDetail
basic: this.chatModel.getRoleData(),
detail: this.chatModel.getRoleDetail()
};
} catch (error) {
console.error(`Failed to get role data for ${this.roleId}:`, error);
console.error(`Failed to get role data:`, error);
this.handleError(error as Error);
return null;
}
@@ -180,33 +282,58 @@ export class ChatController {
* 清除当前角色的聊天历史
*/
public clearChatHistory(): void {
if (!this.roleId) {
const roleId = this.chatModel.getCurrentRoleId();
if (!roleId) {
console.warn("Cannot clear history: roleId is null");
return;
}
try {
ChatAIService.Instance.clearChatHistory(this.roleId);
console.log(`Chat history cleared for role ${this.roleId}`);
// 清除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.roleId;
return this.chatModel.getCurrentRoleId();
}
/**
* 销毁控制器,清理资源
*/
public destroy(): void {
this.roleId = null;
this.chatModel.reset();
this.callback = null;
this.dialogManager = null;
console.log("ChatController destroyed");
@@ -218,8 +345,8 @@ export class ChatController {
* @returns 验证是否通过
*/
private validateSendMessage(message: string): boolean {
if (!this.roleId || this.roleId <= 0) {
const error = new Error("角色ID无效");
if (!this.chatModel.validate()) {
const error = new Error("ChatModel未正确初始化");
this.handleError(error);
return false;
}
@@ -239,6 +366,200 @@ export class ChatController {
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 hasRoleData(roleId: number): boolean {
return this.chatModel.hasRoleData(roleId);
}
/**
* 清除指定角色的所有数据
* @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 maxCount 最大缓存数量
*/
public setMaxCachedRoles(maxCount: number): void {
this.chatModel.setMaxCachedRoles(maxCount);
}
/**
* 获取模型状态摘要
* @param roleId 角色ID,不传则获取全局摘要
*/
public getModelSummary(roleId?: number): any {
return this.chatModel.getStateSummary(roleId);
}
/**
* 检查是否可以发送消息(基于聊天次数限制)
* @returns 是否可以发送消息
*/
public canSendMessage(): boolean {
return this.chatModel.canChat();
}
/**
* 获取当前角色剩余聊天次数
* @returns 剩余聊天次数
*/
public getRemainingChats(): number {
return this.chatModel.getRemainingChatCount();
}
/**
* 获取当前角色已使用的聊天次数
* @returns 已使用的聊天次数
*/
public getUsedChats(): number {
return this.chatModel.getRoleChatCount();
}
/**
* 获取当前角色的聊天次数限制
* @returns 聊天次数限制
*/
public getChatLimit(): number {
return this.chatModel.getRoleChatLimit();
}
/**
* 重置当前角色的聊天次数
*/
public resetChatCount(): void {
this.chatModel.resetChatCount();
console.log("ChatController: Chat count reset for current role");
}
/**
* 处理情绪更新事件
* @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 错误对象