Merge branch 'main' of 47.107.44.202:xionglijia/18xchat

This commit is contained in:
chen wei bo
2025-09-20 20:49:34 +08:00
18 changed files with 2262 additions and 242 deletions
+10 -3
View File
@@ -15,6 +15,7 @@ import {
ProgressBar,
Sprite,
SpriteFrame,
VideoPlayer,
} from "cc";
import { GButton } from "../../Main/Common/GButton";
import ResManager from "../../Main/Manager/ResManager";
@@ -60,7 +61,7 @@ export class PreloadUI extends Component {
loginBtn: Node;
private jinduBar: Node = null;
private jinduFilled: Sprite = null;
private VideoPlayer: VideoPlayer;
start() {
//Utils.addInnerEL(InnerMsgCode.UI_ShowPrompt, this, this.resiveShowPrompt)
Utils.parseNode(this.node, this._nodeTab);
@@ -68,6 +69,8 @@ export class PreloadUI extends Component {
this.loginBtn = this._nodeTab.btnLogin;
this.loginBtn.active = false;
this.VideoPlayer = this._nodeTab.VideoPlayer.getComponent(VideoPlayer);
GButton.BandClick(this.loginBtn, this.onLogin, this);
this.jinduBar = this.node.getChildByName("jinduBar");
@@ -81,6 +84,8 @@ export class PreloadUI extends Component {
this.jinduFilled.fillRange = 0;
if (!this.VideoPlayer.isPlaying) this.VideoPlayer.play();
// 初始化配置
ConfigManager.I.init();
//console.log(ConfigManager.tables.TbGlobalConfig.GameName);
@@ -254,10 +259,12 @@ export class PreloadUI extends Component {
let res = await CommonService.I.reqResConfig(reqData);
if (res && res.code === proto.cs.EnmRetCode.SUCCESS) {
// 保存数据
const aiCharacterData = DataManager.I.getDataById<AiCharacterData>(DataId.AiCharacter);
const aiCharacterData = DataManager.I.getDataById<AiCharacterData>(
DataId.AiCharacter
);
aiCharacterData.aiCharacter = res.data;
}
}
}
private enterGame() {
this.mainScene.init();
@@ -101,6 +101,10 @@ export class ApiConfig {
}
}
public getTimeout() {
return this.config.timeout;
}
/**
* 验证配置是否有效
*/
+44 -2
View File
@@ -11,6 +11,7 @@ import { DataManager, DataId } from "../data/DataManager";
import { GirlData } from "../data/GirlData";
import proto from "db://assets/Scripts/proto/proto.pb.js";
import { TipsPanel } from "../ui/panels/TipsPanel";
import LanguageUtils from "../../Main/Common/LanguageUtils";
/**
* AI聊天服务类
@@ -48,12 +49,18 @@ export class ChatAIService {
{ config },
true
);
TipsPanel.show(LanguageUtils.getText("chat_error_code_2002"));
throw new Error("AI服务初始化失败:配置无效");
}
try {
this.ai = new GoogleGenAI({ apiKey: config.apiKey });
this.ai = new GoogleGenAI({
apiKey: config.apiKey,
httpOptions: { timeout: ApiConfig.Instance.getTimeout() },
});
} catch (error) {
TipsPanel.show(LanguageUtils.getText("chat_error_code_2001"));
ErrorHandler.Instance.handleError(
error as Error,
ErrorType.API_ERROR,
@@ -102,6 +109,23 @@ export class ChatAIService {
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold: HarmBlockThreshold.BLOCK_NONE,
},
{
category: HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
threshold: HarmBlockThreshold.BLOCK_NONE,
},
{
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold: HarmBlockThreshold.BLOCK_NONE,
},
{
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold: HarmBlockThreshold.BLOCK_NONE,
},
{
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold: HarmBlockThreshold.BLOCK_NONE,
},
],
},
history: savedHistory,
@@ -120,6 +144,23 @@ export class ChatAIService {
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold: HarmBlockThreshold.BLOCK_NONE,
},
{
category: HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
threshold: HarmBlockThreshold.BLOCK_NONE,
},
{
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold: HarmBlockThreshold.BLOCK_NONE,
},
{
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold: HarmBlockThreshold.BLOCK_NONE,
},
{
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold: HarmBlockThreshold.BLOCK_NONE,
},
],
},
});
@@ -245,6 +286,7 @@ export class ChatAIService {
return response.text;
} else {
TipsPanel.show(response.promptFeedback.blockReason);
const warningMsg = `AI返回了空响应 (角色ID: ${roleId})`;
ErrorHandler.Instance.handleError(
new Error(warningMsg),
@@ -257,7 +299,7 @@ export class ChatAIService {
} catch (error) {
ErrorHandler.Instance.handleApiError(error, "sendMessage", {
roleId,
message: message.substring(0, 100) + "...",
message: message,
});
TipsPanel.show("api调用失败,请使用vpn并重新启动");
return null;
@@ -7,6 +7,8 @@ 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之间的通信协议
@@ -188,7 +190,8 @@ export class ChatController {
try {
const roleId = this.chatModel.getCurrentRoleId();
if (!roleId) {
throw new Error("Role ID is not available in ChatModel");
console.error("Role ID is not available in ChatModel");
TipsPanel.show(LanguageUtils.getText("chat_error_code_1004"));
}
// 添加用户消息到模型
@@ -40,7 +40,10 @@ export class EmotionAIService {
const emotionConfig = ApiConfig.Instance.getEmotionAIConfig();
try {
this.emotionAI = new GoogleGenAI({ apiKey: emotionConfig.apiKey });
this.emotionAI = new GoogleGenAI({
apiKey: emotionConfig.apiKey,
httpOptions: { timeout: emotionConfig.timeout },
});
} catch (error) {
ErrorHandler.Instance.handleError(
error as Error,
@@ -109,7 +112,9 @@ export class EmotionAIService {
// 分析历史情绪状态
emotionalState = await this.analyzeEmotionalState(roleId);
if (emotionalState == null) {
emotionalState = VideoEmotion.calm_down;
}
console.log(
`Initial emotional state for role ${roleId}: ${VideoEmotion[emotionalState]}`
);
@@ -185,7 +190,7 @@ export class EmotionAIService {
"角色ID必须是正整数",
roleId
);
return VideoEmotion.calm_down;
return null;
}
if (!message || message.trim() === "") {
@@ -194,7 +199,7 @@ export class EmotionAIService {
"消息内容不能为空",
message
);
return VideoEmotion.calm_down;
return null;
}
try {
@@ -214,14 +219,14 @@ export class EmotionAIService {
{ roleId, message, response },
true
);
return VideoEmotion.calm_down;
return null;
}
} catch (error) {
ErrorHandler.Instance.handleApiError(error, "sendEmotionMessage", {
roleId,
message: message.substring(0, 100) + "...",
});
return VideoEmotion.calm_down;
return null;
}
}
@@ -251,7 +256,7 @@ export class EmotionAIService {
{ roleId },
false
);
return VideoEmotion.calm_down;
return null;
}
}
@@ -376,8 +381,11 @@ export class EmotionAIService {
);
// 发送给情绪AI进行分析
const newEmotion = await this.sendEmotionMessage(roleId, analysisPrompt);
let newEmotion = await this.sendEmotionMessage(roleId, analysisPrompt);
if (newEmotion == null) {
newEmotion = currentEmotion;
}
// 更新并保存情绪状态
this.setCurrentEmotion(roleId, newEmotion);
@@ -0,0 +1,202 @@
import { ChatAIService } from '../core/ChatAIService';
/**
* ChatAI 批量测试运行器 - 纯脚本版本
*
* 用法:
* ```typescript
* const testRunner = new ChatAIBatchTestRunner();
* testRunner.runBatchTest();
* ```
*/
export class ChatAIBatchTestRunner {
private isRunning: boolean = false;
constructor() {
console.log("[ChatAIBatchTestRunner] 测试运行器已初始化");
}
/**
* 延时工具函数
* @param seconds 延时秒数
*/
private async delay(seconds: number): Promise<void> {
return new Promise(resolve => {
setTimeout(resolve, seconds * 1000);
});
}
/**
* 运行批量测试
*/
public async runBatchTest(): Promise<void> {
if (this.isRunning) {
console.log("[ChatAIBatchTestRunner] 测试已在运行中,请等待完成...");
return;
}
this.isRunning = true;
console.log("[ChatAIBatchTestRunner] 开始批量测试角色ID 10001-10030");
console.log("[ChatAIBatchTestRunner] 每次测试间隔10秒");
const startTime = Date.now();
const successIds: number[] = [];
const failureIds: number[] = [];
const errorDetails: { [roleId: number]: string } = {};
// 测试角色ID 10001-10030
for (let roleId = 10001; roleId <= 10030; roleId++) {
console.log(`[ChatAIBatchTestRunner] 正在测试角色ID: ${roleId}`);
const testStartTime = Date.now();
try {
const response = await ChatAIService.Instance.sendMessage(roleId, "hello");
const testDuration = Date.now() - testStartTime;
if (response && response.trim() !== "") {
successIds.push(roleId);
console.log(`[ChatAIBatchTestRunner] ✅ 角色 ${roleId} 测试成功 (${testDuration}ms)`);
console.log(`[ChatAIBatchTestRunner] 响应预览: ${response.substring(0, 50)}...`);
} else {
failureIds.push(roleId);
errorDetails[roleId] = "返回空响应";
console.log(`[ChatAIBatchTestRunner] ❌ 角色 ${roleId} 返回空响应 (${testDuration}ms)`);
}
} catch (error) {
const testDuration = Date.now() - testStartTime;
const errorMessage = error instanceof Error ? error.message : String(error);
failureIds.push(roleId);
errorDetails[roleId] = errorMessage;
console.log(`[ChatAIBatchTestRunner] ❌ 角色 ${roleId} 测试失败 (${testDuration}ms): ${errorMessage}`);
}
// 等待10秒(最后一个不需要等待)
if (roleId < 10030) {
console.log(`[ChatAIBatchTestRunner] 等待10秒后继续下一个测试...`);
await this.delay(10);
}
}
const totalDuration = Date.now() - startTime;
// 输出最终报告
this.printFinalReport(successIds, failureIds, errorDetails, totalDuration);
this.isRunning = false;
console.log("[ChatAIBatchTestRunner] 批量测试完成!");
}
/**
* 输出最终测试报告
*/
private printFinalReport(
successIds: number[],
failureIds: number[],
errorDetails: { [roleId: number]: string },
totalDuration: number
): void {
const totalTests = 30;
const successCount = successIds.length;
const failureCount = failureIds.length;
const successRate = ((successCount / totalTests) * 100).toFixed(2);
console.log("\n" + "=".repeat(60));
console.log(" ChatAI 批量测试报告");
console.log("=".repeat(60));
console.log(`测试时间: ${new Date().toLocaleString()}`);
console.log(`总测试数: ${totalTests}`);
console.log(`成功数量: ${successCount}`);
console.log(`失败数量: ${failureCount}`);
console.log(`成功率: ${successRate}%`);
console.log(`总耗时: ${(totalDuration / 1000).toFixed(2)}`);
console.log(`平均耗时: ${(totalDuration / totalTests / 1000).toFixed(2)}秒/测试`);
console.log("\n" + "-".repeat(30) + " 成功的角色ID " + "-".repeat(30));
if (successIds.length > 0) {
const successList = this.formatIdList(successIds);
console.log(successList);
} else {
console.log("无成功案例");
}
console.log("\n" + "-".repeat(30) + " 失败的角色ID " + "-".repeat(30));
if (failureIds.length > 0) {
const failureList = this.formatIdList(failureIds);
console.log(failureList);
console.log("\n" + "-".repeat(25) + " 失败详情 " + "-".repeat(25));
failureIds.forEach(roleId => {
console.log(`角色 ${roleId}: ${errorDetails[roleId]}`);
});
} else {
console.log("无失败案例");
}
console.log("\n" + "=".repeat(60));
// 输出简洁版结果供复制使用
console.log("\n简洁结果:");
console.log(`成功(${successCount}): ${successIds.join(',')}`);
console.log(`失败(${failureCount}): ${failureIds.join(',')}`);
}
/**
* 格式化ID列表为易读格式
*/
private formatIdList(ids: number[]): string {
if (ids.length === 0) return "无";
const sortedIds = ids.sort((a, b) => a - b);
const groups: string[] = [];
let start = sortedIds[0];
let end = sortedIds[0];
for (let i = 1; i < sortedIds.length; i++) {
if (sortedIds[i] === end + 1) {
end = sortedIds[i];
} else {
if (start === end) {
groups.push(`${start}`);
} else if (end === start + 1) {
groups.push(`${start},${end}`);
} else {
groups.push(`${start}-${end}`);
}
start = end = sortedIds[i];
}
}
// 添加最后一组
if (start === end) {
groups.push(`${start}`);
} else if (end === start + 1) {
groups.push(`${start},${end}`);
} else {
groups.push(`${start}-${end}`);
}
return groups.join(', ');
}
/**
* 获取当前运行状态
*/
public isTestRunning(): boolean {
return this.isRunning;
}
}
// 全局实例,可以直接调用
export const chatAIBatchTester = new ChatAIBatchTestRunner();
// 便捷的全局函数
export async function runChatAIBatchTest(): Promise<void> {
await chatAIBatchTester.runBatchTest();
}
// 使用示例:
// import { runChatAIBatchTest } from 'path/to/ChatAIBatchTestRunner';
// runChatAIBatchTest();
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "76e29352-9c31-48f1-ac08-8646aa63a425",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,287 @@
import { _decorator, Component, Node, log, Button, Label } from 'cc';
import { ChatAIService } from '../core/ChatAIService';
const { ccclass, property } = _decorator;
interface TestResult {
roleId: number;
success: boolean;
response?: string;
error?: string;
duration: number;
}
interface TestReport {
totalTests: number;
successCount: number;
failureCount: number;
successIds: number[];
failureIds: number[];
results: TestResult[];
totalDuration: number;
}
@ccclass('ChatAIServiceBatchTest')
export class ChatAIServiceBatchTest extends Component {
@property(Button)
startTestButton: Button = null;
@property(Label)
statusLabel: Label = null;
@property(Label)
resultLabel: Label = null;
private isTestRunning: boolean = false;
private testReport: TestReport = null;
onLoad() {
if (this.startTestButton) {
this.startTestButton.node.on(Button.EventType.CLICK, this.startBatchTest, this);
}
this.updateStatus("点击开始按钮进行批量测试");
}
/**
* 延时工具函数
* @param seconds 延时秒数
*/
private async delay(seconds: number): Promise<void> {
return new Promise(resolve => {
setTimeout(resolve, seconds * 1000);
});
}
/**
* 更新状态显示
*/
private updateStatus(message: string): void {
if (this.statusLabel) {
this.statusLabel.string = message;
}
log(`[ChatAIBatchTest] ${message}`);
}
/**
* 更新结果显示
*/
private updateResult(report: TestReport): void {
if (!this.resultLabel) return;
const resultText = `测试完成!
总测试数: ${report.totalTests}
成功数: ${report.successCount}
失败数: ${report.failureCount}
总耗时: ${(report.totalDuration / 1000).toFixed(2)}s
成功的ID: ${report.successIds.join(', ')}
失败的ID: ${report.failureIds.join(', ')}`;
this.resultLabel.string = resultText;
}
/**
* 测试单个角色ID
*/
private async testSingleRole(roleId: number): Promise<TestResult> {
const startTime = Date.now();
try {
this.updateStatus(`正在测试角色ID: ${roleId}`);
const response = await ChatAIService.Instance.sendMessage(roleId, "hello");
const duration = Date.now() - startTime;
if (response && response.trim() !== "") {
log(`[ChatAIBatchTest] 角色 ${roleId} 测试成功: ${response.substring(0, 50)}...`);
return {
roleId,
success: true,
response: response.substring(0, 100), // 只记录前100字符
duration
};
} else {
log(`[ChatAIBatchTest] 角色 ${roleId} 返回空响应`);
return {
roleId,
success: false,
error: "返回空响应",
duration
};
}
} catch (error) {
const duration = Date.now() - startTime;
const errorMessage = error instanceof Error ? error.message : String(error);
log(`[ChatAIBatchTest] 角色 ${roleId} 测试失败: ${errorMessage}`);
return {
roleId,
success: false,
error: errorMessage,
duration
};
}
}
/**
* 开始批量测试
*/
public async startBatchTest(): Promise<void> {
if (this.isTestRunning) {
this.updateStatus("测试正在进行中,请等待...");
return;
}
this.isTestRunning = true;
if (this.startTestButton) {
this.startTestButton.interactable = false;
}
if (this.resultLabel) {
this.resultLabel.string = "";
}
const startTime = Date.now();
const results: TestResult[] = [];
const successIds: number[] = [];
const failureIds: number[] = [];
log("[ChatAIBatchTest] 开始批量测试角色ID 10001-10030");
this.updateStatus("开始批量测试...");
// 测试角色ID 10001-10030
for (let roleId = 10001; roleId <= 10030; roleId++) {
try {
// 测试单个角色
const result = await this.testSingleRole(roleId);
results.push(result);
if (result.success) {
successIds.push(roleId);
} else {
failureIds.push(roleId);
}
// 等待10秒(最后一个不需要等待)
if (roleId < 10030) {
this.updateStatus(`角色 ${roleId} 测试完成,等待10秒...`);
await this.delay(10);
}
} catch (error) {
log(`[ChatAIBatchTest] 测试角色 ${roleId} 时发生意外错误: ${error}`);
results.push({
roleId,
success: false,
error: `意外错误: ${error}`,
duration: 0
});
failureIds.push(roleId);
}
}
const totalDuration = Date.now() - startTime;
// 生成测试报告
this.testReport = {
totalTests: 30,
successCount: successIds.length,
failureCount: failureIds.length,
successIds,
failureIds,
results,
totalDuration
};
// 输出详细报告到控制台
this.logDetailedReport(this.testReport);
// 更新UI显示
this.updateResult(this.testReport);
this.updateStatus("批量测试完成!");
if (this.startTestButton) {
this.startTestButton.interactable = true;
}
this.isTestRunning = false;
}
/**
* 输出详细测试报告到控制台
*/
private logDetailedReport(report: TestReport): void {
log("========== ChatAI 批量测试报告 ==========");
log(`测试时间: ${new Date().toLocaleString()}`);
log(`总测试数: ${report.totalTests}`);
log(`成功数: ${report.successCount}`);
log(`失败数: ${report.failureCount}`);
log(`成功率: ${((report.successCount / report.totalTests) * 100).toFixed(2)}%`);
log(`总耗时: ${(report.totalDuration / 1000).toFixed(2)}`);
log(`平均耗时: ${(report.totalDuration / report.totalTests / 1000).toFixed(2)}秒/测试`);
log("\n===== 成功的角色ID =====");
if (report.successIds.length > 0) {
log(report.successIds.join(', '));
} else {
log("无");
}
log("\n===== 失败的角色ID =====");
if (report.failureIds.length > 0) {
log(report.failureIds.join(', '));
log("\n===== 失败详情 =====");
report.results
.filter(r => !r.success)
.forEach(result => {
log(`角色 ${result.roleId}: ${result.error}`);
});
} else {
log("无");
}
log("\n===== 详细测试结果 =====");
report.results.forEach(result => {
const status = result.success ? "成功" : "失败";
const duration = (result.duration / 1000).toFixed(2);
const extra = result.success
? `响应: ${result.response?.substring(0, 30)}...`
: `错误: ${result.error}`;
log(`角色 ${result.roleId}: ${status} (${duration}s) - ${extra}`);
});
log("========================================");
}
/**
* 获取测试报告(供外部调用)
*/
public getTestReport(): TestReport {
return this.testReport;
}
/**
* 重置测试状态
*/
public resetTest(): void {
this.isTestRunning = false;
this.testReport = null;
if (this.statusLabel) {
this.statusLabel.string = "点击开始按钮进行批量测试";
}
if (this.resultLabel) {
this.resultLabel.string = "";
}
if (this.startTestButton) {
this.startTestButton.interactable = true;
}
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "a95a7fa5-11a5-47f0-9cbe-6bd704cbbb74",
"files": [],
"subMetas": {},
"userData": {}
}
+121
View File
@@ -0,0 +1,121 @@
# ChatAI 批量测试工具
本工具用于批量测试 ChatAIService 的 sendMessage 功能,测试角色ID 10001-10030 的可用性。
## 文件说明
### 1. ChatAIServiceBatchTest.ts
- **类型**: Cocos Creator 组件
- **用途**: 带UI界面的测试工具
- **特点**: 需要挂载到节点上,有可视化界面
### 2. ChatAIBatchTestRunner.ts
- **类型**: 纯TypeScript脚本
- **用途**: 无UI的批量测试工具
- **特点**: 可以直接在代码中调用
## 使用方法
### 方法一:使用UI组件版本
1. 在Cocos Creator中创建一个测试场景
2. 创建一个节点并挂载 `ChatAIServiceBatchTest` 组件
3. 配置UI元素:
- `startTestButton`: 开始测试按钮
- `statusLabel`: 状态显示标签
- `resultLabel`: 结果显示标签
4. 运行场景,点击按钮开始测试
### 方法二:使用纯脚本版本(推荐)
在任何TypeScript文件中导入并调用:
```typescript
import { runChatAIBatchTest } from 'db://assets/Scripts/chat18x/test/ChatAIBatchTestRunner';
// 直接运行测试
runChatAIBatchTest();
```
或者使用类实例:
```typescript
import { ChatAIBatchTestRunner } from 'db://assets/Scripts/chat18x/test/ChatAIBatchTestRunner';
const testRunner = new ChatAIBatchTestRunner();
await testRunner.runBatchTest();
```
## 测试流程
1. 依次测试角色ID 10001 到 10030
2. 对每个ID发送消息 "hello"
3. 每次调用后等待10秒(最后一个测试不等待)
4. 记录成功和失败的角色ID
5. 输出详细的测试报告
## 测试报告内容
- 总测试数量
- 成功/失败数量和比例
- 成功的角色ID列表
- 失败的角色ID列表及错误原因
- 每个测试的耗时统计
- 总耗时和平均耗时
## 示例输出
```
==============================================================
ChatAI 批量测试报告
==============================================================
测试时间: 2024/3/15 14:30:25
总测试数: 30
成功数量: 25
失败数量: 5
成功率: 83.33%
总耗时: 315.68秒
平均耗时: 10.52秒/测试
------------------------------ 成功的角色ID ------------------------------
10001-10015, 10017-10025, 10028
------------------------------ 失败的角色ID ------------------------------
10016, 10026, 10027, 10029, 10030
------------------------- 失败详情 -------------------------
角色 10016: API调用超时
角色 10026: 返回空响应
角色 10027: 网络连接失败
角色 10029: API密钥无效
角色 10030: 角色配置不存在
==============================================================
简洁结果:
成功(25): 10001,10002,10003,10004,10005,10006,10007,10008,10009,10010,10011,10012,10013,10014,10015,10017,10018,10019,10020,10021,10022,10023,10024,10025,10028
失败(5): 10016,10026,10027,10029,10030
```
## 注意事项
1. 确保 ChatAIService 已正确初始化
2. 测试过程中不要关闭应用,总耗时约5-6分钟
3. 测试结果会输出到控制台,注意查看
4. 建议在开发环境下运行测试
5. 如需中断测试,重启应用即可
## 技术细节
- 使用 Promise 和 async/await 处理异步操作
- 通过 setTimeout 实现精确的10秒延时
- 自动错误捕获和分类
- 智能的ID列表格式化显示
- 详细的执行时间统计
## 故障排除
如果测试无法启动:
1. 检查 ChatAIService 是否正确导入
2. 确认 AI API 配置是否有效
3. 查看控制台错误信息
4. 确保网络连接正常
@@ -0,0 +1,11 @@
{
"ver": "1.0.1",
"importer": "text",
"imported": true,
"uuid": "0bad818f-088c-4639-905a-324516bbdafb",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}
@@ -15,6 +15,7 @@ import { DataId, DataManager } from "../../data/DataManager";
import { NavigationManager } from "../../manager/NavigationManager";
import ResManager from "../../../Main/Manager/ResManager";
import { EnvData } from "../../data/EnvData";
import Utils from "../../../Main/Common/Utils";
const { ccclass, property } = _decorator;
@ccclass("DialogBubble")
@@ -54,6 +55,24 @@ export class DialogBubble extends Component {
this.content.overflow = Overflow.NONE;
//设置头像
if (isPlayer) {
//获取玩家头像,等接入luffa
} else {
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
const path = girlData.getGrilAvatar(
NavigationManager.Instance.getSelectedCategoryId().toString(),
NavigationManager.Instance.getSelectedGirlId()
);
// 加载远程资源
const envData = DataManager.I.getDataById<EnvData>(DataId.Env);
let newPath = envData.cdn + "/" + "Girls/" + path + ".png";
ResManager.I.changeRemoteSpriteFrame(this.avatar, newPath, () => {
this.scaleToFillItem(this.avatar, new Size(90, 90));
});
}
// 如果是加载状态,使用固定的"..."
if (isLoading) {
this.content.string = "...";
@@ -121,23 +140,46 @@ export class DialogBubble extends Component {
// 返回预估的背景高度(用于同步计算)
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()
);
return estimatedHeight;
}
// 加载远程资源
const envData = DataManager.I.getDataById<EnvData>(DataId.Env);
let newPath = envData.cdn + "/" + "Girls/" + path + ".png";
ResManager.I.changeRemoteSpriteFrame(this.avatar, newPath, () => {});
scaleToFillItem(img: Sprite, size: Size) {
if (!img || !img.spriteFrame) return;
// 延迟一帧确保UI布局完成
this.doScaleToFill(img, size);
}
doScaleToFill(img: Sprite, size: Size) {
if (!img || !img.spriteFrame) return;
const itemSize = size;
const imageSize = img.spriteFrame.originalSize;
if (
itemSize.width === 0 ||
itemSize.height === 0 ||
imageSize.width === 0 ||
imageSize.height === 0
) {
return;
}
return estimatedHeight;
const scaleX = itemSize.width / imageSize.width;
const scaleY = itemSize.height / imageSize.height;
const scale = Math.max(scaleX, scaleY);
// 设置Sprite的sizeMode为CUSTOM,允许自定义大小
img.sizeMode = Sprite.SizeMode.CUSTOM;
// 获取图片节点的UITransform并设置大小
const imgTransform = img.node.getComponent(UITransform);
if (imgTransform) {
imgTransform.setContentSize(
imageSize.width * scale,
imageSize.height * scale
);
}
}
private stopLoadingAnimation() {
+14 -4
View File
@@ -73,7 +73,7 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
categoryId: string;
id: number;
private _nodeTab: any = {};
private waitingAI: Node;
nameKey: string;
private currentEmotion: VideoEmotion | null = null;
@@ -87,6 +87,7 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
onLoadCT() {
Utils.parseNode(this.node, this._nodeTab);
this.waitingAI = this._nodeTab.waitingAI;
// 获取 videoArea 的尺寸和位置
this.targetSize = this.videoArea.contentSize;
this.videoAreaPos = new Vec3(540, 1170, 0);
@@ -150,7 +151,7 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
async refresh() {
const newRoleId = NavigationManager.Instance.getSelectedGirlId();
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
this.waitingAI.active = false;
// 检查是否是切换角色
const isRoleSwitch = this.id && this.id !== newRoleId;
@@ -399,18 +400,27 @@ export class ChatPanel extends li_BaseView implements IChatPanelCallback {
this.refresh();
}
isWaiting: boolean = false;
public async OnClickSend() {
const str = this.editBox.string;
if (!str || str == "") {
TipsPanel.show("请输入内容");
TipsPanel.show(LanguageUtils.getText("chat_error_code_1002"));
return;
}
if (this.isWaiting) {
TipsPanel.show(LanguageUtils.getText("chat_error_code_1003"));
}
// 通过ChatController发送消息
this.isWaiting = true;
this.waitingAI.active = true;
const succeed = await this.chatController.sendMessage(str);
if (succeed)
if (succeed) {
// 清空输入框
this.editBox.string = "";
}
this.isWaiting = false;
this.waitingAI.active = false;
}
// === IChatPanelCallback 接口实现 ===
File diff suppressed because it is too large Load Diff
+624
View File
@@ -0,0 +1,624 @@
[
{
"__type__": "cc.AnimationClip",
"_name": "chat_waiting",
"_objFlags": 0,
"__editorExtras__": {
"embeddedPlayerGroups": []
},
"_native": "",
"sample": 60,
"speed": 1,
"wrapMode": 2,
"enableTrsBlending": false,
"_duration": 0.75,
"_hash": 500763545,
"_tracks": [
{
"__id__": 1
},
{
"__id__": 12
},
{
"__id__": 23
}
],
"_exoticAnimation": null,
"_events": [],
"_embeddedPlayers": [],
"_additiveSettings": {
"__id__": 34
},
"_auxiliaryCurveEntries": []
},
{
"__type__": "cc.animation.VectorTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 2
},
"proxy": null
},
"_channels": [
{
"__id__": 4
},
{
"__id__": 6
},
{
"__id__": 8
},
{
"__id__": 10
}
],
"_nComponents": 3
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 3
},
"position"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "dot"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 5
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0,
0.28333333134651184,
0.550000011920929
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -262.56298828125,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -262.56298828125,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -262.56298828125,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 7
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0,
0.28333333134651184,
0.550000011920929
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 2,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 2,
"tangentWeightMode": 0,
"value": 22.399999618530273,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 2,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 9
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0,
0.28333333134651184
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 11
}
},
{
"__type__": "cc.RealCurve",
"_times": [],
"_values": [],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.VectorTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 13
},
"proxy": null
},
"_channels": [
{
"__id__": 15
},
{
"__id__": 17
},
{
"__id__": 19
},
{
"__id__": 21
}
],
"_nComponents": 3
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 14
},
"position"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "dot-001"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 16
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0.0833333358168602,
0.36666667461395264,
0.6333333253860474
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -232.46299743652344,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -232.56300354003906,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -232.56300354003906,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 18
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0.0833333358168602,
0.36666667461395264,
0.6333333253860474
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 2,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 2,
"tangentWeightMode": 0,
"value": 22.399999618530273,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 2,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 20
}
},
{
"__type__": "cc.RealCurve",
"_times": [],
"_values": [],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 22
}
},
{
"__type__": "cc.RealCurve",
"_times": [],
"_values": [],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.VectorTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 24
},
"proxy": null
},
"_channels": [
{
"__id__": 26
},
{
"__id__": 28
},
{
"__id__": 30
},
{
"__id__": 32
}
],
"_nComponents": 3
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 25
},
"position"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "dot-002"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 27
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0.20000000298023224,
0.46666666865348816,
0.75
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -202.56300354003906,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -202.56300354003906,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -202.56300354003906,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 29
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0.20000000298023224,
0.46666666865348816,
0.75
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 2,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 2,
"tangentWeightMode": 0,
"value": 22.399999618530273,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 2,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 31
}
},
{
"__type__": "cc.RealCurve",
"_times": [],
"_values": [],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 33
}
},
{
"__type__": "cc.RealCurve",
"_times": [],
"_values": [],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.AnimationClipAdditiveSettings",
"enabled": false,
"refClip": null
}
]
+13
View File
@@ -0,0 +1,13 @@
{
"ver": "2.0.3",
"importer": "animation-clip",
"imported": true,
"uuid": "aa04af0a-fa02-48bb-8423-66b7aac1f1b4",
"files": [
".cconb"
],
"subMetas": {},
"userData": {
"name": "chat_waiting"
}
}