- MVP架构重构聊天逻辑
- 优化聊天气泡体验 - 修复prompt bug - 实现聊天次数限制功能(未接服务端) - 实现情绪切视频功能
This commit is contained in:
@@ -24,12 +24,16 @@ import ResManager from "db://assets/Scripts/Main/Manager/ResManager";
|
||||
import LanguageUtils from "../../../Main/Common/LanguageUtils";
|
||||
import { UITransitionHelper } from "../../utils/UITransitionHelper";
|
||||
import { VideoEmotion, purchase } from "../../../schema/schema";
|
||||
import { PayToTalkSubpanel } from "./PayToTalkSubpanel";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("ChatPanel")
|
||||
export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
manager: DialogManager = null;
|
||||
private chatController: ChatController = new ChatController();
|
||||
private chatController: ChatController = null;
|
||||
|
||||
@property(PayToTalkSubpanel)
|
||||
payToTalkPanel: PayToTalkSubpanel = null;
|
||||
|
||||
@property(EditBox)
|
||||
editBox: EditBox = null;
|
||||
@@ -62,25 +66,44 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
};
|
||||
|
||||
openUIDataCT(data) {
|
||||
// 处理新的数据格式,支持过渡动画参数
|
||||
if (typeof data === "object" && data.roleId !== undefined) {
|
||||
this.id = data.roleId;
|
||||
const newRoleId =
|
||||
typeof data === "object" && data.roleId !== undefined
|
||||
? data.roleId
|
||||
: data;
|
||||
const withAnimation = typeof data === "object" && data.withSlideTransition;
|
||||
|
||||
// 如果标记了需要滑入动画,则执行动画
|
||||
if (data.withSlideTransition && this.node && this.node.isValid) {
|
||||
// 延迟一帧执行动画,确保节点已正确加载到场景中
|
||||
this.scheduleOnce(() => {
|
||||
UITransitionHelper.slideInFromRight(this.node, 0.3);
|
||||
}, 0);
|
||||
}
|
||||
} else {
|
||||
// 兼容原来的数字格式
|
||||
this.id = data;
|
||||
// 检查是否是切换角色
|
||||
const isRoleSwitch = this.id && this.id !== newRoleId;
|
||||
|
||||
this.id = newRoleId;
|
||||
|
||||
// 如果标记了需要滑入动画,则执行动画
|
||||
if (withAnimation && this.node && this.node.isValid) {
|
||||
// 延迟一帧执行动画,确保节点已正确加载到场景中
|
||||
this.scheduleOnce(() => {
|
||||
UITransitionHelper.slideInFromRight(this.node, 0.3);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// 初始化ChatController
|
||||
// 获取ChatController单例并绑定当前Panel
|
||||
this.chatController = ChatController.Instance;
|
||||
this.chatController.bindView(this);
|
||||
|
||||
// 初始化或切换ChatController
|
||||
if (this.id && this.id > 0) {
|
||||
this.chatController.initialize(this.id, this);
|
||||
if (isRoleSwitch && this.chatController.hasRoleData(this.id)) {
|
||||
// 如果是角色切换且有缓存数据,使用switchRole
|
||||
console.log(
|
||||
`ChatPanel: Switching from role ${this.chatController.getCurrentRoleId()} to role ${
|
||||
this.id
|
||||
}`
|
||||
);
|
||||
this.chatController.switchRole(this.id);
|
||||
} else {
|
||||
// 首次初始化或没有缓存数据,使用initialize
|
||||
console.log(`ChatPanel: Initializing role ${this.id}`);
|
||||
this.chatController.initialize(this.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +112,7 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
|
||||
this.register();
|
||||
this.refresh(this.id);
|
||||
|
||||
this.payToTalkPanel.node.active = false;
|
||||
// Add video loaded event callback
|
||||
if (this.girlVideo) {
|
||||
this.girlVideo.node.on(
|
||||
@@ -147,9 +170,10 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
);
|
||||
}
|
||||
|
||||
// 销毁ChatController
|
||||
// 解绑ChatController(不销毁,因为它是单例)
|
||||
if (this.chatController) {
|
||||
this.chatController.destroy();
|
||||
this.chatController.unbindView();
|
||||
this.chatController = null;
|
||||
}
|
||||
}
|
||||
refresh(id: number) {
|
||||
@@ -178,59 +202,53 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
}
|
||||
);
|
||||
|
||||
// Store commercialVideos for emotion-based switching
|
||||
if (dataDetail && dataDetail.commercialVideos) {
|
||||
this.commercialVideos = dataDetail.commercialVideos;
|
||||
console.log(`Loaded ${this.commercialVideos.length} videos for role ${this.id}`);
|
||||
|
||||
// 通过ChatController获取商业视频数据
|
||||
const chatModel = this.chatController.getChatModel();
|
||||
const commercialVideos = chatModel.getCommercialVideos();
|
||||
this.commercialVideos = commercialVideos;
|
||||
|
||||
if (commercialVideos.length > 0) {
|
||||
console.log(
|
||||
`Loaded ${commercialVideos.length} videos for role ${this.id}`
|
||||
);
|
||||
|
||||
// Load initial video based on current emotion
|
||||
if (this.commercialVideos.length > 0 && this.girlVideo) {
|
||||
if (this.girlVideo) {
|
||||
// Get current emotion from ChatController
|
||||
let currentEmotion = VideoEmotion.calm_down; // Default fallback
|
||||
|
||||
try {
|
||||
if (this.chatController) {
|
||||
currentEmotion = this.chatController.getCurrentEmotion();
|
||||
console.log(`Current emotion for role ${this.id}: ${VideoEmotion[currentEmotion]}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to get current emotion during initialization, using calm_down as default:", error);
|
||||
}
|
||||
|
||||
// Find video matching current emotion or fallback
|
||||
const initialVideo = this.commercialVideos.find(video => video.emotion === currentEmotion)
|
||||
|| this.commercialVideos.find(video => video.emotion === VideoEmotion.calm_down)
|
||||
|| this.commercialVideos[0]; // Final fallback to first video
|
||||
|
||||
console.log(`Loading initial video: ${initialVideo.path} (emotion: ${VideoEmotion[initialVideo.emotion]})`);
|
||||
|
||||
// Set current emotion to the loaded video's emotion
|
||||
this.currentEmotion = initialVideo.emotion;
|
||||
|
||||
ResManager.I.changeBundleVideo(
|
||||
this.girlVideo,
|
||||
initialVideo.path,
|
||||
"Chat18x"
|
||||
const currentEmotion = this.chatController.getCurrentEmotion();
|
||||
console.log(
|
||||
`Current emotion for role ${this.id}: ${VideoEmotion[currentEmotion]}`
|
||||
);
|
||||
// Also immediately try to adjust scale (in case already loaded)
|
||||
this.adjustVideoScale();
|
||||
|
||||
// Get appropriate video from ChatModel
|
||||
const initialVideo =
|
||||
chatModel.getVideoByEmotion(currentEmotion) ||
|
||||
chatModel.getDefaultVideo();
|
||||
|
||||
if (initialVideo) {
|
||||
console.log(
|
||||
`Loading initial video: ${initialVideo.path} (emotion: ${
|
||||
VideoEmotion[initialVideo.emotion]
|
||||
})`
|
||||
);
|
||||
|
||||
// Set current emotion to the loaded video's emotion
|
||||
this.currentEmotion = initialVideo.emotion;
|
||||
|
||||
ResManager.I.changeBundleVideo(
|
||||
this.girlVideo,
|
||||
initialVideo.path,
|
||||
"Chat18x"
|
||||
);
|
||||
// Also immediately try to adjust scale (in case already loaded)
|
||||
this.adjustVideoScale();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.commercialVideos = [];
|
||||
this.currentEmotion = null; // Reset current emotion when no videos available
|
||||
console.log("No commercialVideos data available for role", this.id);
|
||||
}
|
||||
|
||||
// if (dataDetail && dataDetail.commercialVideos && dataDetail.commercialVideos.length > 0) {
|
||||
// let uiTransform: UITransform =
|
||||
// this._nodeTab.BgFrame.getComponent(UITransform);
|
||||
|
||||
// Utils.sendInnerMsg(InnerMsgCode.SimplePlayVide, {
|
||||
// path: dataDetail.commercialVideos[0].path,
|
||||
// size: uiTransform.contentSize,
|
||||
// bgFrameNode: this._nodeTab.BgFrame,
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
adjustVideoScale() {
|
||||
@@ -278,68 +296,101 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
}
|
||||
|
||||
private switchVideoByEmotion(emotion: VideoEmotion): void {
|
||||
if (!this.girlVideo || !this.commercialVideos || this.commercialVideos.length === 0) {
|
||||
console.warn("Cannot switch video: missing video player or commercialVideos data");
|
||||
if (!this.girlVideo) {
|
||||
console.warn("Cannot switch video: missing video player");
|
||||
return;
|
||||
}
|
||||
// Get video data from ChatModel through ChatController
|
||||
const chatModel = this.chatController.getChatModel();
|
||||
const commercialVideos = chatModel.getCommercialVideos();
|
||||
|
||||
if (commercialVideos.length === 0) {
|
||||
console.warn("Cannot switch video: no commercialVideos data");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if emotion has changed
|
||||
if (this.currentEmotion === emotion) {
|
||||
console.log(`Emotion ${VideoEmotion[emotion]} unchanged, skipping video switch`);
|
||||
console.log(
|
||||
`Emotion ${VideoEmotion[emotion]} unchanged, skipping video switch`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find video matching the emotion
|
||||
const matchingVideo = this.commercialVideos.find(video => video.emotion === emotion);
|
||||
|
||||
// Get video matching the emotion from ChatModel
|
||||
const matchingVideo = chatModel.getVideoByEmotion(emotion);
|
||||
|
||||
if (matchingVideo) {
|
||||
console.log(`Switching to video for emotion ${VideoEmotion[emotion]}: ${matchingVideo.path} (from ${this.currentEmotion !== null ? VideoEmotion[this.currentEmotion] : 'null'})`);
|
||||
|
||||
console.log(
|
||||
`Switching to video for emotion ${VideoEmotion[emotion]}: ${
|
||||
matchingVideo.path
|
||||
} (from ${
|
||||
this.currentEmotion !== null
|
||||
? VideoEmotion[this.currentEmotion]
|
||||
: "null"
|
||||
})`
|
||||
);
|
||||
|
||||
// Load the new video
|
||||
ResManager.I.changeBundleVideo(
|
||||
this.girlVideo,
|
||||
matchingVideo.path,
|
||||
"Chat18x"
|
||||
);
|
||||
|
||||
|
||||
// Update current emotion state
|
||||
this.currentEmotion = emotion;
|
||||
|
||||
|
||||
// Adjust scale and enable playback
|
||||
this.adjustVideoScale();
|
||||
this.setVideoEnable(true);
|
||||
} else {
|
||||
console.warn(`No video found for emotion ${VideoEmotion[emotion]}, falling back to first available video`);
|
||||
|
||||
// Fallback to first video if no match found
|
||||
if (this.commercialVideos.length > 0) {
|
||||
const fallbackVideo = this.commercialVideos[0];
|
||||
console.log(`Loading fallback video: ${fallbackVideo.path} (emotion: ${VideoEmotion[fallbackVideo.emotion]})`);
|
||||
|
||||
console.warn(
|
||||
`No video found for emotion ${VideoEmotion[emotion]}, falling back to default video`
|
||||
);
|
||||
|
||||
// Fallback to default video from ChatModel
|
||||
const fallbackVideo = chatModel.getDefaultVideo();
|
||||
if (fallbackVideo) {
|
||||
console.log(
|
||||
`Loading fallback video: ${fallbackVideo.path} (emotion: ${
|
||||
VideoEmotion[fallbackVideo.emotion]
|
||||
})`
|
||||
);
|
||||
|
||||
ResManager.I.changeBundleVideo(
|
||||
this.girlVideo,
|
||||
fallbackVideo.path,
|
||||
"Chat18x"
|
||||
);
|
||||
|
||||
|
||||
// Update current emotion state to the fallback video's emotion
|
||||
this.currentEmotion = fallbackVideo.emotion;
|
||||
|
||||
|
||||
this.adjustVideoScale();
|
||||
this.setVideoEnable(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onEmotionInitialized(data: {roleId: number, emotion: VideoEmotion}): void {
|
||||
private onEmotionInitialized(data: {
|
||||
roleId: number;
|
||||
emotion: VideoEmotion;
|
||||
}): void {
|
||||
// 检查是否是当前角色
|
||||
if (data.roleId === this.id) {
|
||||
console.log(`Emotion initialized for current role ${this.id}: ${VideoEmotion[data.emotion]}`);
|
||||
|
||||
console.log(
|
||||
`Emotion initialized for current role ${this.id}: ${
|
||||
VideoEmotion[data.emotion]
|
||||
}`
|
||||
);
|
||||
|
||||
// 切换到对应的视频
|
||||
this.switchVideoByEmotion(data.emotion);
|
||||
} else {
|
||||
console.log(`Emotion initialized for role ${data.roleId} (not current role ${this.id}), ignoring`);
|
||||
console.log(
|
||||
`Emotion initialized for role ${data.roleId} (not current role ${this.id}), ignoring`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,14 +409,11 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
const str = this.editBox.string;
|
||||
if (!str || str == "") return;
|
||||
|
||||
// 清空输入框
|
||||
this.editBox.string = "";
|
||||
|
||||
// 通过ChatController发送消息
|
||||
await this.chatController.sendMessage(str);
|
||||
|
||||
//测试
|
||||
//this.popUpImage.refresh("Image/1/blur_naked_1");
|
||||
const succeed = await this.chatController.sendMessage(str);
|
||||
if (succeed)
|
||||
// 清空输入框
|
||||
this.editBox.string = "";
|
||||
}
|
||||
|
||||
// === IChatPanelCallback 接口实现 ===
|
||||
@@ -376,7 +424,7 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
*/
|
||||
public onMessageSent(message: string): void {
|
||||
console.log("Message sent:", message);
|
||||
// 可以在这里添加发送中的UI状态显示,比如显示loading等
|
||||
// 消息发送后开始显示加载动画,由DialogManager处理
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -385,7 +433,7 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
*/
|
||||
public onMessageReceived(response: string): void {
|
||||
console.log("Message received:", response);
|
||||
// 可以在这里添加接收到回复的UI效果,比如播放声音等
|
||||
// AI回复收到后加载动画会被自动移除,可以在这里添加其他UI效果
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -401,20 +449,25 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
* @param error 错误信息
|
||||
*/
|
||||
public onError(error: Error): void {
|
||||
console.error("ChatPanel error:", error);
|
||||
// 可以在这里显示错误提示给用户
|
||||
console.error("Chat error:", error);
|
||||
// 出错时加载动画会被自动移除,可以在这里显示错误提示给用户
|
||||
}
|
||||
|
||||
onChatLimitReached(): void {
|
||||
this.payToTalkPanel.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 情绪状态更新回调 (实现IChatPanelCallback接口)
|
||||
* @param emotion 当前情绪状态
|
||||
*/
|
||||
public onEmotionUpdated(emotion: VideoEmotion): void {
|
||||
console.log(`ChatPanel: Emotion updated to ${VideoEmotion[emotion]} (${emotion})`);
|
||||
|
||||
console.log(
|
||||
`ChatPanel: Emotion updated to ${VideoEmotion[emotion]} (${emotion})`
|
||||
);
|
||||
|
||||
// Switch video based on the new emotion
|
||||
this.switchVideoByEmotion(emotion);
|
||||
|
||||
|
||||
// Additional UI updates can be added here based on emotion
|
||||
// 例如:改变角色表情、背景色、播放相应的动画等
|
||||
switch (emotion) {
|
||||
@@ -450,4 +503,40 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
||||
onClickRecord() {
|
||||
ViewManager.I.openBundlesView("RecordPanel", this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前聊天状态信息(用于调试)
|
||||
*/
|
||||
public getChatStatus(): any {
|
||||
if (!this.chatController) {
|
||||
return { error: "ChatController not initialized" };
|
||||
}
|
||||
|
||||
return {
|
||||
currentPanelRoleId: this.id,
|
||||
controllerStats: this.chatController.getDialogStats(),
|
||||
modelSummary: this.chatController.getModelSummary(),
|
||||
allRolesStats: this.chatController.getAllRolesStats(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动清除指定角色的数据(调试用)
|
||||
* @param roleId 角色ID,不传则清除当前角色
|
||||
*/
|
||||
public clearRoleDataDebug(roleId?: number): void {
|
||||
if (!this.chatController) {
|
||||
console.warn("ChatController not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
const targetRoleId = roleId || this.id;
|
||||
if (!targetRoleId) {
|
||||
console.warn("No role ID specified");
|
||||
return;
|
||||
}
|
||||
|
||||
this.chatController.clearRoleData(targetRoleId);
|
||||
console.log(`Cleared data for role ${targetRoleId}`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user