2025-08-11 16:08:59 +08:00
|
|
|
import { ChatContentsLayout } from "../ui/components/ChatContentsLayout";
|
2025-09-16 16:55:39 +08:00
|
|
|
import { DiaLogData, Dialog } from "../data/DialogData";
|
2025-08-11 16:08:59 +08:00
|
|
|
import Utils from "db://assets/Scripts/Main/Common/Utils";
|
2025-08-13 11:31:21 +08:00
|
|
|
import { InnerMsgCode } from "db://assets/Scripts/Main/Config/InnerMsgCode";
|
2025-08-11 16:08:59 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 对话管理器
|
2025-08-13 11:31:21 +08:00
|
|
|
*
|
2025-08-11 16:08:59 +08:00
|
|
|
* 专注于管理对话数据和对话流程,导航功能已迁移至NavigationManager
|
2025-08-27 16:12:19 +08:00
|
|
|
* 在MVP架构中,主要负责Dialog数据的统一管理和UI更新通知
|
2025-08-13 11:31:21 +08:00
|
|
|
*
|
2025-08-11 16:08:59 +08:00
|
|
|
* @author AI Chat System
|
2025-08-27 16:12:19 +08:00
|
|
|
* @version 2.1.0 (MVP架构优化)
|
2025-08-11 16:08:59 +08:00
|
|
|
*/
|
|
|
|
|
export class DialogManager {
|
|
|
|
|
private static _instance: DialogManager;
|
2025-08-13 11:31:21 +08:00
|
|
|
|
2025-08-11 16:08:59 +08:00
|
|
|
/**
|
|
|
|
|
* 获取DialogManager的单例实例
|
2025-08-13 11:31:21 +08:00
|
|
|
*
|
2025-08-11 16:08:59 +08:00
|
|
|
* @returns {DialogManager} 对话管理器实例
|
|
|
|
|
* @static
|
|
|
|
|
*/
|
|
|
|
|
public static getInstance(): DialogManager {
|
|
|
|
|
if (!this._instance) {
|
|
|
|
|
this._instance = new DialogManager();
|
|
|
|
|
}
|
|
|
|
|
return this._instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private constructor() {
|
2025-09-16 16:55:39 +08:00
|
|
|
this.dialogData = new DiaLogData();
|
2025-08-11 16:08:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 对话数据实例 */
|
2025-09-16 16:55:39 +08:00
|
|
|
private dialogData: DiaLogData;
|
2025-08-13 11:31:21 +08:00
|
|
|
|
2025-08-11 16:08:59 +08:00
|
|
|
/** 聊天内容布局组件引用 */
|
|
|
|
|
public layoutout: ChatContentsLayout;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 更新对话内容
|
2025-08-13 11:31:21 +08:00
|
|
|
*
|
2025-08-11 16:08:59 +08:00
|
|
|
* @param {boolean} isPlayer - 是否为玩家消息
|
|
|
|
|
* @param {string} str - 消息内容
|
|
|
|
|
* @param {boolean} fromPlayer - 是否来自玩家输入(用于清理对话)
|
|
|
|
|
*/
|
2025-08-13 11:31:21 +08:00
|
|
|
public updateDialog(
|
|
|
|
|
isPlayer: boolean,
|
|
|
|
|
str: string,
|
|
|
|
|
fromPlayer: boolean = false
|
|
|
|
|
): void {
|
|
|
|
|
if (fromPlayer) {
|
2025-09-16 16:55:39 +08:00
|
|
|
this.dialogData.cleanDialog();
|
2025-08-11 16:08:59 +08:00
|
|
|
}
|
2025-09-16 16:55:39 +08:00
|
|
|
this.dialogData.pushDialog(isPlayer, str);
|
2025-08-11 16:08:59 +08:00
|
|
|
}
|
|
|
|
|
|
2025-08-27 16:12:19 +08:00
|
|
|
/**
|
|
|
|
|
* 更新分段对话内容(支持多个气泡显示和延迟效果)
|
|
|
|
|
*
|
|
|
|
|
* @param {boolean} isPlayer - 是否为玩家消息
|
|
|
|
|
* @param {string} str - 消息内容
|
|
|
|
|
* @param {boolean} fromPlayer - 是否来自玩家输入(用于清理对话)
|
|
|
|
|
*/
|
|
|
|
|
public updateDialogWithSegments(
|
|
|
|
|
isPlayer: boolean,
|
|
|
|
|
str: string,
|
|
|
|
|
fromPlayer: boolean = false
|
|
|
|
|
): void {
|
|
|
|
|
if (fromPlayer) {
|
2025-09-16 16:55:39 +08:00
|
|
|
this.dialogData.cleanDialog();
|
2025-08-27 16:12:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果是玩家消息,直接添加单个气泡
|
|
|
|
|
if (isPlayer) {
|
2025-09-16 16:55:39 +08:00
|
|
|
this.dialogData.pushDialog(isPlayer, str);
|
2025-08-27 16:12:19 +08:00
|
|
|
console.log("Dialog updated:", {
|
|
|
|
|
isPlayer,
|
|
|
|
|
content: str.substring(0, 50) + "...",
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果是AI消息,进行分段处理
|
|
|
|
|
const segments = this.splitMessageIntoSegments(str);
|
2025-09-16 16:55:39 +08:00
|
|
|
|
2025-08-27 16:12:19 +08:00
|
|
|
if (segments.length <= 1) {
|
|
|
|
|
// 如果只有一段,直接显示单个气泡
|
2025-09-16 16:55:39 +08:00
|
|
|
this.dialogData.pushDialog(isPlayer, str);
|
2025-08-27 16:12:19 +08:00
|
|
|
} else {
|
|
|
|
|
// 多段内容,依次显示多个气泡
|
|
|
|
|
this.displaySegmentsWithDelay(segments, isPlayer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将消息分割成多个段落
|
|
|
|
|
* @param message 完整消息内容
|
|
|
|
|
* @returns 段落数组
|
|
|
|
|
*/
|
|
|
|
|
private splitMessageIntoSegments(message: string): string[] {
|
|
|
|
|
// 首先按双换行符分割
|
|
|
|
|
let segments = message.split(/\n\n+/);
|
2025-09-16 16:55:39 +08:00
|
|
|
|
2025-08-27 16:12:19 +08:00
|
|
|
// 如果只有一段,则按单换行符分割
|
|
|
|
|
if (segments.length === 1) {
|
|
|
|
|
segments = message.split(/\n+/);
|
|
|
|
|
}
|
2025-09-16 16:55:39 +08:00
|
|
|
|
2025-08-27 16:12:19 +08:00
|
|
|
// 过滤空段落并去除首尾空白
|
|
|
|
|
return segments
|
2025-09-16 16:55:39 +08:00
|
|
|
.map((segment) => segment.trim())
|
|
|
|
|
.filter((segment) => segment.length > 0);
|
2025-08-27 16:12:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 分段显示消息,每个段落有随机延迟
|
|
|
|
|
* @param segments 段落数组
|
|
|
|
|
* @param isPlayer 是否为玩家消息
|
|
|
|
|
*/
|
2025-09-16 16:55:39 +08:00
|
|
|
private displaySegmentsWithDelay(
|
|
|
|
|
segments: string[],
|
|
|
|
|
isPlayer: boolean
|
|
|
|
|
): void {
|
|
|
|
|
// let currentDelay = 500; // 基础延迟 500ms
|
|
|
|
|
// 每个后续段落增加随机延迟 (800-1500ms)
|
|
|
|
|
|
|
|
|
|
// currentDelay += 800 + Math.random() * 700;
|
|
|
|
|
|
2025-08-27 16:12:19 +08:00
|
|
|
segments.forEach((segment, index) => {
|
2025-09-16 16:55:39 +08:00
|
|
|
this.dialogData.pushDialog(isPlayer, segment);
|
2025-08-27 16:12:19 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 添加加载中的对话
|
|
|
|
|
* 显示"..."循环动画表示AI正在回复
|
|
|
|
|
*/
|
|
|
|
|
public addLoadingDialog(): void {
|
2025-09-16 16:55:39 +08:00
|
|
|
this.dialogData.pushDialog(false, "...", true);
|
2025-08-27 16:12:19 +08:00
|
|
|
console.log("Loading dialog added");
|
|
|
|
|
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 移除加载中的对话
|
|
|
|
|
* 在收到AI回复或出错时调用
|
|
|
|
|
*/
|
|
|
|
|
public removeLoadingDialog(): void {
|
2025-09-16 16:55:39 +08:00
|
|
|
const dialogs = this.dialogData.GetDialogs();
|
|
|
|
|
const loadingIndex = dialogs.findIndex((dialog) => dialog.isLoading);
|
2025-08-27 16:12:19 +08:00
|
|
|
if (loadingIndex !== -1) {
|
|
|
|
|
dialogs.splice(loadingIndex, 1);
|
|
|
|
|
console.log("Loading dialog removed");
|
|
|
|
|
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-11 16:08:59 +08:00
|
|
|
/**
|
|
|
|
|
* 获取所有对话记录
|
2025-08-13 11:31:21 +08:00
|
|
|
*
|
2025-08-11 16:08:59 +08:00
|
|
|
* @returns {Dialog[]} 对话记录数组
|
|
|
|
|
*/
|
|
|
|
|
public getDialogs() {
|
2025-09-16 16:55:39 +08:00
|
|
|
return this.dialogData.GetDialogs();
|
2025-08-11 16:08:59 +08:00
|
|
|
}
|
2025-08-13 11:31:21 +08:00
|
|
|
|
2025-08-11 16:08:59 +08:00
|
|
|
/**
|
|
|
|
|
* 清空当前对话记录
|
|
|
|
|
*/
|
|
|
|
|
public clearDialogs(): void {
|
2025-09-16 16:55:39 +08:00
|
|
|
this.dialogData.cleanDialog();
|
2025-08-11 16:08:59 +08:00
|
|
|
console.log("All dialogs cleared");
|
|
|
|
|
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
|
|
|
|
}
|
2025-08-27 16:12:19 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 批量设置对话记录 (用于与ChatModel同步)
|
|
|
|
|
* @param dialogs 对话记录数组
|
|
|
|
|
*/
|
|
|
|
|
public setDialogs(dialogs: Dialog[]): void {
|
2025-09-16 16:55:39 +08:00
|
|
|
this.dialogData.cleanDialog();
|
|
|
|
|
dialogs.forEach((dialog) => {
|
|
|
|
|
this.dialogData.pushDialog(
|
|
|
|
|
dialog.isPlayer,
|
|
|
|
|
dialog.content,
|
|
|
|
|
dialog.isLoading || false
|
|
|
|
|
);
|
2025-08-27 16:12:19 +08:00
|
|
|
});
|
|
|
|
|
console.log(`DialogManager: Set ${dialogs.length} dialogs`);
|
|
|
|
|
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 同步ChatModel的对话数据到DialogManager
|
|
|
|
|
* @param dialogs ChatModel中的对话数据
|
|
|
|
|
*/
|
|
|
|
|
public syncFromChatModel(dialogs: Dialog[]): void {
|
|
|
|
|
this.setDialogs(dialogs);
|
|
|
|
|
console.log("DialogManager: Synced dialogs from ChatModel");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取对话记录数量
|
|
|
|
|
*/
|
|
|
|
|
public getDialogCount(): number {
|
2025-09-16 16:55:39 +08:00
|
|
|
return this.dialogData.GetDialogs().length;
|
2025-08-27 16:12:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取最后一条对话记录
|
|
|
|
|
*/
|
|
|
|
|
public getLastDialog(): Dialog | null {
|
2025-09-16 16:55:39 +08:00
|
|
|
const dialogs = this.dialogData.GetDialogs();
|
2025-08-27 16:12:19 +08:00
|
|
|
return dialogs.length > 0 ? dialogs[dialogs.length - 1] : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 检查是否有对话记录
|
|
|
|
|
*/
|
|
|
|
|
public hasDialogs(): boolean {
|
2025-09-16 16:55:39 +08:00
|
|
|
return this.dialogData.GetDialogs().length > 0;
|
2025-08-27 16:12:19 +08:00
|
|
|
}
|
2025-08-11 16:08:59 +08:00
|
|
|
}
|