优化聊天部分
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
* 统一管理所有API相关的配置信息
|
* 统一管理所有API相关的配置信息
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { DataId, DataManager } from "../data/DataManager";
|
||||||
|
import { GlobalData } from "../data/GlobalData";
|
||||||
import { ConfigManager } from "../manager/ConfigManager";
|
import { ConfigManager } from "../manager/ConfigManager";
|
||||||
|
|
||||||
export interface AIConfig {
|
export interface AIConfig {
|
||||||
@@ -41,15 +43,21 @@ export class ApiConfig {
|
|||||||
* TODO: 应该从环境变量或安全配置文件中读取
|
* TODO: 应该从环境变量或安全配置文件中读取
|
||||||
*/
|
*/
|
||||||
private initConfig(): void {
|
private initConfig(): void {
|
||||||
const config = ConfigManager.tables.TbGlobalConfig;
|
//const config = DataManager.I.getDataById<GlobalData>(DataId.Global).getApiKey();
|
||||||
this.config = {
|
this.config = {
|
||||||
// 警告: API密钥不应该硬编码在代码中
|
// 警告: API密钥不应该硬编码在代码中
|
||||||
// 生产环境中应该从环境变量或安全配置文件中读取
|
// 生产环境中应该从环境变量或安全配置文件中读取
|
||||||
apiKey: config.ApiKey,
|
apiKey: DataManager.I.getDataById<GlobalData>(DataId.Global).getApiKey(),
|
||||||
model: config.Model,
|
model: DataManager.I.getDataById<GlobalData>(DataId.Global).getModel(),
|
||||||
temperature: config.Temperature,
|
temperature: DataManager.I.getDataById<GlobalData>(
|
||||||
maxTokens: config.MaxTokens,
|
DataId.Global
|
||||||
timeout: config.Timeout, // 30秒
|
).getTemperature(),
|
||||||
|
maxTokens: DataManager.I.getDataById<GlobalData>(
|
||||||
|
DataId.Global
|
||||||
|
).getMaxTokens(),
|
||||||
|
timeout: DataManager.I.getDataById<GlobalData>(
|
||||||
|
DataId.Global
|
||||||
|
).getTimeout(), // 30秒
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,13 +72,18 @@ export class ApiConfig {
|
|||||||
* 获取情绪AI配置
|
* 获取情绪AI配置
|
||||||
*/
|
*/
|
||||||
public getEmotionAIConfig(): AIConfig {
|
public getEmotionAIConfig(): AIConfig {
|
||||||
const config = ConfigManager.tables.TbGlobalConfig;
|
|
||||||
return {
|
return {
|
||||||
apiKey: config.emotionApiKey,
|
apiKey: DataManager.I.getDataById<GlobalData>(
|
||||||
model: config.Model,
|
DataId.Global
|
||||||
temperature: config.Temperature,
|
).getEmotionApiKey(),
|
||||||
maxTokens: config.MaxTokens,
|
model: DataManager.I.getDataById<GlobalData>(DataId.Global).getModel(),
|
||||||
timeout: config.Timeout,
|
temperature: 0.2,
|
||||||
|
maxTokens: DataManager.I.getDataById<GlobalData>(
|
||||||
|
DataId.Global
|
||||||
|
).getMaxTokens(),
|
||||||
|
timeout: DataManager.I.getDataById<GlobalData>(
|
||||||
|
DataId.Global
|
||||||
|
).getTimeout(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,10 +87,9 @@ export class ChatAIService {
|
|||||||
const config = ApiConfig.Instance.getAIConfig();
|
const config = ApiConfig.Instance.getAIConfig();
|
||||||
|
|
||||||
// 使用 ChatHistoryManager 的统一加载方法(优先本地,无则获取服务端)
|
// 使用 ChatHistoryManager 的统一加载方法(优先本地,无则获取服务端)
|
||||||
const savedHistory = await ChatHistoryManager.Instance.loadChatHistory(
|
let savedHistory = await ChatHistoryManager.Instance.loadChatHistory(
|
||||||
roleId
|
roleId
|
||||||
);
|
);
|
||||||
|
|
||||||
let chat;
|
let chat;
|
||||||
if (savedHistory && savedHistory.length > 0) {
|
if (savedHistory && savedHistory.length > 0) {
|
||||||
chat = this.ai.chats.create({
|
chat = this.ai.chats.create({
|
||||||
|
|||||||
@@ -191,9 +191,6 @@ export class ChatController {
|
|||||||
throw new Error("Role ID is not available in ChatModel");
|
throw new Error("Role ID is not available in ChatModel");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通知界面消息发送开始
|
|
||||||
this.callback?.onMessageSent(message);
|
|
||||||
|
|
||||||
// 添加用户消息到模型
|
// 添加用户消息到模型
|
||||||
this.chatModel.addDialog(true, message);
|
this.chatModel.addDialog(true, message);
|
||||||
|
|
||||||
@@ -201,6 +198,9 @@ export class ChatController {
|
|||||||
this.dialogManager?.updateDialog(true, message, true);
|
this.dialogManager?.updateDialog(true, message, true);
|
||||||
this.callback?.onDialogUpdated();
|
this.callback?.onDialogUpdated();
|
||||||
|
|
||||||
|
// 通知界面消息发送开始
|
||||||
|
this.callback?.onMessageSent(message);
|
||||||
|
|
||||||
// 显示加载中的对话
|
// 显示加载中的对话
|
||||||
this.dialogManager?.addLoadingDialog();
|
this.dialogManager?.addLoadingDialog();
|
||||||
|
|
||||||
@@ -469,7 +469,7 @@ export class ChatController {
|
|||||||
* 检查是否有指定角色的数据
|
* 检查是否有指定角色的数据
|
||||||
* @param roleId 角色ID
|
* @param roleId 角色ID
|
||||||
*/
|
*/
|
||||||
public hasRoleData(categoryId:string,roleId: number): boolean {
|
public hasRoleData(categoryId: string, roleId: number): boolean {
|
||||||
return this.chatModel.hasRoleData(roleId);
|
return this.chatModel.hasRoleData(roleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ export class RoleConfigLoader {
|
|||||||
AiCharacter.basePrompt +
|
AiCharacter.basePrompt +
|
||||||
"\n" +
|
"\n" +
|
||||||
AiCharacter.additionPrompt;
|
AiCharacter.additionPrompt;
|
||||||
|
|
||||||
|
console.log(AiCharacter.basePrompt);
|
||||||
|
console.log(AiCharacter.additionPrompt);
|
||||||
return prompt;
|
return prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +33,7 @@ export class RoleConfigLoader {
|
|||||||
*/
|
*/
|
||||||
public static getEmotionInstruction(roleId: number): string {
|
public static getEmotionInstruction(roleId: number): string {
|
||||||
const AiCharacter = ConfigManager.tables.TbAiCharacters.get(roleId);
|
const AiCharacter = ConfigManager.tables.TbAiCharacters.get(roleId);
|
||||||
const globalPrompt = ConfigManager.tables.TbGlobalConfig.EmotionRating;
|
const globalPrompt = ConfigManager.tables.TbGlobalConfig.EmotionBasePrompt;
|
||||||
const prompt = globalPrompt + "\n" + AiCharacter.basePrompt;
|
const prompt = globalPrompt + "\n" + AiCharacter.basePrompt;
|
||||||
return prompt;
|
return prompt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,24 @@ export interface Dialog {
|
|||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DemoData {
|
export class DiaLogData {
|
||||||
public Dialogs: Dialog[] = [];
|
public Dialogs: Dialog[] = [];
|
||||||
|
|
||||||
public cleanDialog() {
|
public cleanDialog() {
|
||||||
this.Dialogs = [];
|
this.Dialogs = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public pushDialog(isPlayer: boolean, str: string, isLoading: boolean = false) {
|
public pushDialog(
|
||||||
|
isPlayer: boolean,
|
||||||
|
str: string,
|
||||||
|
isLoading: boolean = false
|
||||||
|
) {
|
||||||
if (!this.Dialogs) this.Dialogs = [];
|
if (!this.Dialogs) this.Dialogs = [];
|
||||||
this.Dialogs.push({ isPlayer: isPlayer, content: str, isLoading: isLoading });
|
this.Dialogs.push({
|
||||||
|
isPlayer: isPlayer,
|
||||||
|
content: str,
|
||||||
|
isLoading: isLoading,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public GetDialogs() {
|
public GetDialogs() {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { find } from "cc";
|
|
||||||
import { ChatContentsLayout } from "../ui/components/ChatContentsLayout";
|
import { ChatContentsLayout } from "../ui/components/ChatContentsLayout";
|
||||||
import { DemoData, Dialog } from "../data/DialogData";
|
import { DiaLogData, Dialog } from "../data/DialogData";
|
||||||
import { NavigationManager } from "./NavigationManager";
|
|
||||||
import Utils from "db://assets/Scripts/Main/Common/Utils";
|
import Utils from "db://assets/Scripts/Main/Common/Utils";
|
||||||
import { InnerMsgCode } from "db://assets/Scripts/Main/Config/InnerMsgCode";
|
import { InnerMsgCode } from "db://assets/Scripts/Main/Config/InnerMsgCode";
|
||||||
|
|
||||||
@@ -31,19 +29,15 @@ export class DialogManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.demoData = new DemoData();
|
this.dialogData = new DiaLogData();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 对话数据实例 */
|
/** 对话数据实例 */
|
||||||
private demoData: DemoData;
|
private dialogData: DiaLogData;
|
||||||
|
|
||||||
/** 当前主题ID */
|
|
||||||
private themeId = -1;
|
|
||||||
|
|
||||||
/** 聊天内容布局组件引用 */
|
/** 聊天内容布局组件引用 */
|
||||||
public layoutout: ChatContentsLayout;
|
public layoutout: ChatContentsLayout;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新对话内容
|
* 更新对话内容
|
||||||
*
|
*
|
||||||
@@ -57,14 +51,9 @@ export class DialogManager {
|
|||||||
fromPlayer: boolean = false
|
fromPlayer: boolean = false
|
||||||
): void {
|
): void {
|
||||||
if (fromPlayer) {
|
if (fromPlayer) {
|
||||||
this.demoData.cleanDialog();
|
this.dialogData.cleanDialog();
|
||||||
}
|
}
|
||||||
this.demoData.pushDialog(isPlayer, str);
|
this.dialogData.pushDialog(isPlayer, str);
|
||||||
console.log("Dialog updated:", {
|
|
||||||
isPlayer,
|
|
||||||
content: str.substring(0, 50) + "...",
|
|
||||||
});
|
|
||||||
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,17 +69,16 @@ export class DialogManager {
|
|||||||
fromPlayer: boolean = false
|
fromPlayer: boolean = false
|
||||||
): void {
|
): void {
|
||||||
if (fromPlayer) {
|
if (fromPlayer) {
|
||||||
this.demoData.cleanDialog();
|
this.dialogData.cleanDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是玩家消息,直接添加单个气泡
|
// 如果是玩家消息,直接添加单个气泡
|
||||||
if (isPlayer) {
|
if (isPlayer) {
|
||||||
this.demoData.pushDialog(isPlayer, str);
|
this.dialogData.pushDialog(isPlayer, str);
|
||||||
console.log("Dialog updated:", {
|
console.log("Dialog updated:", {
|
||||||
isPlayer,
|
isPlayer,
|
||||||
content: str.substring(0, 50) + "...",
|
content: str.substring(0, 50) + "...",
|
||||||
});
|
});
|
||||||
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,8 +87,7 @@ export class DialogManager {
|
|||||||
|
|
||||||
if (segments.length <= 1) {
|
if (segments.length <= 1) {
|
||||||
// 如果只有一段,直接显示单个气泡
|
// 如果只有一段,直接显示单个气泡
|
||||||
this.demoData.pushDialog(isPlayer, str);
|
this.dialogData.pushDialog(isPlayer, str);
|
||||||
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
|
||||||
} else {
|
} else {
|
||||||
// 多段内容,依次显示多个气泡
|
// 多段内容,依次显示多个气泡
|
||||||
this.displaySegmentsWithDelay(segments, isPlayer);
|
this.displaySegmentsWithDelay(segments, isPlayer);
|
||||||
@@ -123,8 +110,8 @@ export class DialogManager {
|
|||||||
|
|
||||||
// 过滤空段落并去除首尾空白
|
// 过滤空段落并去除首尾空白
|
||||||
return segments
|
return segments
|
||||||
.map(segment => segment.trim())
|
.map((segment) => segment.trim())
|
||||||
.filter(segment => segment.length > 0);
|
.filter((segment) => segment.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,22 +119,17 @@ export class DialogManager {
|
|||||||
* @param segments 段落数组
|
* @param segments 段落数组
|
||||||
* @param isPlayer 是否为玩家消息
|
* @param isPlayer 是否为玩家消息
|
||||||
*/
|
*/
|
||||||
private displaySegmentsWithDelay(segments: string[], isPlayer: boolean): void {
|
private displaySegmentsWithDelay(
|
||||||
let currentDelay = 500; // 基础延迟 500ms
|
segments: string[],
|
||||||
|
isPlayer: boolean
|
||||||
|
): void {
|
||||||
|
// let currentDelay = 500; // 基础延迟 500ms
|
||||||
|
// 每个后续段落增加随机延迟 (800-1500ms)
|
||||||
|
|
||||||
|
// currentDelay += 800 + Math.random() * 700;
|
||||||
|
|
||||||
segments.forEach((segment, index) => {
|
segments.forEach((segment, index) => {
|
||||||
setTimeout(() => {
|
this.dialogData.pushDialog(isPlayer, segment);
|
||||||
this.demoData.pushDialog(isPlayer, segment);
|
|
||||||
console.log(`Segment ${index + 1}/${segments.length} displayed:`, {
|
|
||||||
isPlayer,
|
|
||||||
content: segment.substring(0, 30) + "...",
|
|
||||||
delay: currentDelay
|
|
||||||
});
|
|
||||||
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
|
||||||
}, currentDelay);
|
|
||||||
|
|
||||||
// 每个后续段落增加随机延迟 (800-1500ms)
|
|
||||||
currentDelay += 800 + Math.random() * 700;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +138,7 @@ export class DialogManager {
|
|||||||
* 显示"..."循环动画表示AI正在回复
|
* 显示"..."循环动画表示AI正在回复
|
||||||
*/
|
*/
|
||||||
public addLoadingDialog(): void {
|
public addLoadingDialog(): void {
|
||||||
this.demoData.pushDialog(false, "...", true);
|
this.dialogData.pushDialog(false, "...", true);
|
||||||
console.log("Loading dialog added");
|
console.log("Loading dialog added");
|
||||||
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
||||||
}
|
}
|
||||||
@@ -166,8 +148,8 @@ export class DialogManager {
|
|||||||
* 在收到AI回复或出错时调用
|
* 在收到AI回复或出错时调用
|
||||||
*/
|
*/
|
||||||
public removeLoadingDialog(): void {
|
public removeLoadingDialog(): void {
|
||||||
const dialogs = this.demoData.GetDialogs();
|
const dialogs = this.dialogData.GetDialogs();
|
||||||
const loadingIndex = dialogs.findIndex(dialog => dialog.isLoading);
|
const loadingIndex = dialogs.findIndex((dialog) => dialog.isLoading);
|
||||||
if (loadingIndex !== -1) {
|
if (loadingIndex !== -1) {
|
||||||
dialogs.splice(loadingIndex, 1);
|
dialogs.splice(loadingIndex, 1);
|
||||||
console.log("Loading dialog removed");
|
console.log("Loading dialog removed");
|
||||||
@@ -181,14 +163,14 @@ export class DialogManager {
|
|||||||
* @returns {Dialog[]} 对话记录数组
|
* @returns {Dialog[]} 对话记录数组
|
||||||
*/
|
*/
|
||||||
public getDialogs() {
|
public getDialogs() {
|
||||||
return this.demoData.GetDialogs();
|
return this.dialogData.GetDialogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空当前对话记录
|
* 清空当前对话记录
|
||||||
*/
|
*/
|
||||||
public clearDialogs(): void {
|
public clearDialogs(): void {
|
||||||
this.demoData.cleanDialog();
|
this.dialogData.cleanDialog();
|
||||||
console.log("All dialogs cleared");
|
console.log("All dialogs cleared");
|
||||||
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
||||||
}
|
}
|
||||||
@@ -198,9 +180,13 @@ export class DialogManager {
|
|||||||
* @param dialogs 对话记录数组
|
* @param dialogs 对话记录数组
|
||||||
*/
|
*/
|
||||||
public setDialogs(dialogs: Dialog[]): void {
|
public setDialogs(dialogs: Dialog[]): void {
|
||||||
this.demoData.cleanDialog();
|
this.dialogData.cleanDialog();
|
||||||
dialogs.forEach(dialog => {
|
dialogs.forEach((dialog) => {
|
||||||
this.demoData.pushDialog(dialog.isPlayer, dialog.content, dialog.isLoading || false);
|
this.dialogData.pushDialog(
|
||||||
|
dialog.isPlayer,
|
||||||
|
dialog.content,
|
||||||
|
dialog.isLoading || false
|
||||||
|
);
|
||||||
});
|
});
|
||||||
console.log(`DialogManager: Set ${dialogs.length} dialogs`);
|
console.log(`DialogManager: Set ${dialogs.length} dialogs`);
|
||||||
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
Utils.sendInnerMsg(InnerMsgCode.Chat_DialogRefresh);
|
||||||
@@ -219,14 +205,14 @@ export class DialogManager {
|
|||||||
* 获取对话记录数量
|
* 获取对话记录数量
|
||||||
*/
|
*/
|
||||||
public getDialogCount(): number {
|
public getDialogCount(): number {
|
||||||
return this.demoData.GetDialogs().length;
|
return this.dialogData.GetDialogs().length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取最后一条对话记录
|
* 获取最后一条对话记录
|
||||||
*/
|
*/
|
||||||
public getLastDialog(): Dialog | null {
|
public getLastDialog(): Dialog | null {
|
||||||
const dialogs = this.demoData.GetDialogs();
|
const dialogs = this.dialogData.GetDialogs();
|
||||||
return dialogs.length > 0 ? dialogs[dialogs.length - 1] : null;
|
return dialogs.length > 0 ? dialogs[dialogs.length - 1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,6 +220,6 @@ export class DialogManager {
|
|||||||
* 检查是否有对话记录
|
* 检查是否有对话记录
|
||||||
*/
|
*/
|
||||||
public hasDialogs(): boolean {
|
public hasDialogs(): boolean {
|
||||||
return this.demoData.GetDialogs().length > 0;
|
return this.dialogData.GetDialogs().length > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ export class ChatContentsLayout extends Component {
|
|||||||
|
|
||||||
fixMaxWidth: number;
|
fixMaxWidth: number;
|
||||||
|
|
||||||
|
// 用于跟踪延时任务,支持中断清理
|
||||||
|
private delayedTasks: number[] = [];
|
||||||
|
|
||||||
protected start(): void {
|
protected start(): void {
|
||||||
this.lBubble.node.active = false;
|
this.lBubble.node.active = false;
|
||||||
this.rBubble.node.active = false;
|
this.rBubble.node.active = false;
|
||||||
@@ -42,6 +45,9 @@ export class ChatContentsLayout extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UpdateDialog(dialogs: Dialog[]) {
|
UpdateDialog(dialogs: Dialog[]) {
|
||||||
|
// 清理所有正在进行的延时任务
|
||||||
|
this.clearDelayedTasks();
|
||||||
|
|
||||||
// 隐藏当前使用的气泡并放入缓存
|
// 隐藏当前使用的气泡并放入缓存
|
||||||
for (let i = 0; i < this.bubbles.length; i++) {
|
for (let i = 0; i < this.bubbles.length; i++) {
|
||||||
if (this.bubbles[i].node) {
|
if (this.bubbles[i].node) {
|
||||||
@@ -51,7 +57,7 @@ export class ChatContentsLayout extends Component {
|
|||||||
}
|
}
|
||||||
this.bubbles = [];
|
this.bubbles = [];
|
||||||
|
|
||||||
this.updateDialogLayout(dialogs);
|
this.updateDialogLayoutWithDelay(dialogs);
|
||||||
|
|
||||||
// 清理多余的缓存气泡,避免内存泄漏
|
// 清理多余的缓存气泡,避免内存泄漏
|
||||||
this.cleanupExcessCachedBubbles();
|
this.cleanupExcessCachedBubbles();
|
||||||
@@ -93,7 +99,7 @@ export class ChatContentsLayout extends Component {
|
|||||||
? instantiate(this.rBubble.node)
|
? instantiate(this.rBubble.node)
|
||||||
: instantiate(this.lBubble.node);
|
: instantiate(this.lBubble.node);
|
||||||
newBubble = newBubbleNode.getComponent(DialogBubble);
|
newBubble = newBubbleNode.getComponent(DialogBubble);
|
||||||
newBubble.init();
|
newBubble.init(650);
|
||||||
newBubbleNode.setParent(this.node);
|
newBubbleNode.setParent(this.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +110,7 @@ export class ChatContentsLayout extends Component {
|
|||||||
pendingUpdates++;
|
pendingUpdates++;
|
||||||
newBubble.updateBubbleContent(
|
newBubble.updateBubbleContent(
|
||||||
dialog.content,
|
dialog.content,
|
||||||
|
dialog.isPlayer,
|
||||||
(actualHeight: number) => {
|
(actualHeight: number) => {
|
||||||
initPosY += actualHeight + 40;
|
initPosY += actualHeight + 40;
|
||||||
pendingUpdates--;
|
pendingUpdates--;
|
||||||
@@ -135,4 +142,154 @@ export class ChatContentsLayout extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private clearDelayedTasks() {
|
||||||
|
// 清理所有正在进行的延时任务
|
||||||
|
for (const taskId of this.delayedTasks) {
|
||||||
|
clearTimeout(taskId);
|
||||||
|
}
|
||||||
|
this.delayedTasks = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private groupConsecutiveNonPlayerDialogs(dialogs: Dialog[]): Dialog[][] {
|
||||||
|
const groups: Dialog[][] = [];
|
||||||
|
let currentGroup: Dialog[] = [];
|
||||||
|
|
||||||
|
for (const dialog of dialogs) {
|
||||||
|
if (dialog.isPlayer) {
|
||||||
|
// 遇到player消息,结束当前非player组
|
||||||
|
if (currentGroup.length > 0) {
|
||||||
|
groups.push([...currentGroup]);
|
||||||
|
currentGroup = [];
|
||||||
|
}
|
||||||
|
// player消息单独成组
|
||||||
|
groups.push([dialog]);
|
||||||
|
} else {
|
||||||
|
// 非player消息加入当前组
|
||||||
|
currentGroup.push(dialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理最后一组非player消息
|
||||||
|
if (currentGroup.length > 0) {
|
||||||
|
groups.push(currentGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateDialogLayoutWithDelay(dialogs: Dialog[]) {
|
||||||
|
const groups = this.groupConsecutiveNonPlayerDialogs(dialogs);
|
||||||
|
let initPosY: number = this.initPos.position.y;
|
||||||
|
|
||||||
|
// 倒序处理组,保持原有的显示顺序
|
||||||
|
this.processGroupsWithDelay(groups, groups.length - 1, initPosY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private processGroupsWithDelay(
|
||||||
|
groups: Dialog[][],
|
||||||
|
groupIndex: number,
|
||||||
|
currentPosY: number
|
||||||
|
) {
|
||||||
|
if (groupIndex < 0) return;
|
||||||
|
|
||||||
|
const currentGroup = groups[groupIndex];
|
||||||
|
|
||||||
|
// 直接渲染组,延时逻辑已移到单条消息级别
|
||||||
|
this.renderDialogGroup(currentGroup, currentPosY, (newPosY) => {
|
||||||
|
// 递归处理下一组,传递更新后的位置
|
||||||
|
this.processGroupsWithDelay(groups, groupIndex - 1, newPosY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderDialogGroup(
|
||||||
|
dialogs: Dialog[],
|
||||||
|
startPosY: number,
|
||||||
|
onComplete: (finalPosY: number) => void
|
||||||
|
) {
|
||||||
|
let currentPosY = startPosY;
|
||||||
|
|
||||||
|
const updateNextBubble = (index: number) => {
|
||||||
|
if (index < 0) {
|
||||||
|
onComplete(currentPosY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialog = dialogs[index];
|
||||||
|
|
||||||
|
// 计算当前消息的延时:非player消息且不是组内最后一条时添加延时
|
||||||
|
const isNonPlayerMessage = !dialog.isPlayer;
|
||||||
|
const isLastInGroup = index === dialogs.length - 1;
|
||||||
|
const delay =
|
||||||
|
isNonPlayerMessage && !isLastInGroup ? Math.random() * 1000 + 500 : 0; // 500-1500ms随机延时
|
||||||
|
|
||||||
|
const renderCurrentBubble = () => {
|
||||||
|
let newBubble: DialogBubble = null;
|
||||||
|
|
||||||
|
// 尝试从缓存中获取合适的气泡
|
||||||
|
const isPlayerBubble = dialog.isPlayer;
|
||||||
|
for (let j = 0; j < this.cachedBubbles.length; j++) {
|
||||||
|
const cached = this.cachedBubbles[j];
|
||||||
|
if (cached && cached.node) {
|
||||||
|
const isRightBubble = cached.node.position.x > 0;
|
||||||
|
if (
|
||||||
|
(isPlayerBubble && isRightBubble) ||
|
||||||
|
(!isPlayerBubble && !isRightBubble)
|
||||||
|
) {
|
||||||
|
newBubble = cached;
|
||||||
|
this.cachedBubbles.splice(j, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果缓存中没有合适的气泡,创建新的
|
||||||
|
if (!newBubble) {
|
||||||
|
let newBubbleNode = isPlayerBubble
|
||||||
|
? instantiate(this.rBubble.node)
|
||||||
|
: instantiate(this.lBubble.node);
|
||||||
|
newBubble = newBubbleNode.getComponent(DialogBubble);
|
||||||
|
newBubble.init(650);
|
||||||
|
newBubbleNode.setParent(this.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
newBubble.node.active = true;
|
||||||
|
let pos = newBubble.node.position;
|
||||||
|
newBubble.node.position = new Vec3(pos.x, currentPosY, pos.z);
|
||||||
|
|
||||||
|
newBubble.updateBubbleContent(
|
||||||
|
dialog.content,
|
||||||
|
dialog.isPlayer,
|
||||||
|
(actualHeight: number) => {
|
||||||
|
currentPosY += actualHeight + 40;
|
||||||
|
// 渲染完当前气泡后,处理下一个
|
||||||
|
updateNextBubble(index - 1);
|
||||||
|
},
|
||||||
|
dialog.isLoading || false
|
||||||
|
);
|
||||||
|
|
||||||
|
this.bubbles.push(newBubble);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (delay > 0) {
|
||||||
|
// 添加延时任务ID到跟踪数组
|
||||||
|
const taskId = setTimeout(() => {
|
||||||
|
// 从跟踪数组中移除已完成的任务
|
||||||
|
const taskIndex = this.delayedTasks.indexOf(taskId);
|
||||||
|
if (taskIndex > -1) {
|
||||||
|
this.delayedTasks.splice(taskIndex, 1);
|
||||||
|
}
|
||||||
|
renderCurrentBubble();
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
this.delayedTasks.push(taskId);
|
||||||
|
} else {
|
||||||
|
// 立即渲染
|
||||||
|
renderCurrentBubble();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从组内最后一个对话开始处理(倒序)
|
||||||
|
updateNextBubble(dialogs.length - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,21 @@ import {
|
|||||||
Node,
|
Node,
|
||||||
Overflow,
|
Overflow,
|
||||||
Size,
|
Size,
|
||||||
|
Sprite,
|
||||||
UITransform,
|
UITransform,
|
||||||
view,
|
view,
|
||||||
} from "cc";
|
} from "cc";
|
||||||
import Tools from "../../utils/tools";
|
import Tools from "../../utils/tools";
|
||||||
|
import { GirlData } from "../../data/GirlData";
|
||||||
|
import { DataId, DataManager } from "../../data/DataManager";
|
||||||
|
import { NavigationManager } from "../../manager/NavigationManager";
|
||||||
|
import ResManager from "../../../Main/Manager/ResManager";
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
@ccclass("DialogBubble")
|
@ccclass("DialogBubble")
|
||||||
export class DialogBubble extends Component {
|
export class DialogBubble extends Component {
|
||||||
|
@property(Sprite)
|
||||||
|
avatar: Sprite = null;
|
||||||
@property(UITransform)
|
@property(UITransform)
|
||||||
bg: UITransform = null;
|
bg: UITransform = null;
|
||||||
@property(Label)
|
@property(Label)
|
||||||
@@ -21,8 +28,12 @@ export class DialogBubble extends Component {
|
|||||||
contentT: UITransform = null;
|
contentT: UITransform = null;
|
||||||
|
|
||||||
fixMaxWidth: number;
|
fixMaxWidth: number;
|
||||||
private loadingAnimationId: number = null;
|
|
||||||
|
uiTransform: UITransform;
|
||||||
|
|
||||||
init(maxWidth?: number) {
|
init(maxWidth?: number) {
|
||||||
|
this.uiTransform = this.node.getComponent(UITransform);
|
||||||
|
|
||||||
// 如果没有提供maxWidth,使用屏幕宽度的80%
|
// 如果没有提供maxWidth,使用屏幕宽度的80%
|
||||||
if (maxWidth === undefined) {
|
if (maxWidth === undefined) {
|
||||||
this.fixMaxWidth = view.getVisibleSize().width * 0.8;
|
this.fixMaxWidth = view.getVisibleSize().width * 0.8;
|
||||||
@@ -33,6 +44,7 @@ export class DialogBubble extends Component {
|
|||||||
|
|
||||||
updateBubbleContent(
|
updateBubbleContent(
|
||||||
str: string,
|
str: string,
|
||||||
|
isPlayer: boolean = false,
|
||||||
callback?: (actualHeight: number) => void,
|
callback?: (actualHeight: number) => void,
|
||||||
isLoading: boolean = false
|
isLoading: boolean = false
|
||||||
) {
|
) {
|
||||||
@@ -80,7 +92,7 @@ export class DialogBubble extends Component {
|
|||||||
// 设置内容区域宽度,让Label自动计算高度
|
// 设置内容区域宽度,让Label自动计算高度
|
||||||
this.contentT.setContentSize(new Size(contentWidth, 0));
|
this.contentT.setContentSize(new Size(contentWidth, 0));
|
||||||
this.bg.setContentSize(new Size(contentWidth + 37, 0));
|
this.bg.setContentSize(new Size(contentWidth + 37, 0));
|
||||||
|
this.uiTransform.setContentSize(new Size(contentWidth + 37, 0));
|
||||||
// 强制更新Label的渲染数据以获取正确的高度
|
// 强制更新Label的渲染数据以获取正确的高度
|
||||||
this.content.updateRenderData(true);
|
this.content.updateRenderData(true);
|
||||||
|
|
||||||
@@ -95,6 +107,9 @@ export class DialogBubble extends Component {
|
|||||||
// 根据内容大小调整背景大小,添加适当的内边距
|
// 根据内容大小调整背景大小,添加适当的内边距
|
||||||
//const bgHeight = actualHeight + 17;
|
//const bgHeight = actualHeight + 17;
|
||||||
this.bg.setContentSize(new Size(actualSize.x + 40, actualSize.y + 30));
|
this.bg.setContentSize(new Size(actualSize.x + 40, actualSize.y + 30));
|
||||||
|
this.uiTransform.setContentSize(
|
||||||
|
new Size(actualSize.x + 40, actualSize.y + 30)
|
||||||
|
);
|
||||||
|
|
||||||
// 回调通知实际高度
|
// 回调通知实际高度
|
||||||
if (callback) {
|
if (callback) {
|
||||||
@@ -104,6 +119,19 @@ export class DialogBubble extends Component {
|
|||||||
|
|
||||||
// 返回预估的背景高度(用于同步计算)
|
// 返回预估的背景高度(用于同步计算)
|
||||||
const estimatedHeight = Math.max(lines.length * 35 + 20, 60); // 最小高度60
|
const estimatedHeight = Math.max(lines.length * 35 + 20, 60); // 最小高度60
|
||||||
|
|
||||||
|
//设置头像
|
||||||
|
if (isPlayer) {
|
||||||
|
//获取玩家头像,等接入luffa
|
||||||
|
} else {
|
||||||
|
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
|
||||||
|
const path = girlData.getGrilAvatar(
|
||||||
|
NavigationManager.Instance.getSelectedCategoryId().toString(),
|
||||||
|
NavigationManager.Instance.getSelectedGirlId()
|
||||||
|
);
|
||||||
|
ResManager.I.changeBundleSpriteFrame(this.avatar, path, "Girls");
|
||||||
|
}
|
||||||
|
|
||||||
return estimatedHeight;
|
return estimatedHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { SceneBgVideoLayer } from "../../../Sub/UI/SceneBgVideoLayer";
|
|||||||
import { NavigationManager, PanelType } from "../../manager/NavigationManager";
|
import { NavigationManager, PanelType } from "../../manager/NavigationManager";
|
||||||
import { GirlService } from "../../network/services/GirlService";
|
import { GirlService } from "../../network/services/GirlService";
|
||||||
import proto from "db://assets/Scripts/proto/proto.pb.js";
|
import proto from "db://assets/Scripts/proto/proto.pb.js";
|
||||||
|
import { TipsPanel } from "./TipsPanel";
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
@ccclass("ChatPanel")
|
@ccclass("ChatPanel")
|
||||||
@@ -389,7 +390,10 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
|||||||
|
|
||||||
public async OnClickSend() {
|
public async OnClickSend() {
|
||||||
const str = this.editBox.string;
|
const str = this.editBox.string;
|
||||||
if (!str || str == "") return;
|
if (!str || str == "") {
|
||||||
|
TipsPanel.show("请输入内容");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 通过ChatController发送消息
|
// 通过ChatController发送消息
|
||||||
const succeed = await this.chatController.sendMessage(str);
|
const succeed = await this.chatController.sendMessage(str);
|
||||||
@@ -437,6 +441,7 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
|
|||||||
|
|
||||||
onChatLimitReached(): void {
|
onChatLimitReached(): void {
|
||||||
this.payToTalkPanel.show(this.categoryId, this.id);
|
this.payToTalkPanel.show(this.categoryId, this.id);
|
||||||
|
TipsPanel.show(LanguageUtils.getText("chat_error_code_1001"));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 情绪状态更新回调 (实现IChatPanelCallback接口)
|
* 情绪状态更新回调 (实现IChatPanelCallback接口)
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ export class GlobalConfig {
|
|||||||
this.OnceChatAddScore = _buf_.readInt()
|
this.OnceChatAddScore = _buf_.readInt()
|
||||||
this.ScoreExchangeStarLevel = _buf_.readInt()
|
this.ScoreExchangeStarLevel = _buf_.readInt()
|
||||||
this.GameName = _buf_.readString()
|
this.GameName = _buf_.readString()
|
||||||
this.EmotionRating = _buf_.readString()
|
this.EmotionBasePrompt = _buf_.readString()
|
||||||
this.girlBasePrompt = _buf_.readString()
|
this.girlBasePrompt = _buf_.readString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +276,7 @@ export class GlobalConfig {
|
|||||||
/**
|
/**
|
||||||
* 情绪评分机器人system_prompt,需拼接角色prompt
|
* 情绪评分机器人system_prompt,需拼接角色prompt
|
||||||
*/
|
*/
|
||||||
readonly EmotionRating: string
|
readonly EmotionBasePrompt: string
|
||||||
/**
|
/**
|
||||||
* ai机器人基础规则
|
* ai机器人基础规则
|
||||||
*/
|
*/
|
||||||
@@ -801,7 +801,7 @@ export class TbGlobalConfig {
|
|||||||
/**
|
/**
|
||||||
* 情绪评分机器人system_prompt,需拼接角色prompt
|
* 情绪评分机器人system_prompt,需拼接角色prompt
|
||||||
*/
|
*/
|
||||||
get EmotionRating(): string { return this._data.EmotionRating; }
|
get EmotionBasePrompt(): string { return this._data.EmotionBasePrompt; }
|
||||||
/**
|
/**
|
||||||
* ai机器人基础规则
|
* ai机器人基础规则
|
||||||
*/
|
*/
|
||||||
|
|||||||
+1160
-470
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
Submodule xchat_cfg updated: 8e114f49a0...6f87186031
Reference in New Issue
Block a user