Files
18xchat/assets/Scripts/chat18x/core/ChatAIService.ts
T

367 lines
10 KiB
TypeScript
Raw Normal View History

2025-08-11 16:08:59 +08:00
// 首先加载 polyfills 以确保兼容性
import "../utils/polyfills";
2025-08-27 16:12:19 +08:00
import { GoogleGenAI, HarmBlockThreshold, HarmCategory } from "@google/genai";
import { RoleConfigLoader } from "./RoleConfigLoader";
import { ChatHistoryManager } from "../manager/ChatHistoryManager";
import { ApiConfig } from "./ApiConfigLoader";
2025-08-11 16:08:59 +08:00
import { ErrorHandler, ErrorType } from "../utils/ErrorHandler";
import { EmotionAIService } from "./EmotionAIService";
2025-09-10 17:37:24 +08:00
import { ChatService } from "db://assets/Scripts/chat18x/network/services/ChatService";
import { DataManager, DataId } from "../data/DataManager";
import { GirlData } from "../data/GirlData";
import proto from 'db://assets/Scripts/proto/proto.pb.js';
2025-08-11 16:08:59 +08:00
/**
* AI聊天服务类
*
2025-08-11 16:08:59 +08:00
* 基于Google Gemini API实现的多角色聊天系统,支持:
* - 多个独立的聊天实例管理
* - 每个角色拥有独立的对话上下文和历史记录
* - 本地聊天历史存储和加载
* - 动态角色配置和切换
*
2025-08-11 16:08:59 +08:00
* @example
* ```typescript
* const chatService = ChatAIService.Instance;
* chatService.setCurrentRole(10001);
* const response = await chatService.sendMessage(10001, "Hello");
* ```
*
2025-08-11 16:08:59 +08:00
* @author AI Chat System
* @version 2.0.0
*/
export class ChatAIService {
private static _instance: ChatAIService;
private ai: GoogleGenAI;
private chatInstances: Map<number, any> = new Map();
private currentRoleId: number | null = null;
2025-08-11 16:08:59 +08:00
private constructor() {
const config = ApiConfig.Instance.getAIConfig();
// 验证配置
if (!ApiConfig.Instance.validateConfig()) {
ErrorHandler.Instance.handleError(
new Error("AI配置验证失败"),
ErrorType.CONFIG_ERROR,
{ config },
true
);
throw new Error("AI服务初始化失败:配置无效");
2025-08-11 16:08:59 +08:00
}
try {
this.ai = new GoogleGenAI({ apiKey: config.apiKey });
} catch (error) {
ErrorHandler.Instance.handleError(
error as Error,
ErrorType.API_ERROR,
{ config: { ...config, apiKey: "***" } }, // 隐藏API密钥
true
);
throw error;
}
}
/**
* 获取ChatAIService的单例实例
*
* @returns {ChatAIService} 聊天服务实例
* @static
*/
public static get Instance(): ChatAIService {
if (!this._instance) {
this._instance = new ChatAIService();
}
return this._instance;
}
/**
* 创建或获取指定角色的聊天实例
* @param roleId 角色ID
*/
private createOrGetChat(roleId: number): any {
if (!this.chatInstances.has(roleId)) {
const systemInstruction = RoleConfigLoader.getRoleInstruction(roleId);
const config = ApiConfig.Instance.getAIConfig();
// 从本地加载历史记录
const savedHistory = ChatHistoryManager.Instance.loadHistory(roleId);
let chat;
if (savedHistory && savedHistory.length > 0) {
chat = this.ai.chats.create({
model: config.model,
config: {
temperature: config.temperature,
systemInstruction: systemInstruction,
2025-08-27 16:12:19 +08:00
safetySettings: [
{
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold: HarmBlockThreshold.BLOCK_NONE,
},
],
},
history: savedHistory,
});
} else {
chat = this.ai.chats.create({
model: config.model,
config: {
temperature: config.temperature,
systemInstruction: systemInstruction,
2025-08-27 16:12:19 +08:00
safetySettings: [
{
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold: HarmBlockThreshold.BLOCK_NONE,
},
],
},
});
}
this.chatInstances.set(roleId, chat);
if (savedHistory.length > 0) {
console.log(
`Loaded ${savedHistory.length} history messages for role ${roleId}`
);
} else {
console.log(`Created new chat instance for role ${roleId}`);
}
}
return this.chatInstances.get(roleId);
}
/**
* 设置当前活动的角色ID
* @param roleId 角色ID
*/
public setCurrentRole(roleId: number): void {
this.currentRoleId = roleId;
// 预创建聊天实例
this.createOrGetChat(roleId);
// 预创建情绪聊天实例
EmotionAIService.Instance.ensureEmotionChatExists(roleId);
}
/**
* 获取当前角色ID
*/
public getCurrentRoleId(): number | null {
return this.currentRoleId;
}
/**
* 向指定角色发送消息并获取AI回复
*
* @param {number} roleId - 角色ID,用于区分不同的聊天实例
* @param {string} message - 用户发送的消息内容
* @returns {Promise<string>} AI的回复消息,如果发生错误返回null
*
* @example
* ```typescript
* const response = await chatService.sendMessage(10001, "你好");
* console.log(response); // AI的回复
* ```
*/
public async sendMessage(roleId: number, message: string): Promise<string> {
// 输入验证
if (!roleId || roleId <= 0) {
ErrorHandler.Instance.handleValidationError(
"roleId",
"角色ID必须是正整数",
roleId
);
return null;
2025-08-11 16:08:59 +08:00
}
if (!message || message.trim() === "") {
ErrorHandler.Instance.handleValidationError(
"message",
"消息内容不能为空",
message
);
return null;
2025-08-11 16:08:59 +08:00
}
try {
const chat = this.createOrGetChat(roleId);
const response = await chat.sendMessage({
message: message.trim(),
});
2025-08-11 16:08:59 +08:00
if (response && response.text) {
console.log(`Response from role ${roleId}:`, response.text);
2025-08-11 16:08:59 +08:00
try {
// 保存用户消息
ChatHistoryManager.Instance.appendMessage(roleId, {
role: "user",
parts: [{ text: message }],
});
2025-08-11 16:08:59 +08:00
// 保存AI回复
ChatHistoryManager.Instance.appendMessage(roleId, {
role: "model",
parts: [{ text: response.text }],
});
2025-09-10 17:37:24 +08:00
// 上报聊天数据到服务器
let msgList: proto.cs.IChatMsg[] = [];
let userData = {
isAi: false,
msg: message,
};
msgList.push(userData);
let aiData = {
isAi: true,
msg: response.text,
};
msgList.push(aiData);
this.reqChatMsg(roleId, msgList);
2025-08-27 16:12:19 +08:00
// 异步更新情绪状态(非阻塞)
EmotionAIService.Instance.updateEmotionFromChat(
roleId,
message,
response.text
).catch(emotionError => {
console.warn(
`Failed to update emotion for role ${roleId}:`,
emotionError
);
// 情绪更新失败不影响聊天功能
2025-08-27 16:12:19 +08:00
});
} catch (storageError) {
ErrorHandler.Instance.handleError(
storageError as Error,
ErrorType.STORAGE_ERROR,
{ roleId, message: message.substring(0, 100) },
false
);
// 即使存储失败,也返回AI回复
2025-08-11 16:08:59 +08:00
}
return response.text;
} else {
const warningMsg = `AI返回了空响应 (角色ID: ${roleId})`;
ErrorHandler.Instance.handleError(
new Error(warningMsg),
ErrorType.API_ERROR,
2025-08-27 16:12:19 +08:00
{ roleId, chat, response },
true
);
return null;
}
} catch (error) {
ErrorHandler.Instance.handleApiError(error, "sendMessage", {
roleId,
message: message.substring(0, 100) + "...",
});
return null;
2025-08-11 16:08:59 +08:00
}
}
2025-08-11 16:08:59 +08:00
/**
* 清除指定角色的聊天历史
* @param roleId 角色ID
*/
public clearChatHistory(roleId: number): void {
if (this.chatInstances.has(roleId)) {
this.chatInstances.delete(roleId);
2025-08-11 16:08:59 +08:00
}
// 清除情绪聊天历史
EmotionAIService.Instance.clearEmotionHistory(roleId);
// 清除本地存储的历史
ChatHistoryManager.Instance.clearHistory(roleId);
console.log(`Cleared chat history for role ${roleId}`);
}
2025-08-11 16:08:59 +08:00
/**
* 清除所有聊天历史
*/
public clearAllChatHistory(): void {
this.chatInstances.clear();
// 清除所有情绪聊天历史
EmotionAIService.Instance.clearAllEmotionHistory();
// 清除本地存储的所有历史
ChatHistoryManager.Instance.clearAllHistory();
console.log("Cleared all chat histories");
}
2025-08-11 16:08:59 +08:00
/**
* 获取当前活跃的聊天实例数量
*/
public getActiveChatCount(): number {
return this.chatInstances.size;
}
/**
* 兼容旧接口的Post方法
* @deprecated 请使用sendMessage方法,此方法将在下个版本中移除
*/
public async Post(data: GPTRequest): Promise<string> {
console.warn("Post方法已废弃,请使用sendMessage方法");
const roleId = this.currentRoleId || 10001; // 默认使用第一个角色
const message = data.messages[0]?.content || "";
return this.sendMessage(roleId, message);
}
2025-09-10 17:37:24 +08:00
/**
* 上报聊天数据到服务器
*/
async reqChatMsg(girlId: number, msgList: proto.cs.IChatMsg[]) {
// 请求购买商品
const reqData = {
girlId,
ChatMsg: msgList,
2025-09-10 19:15:01 +08:00
msgId: new Date().getTime(),
2025-09-10 17:37:24 +08:00
};
console.log("请求上报聊天数据的请求数据:", reqData);
let res = await ChatService.I.reqChatMsg(reqData);
console.log("请求上报聊天数据的响应数据:", res);
if (res && res.code === proto.cs.EnmRetCode.SUCCESS) {
const resData = res.data;
// 保存数据
const girlData = DataManager.I.getDataById<GirlData>(DataId.Girl);
const category = girlData.getGrilCategoryById(girlId);
girlData.setChatTotalCount(category.toString(), girlId, resData.chatTotalCount);
girlData.setChatRemainCount(category.toString(), girlId, resData.chatRemainCount);
}
}
2025-08-11 16:08:59 +08:00
}
// 请求数据类型定义
export interface GPTRequest {
model: string;
messages: { role: string; content: string }[];
temperature: number;
id: string;
2025-08-11 16:08:59 +08:00
}
// 兼容旧名称(已废弃,建议使用 GPTRequest
/** @deprecated 请使用 GPTRequest */
export type GPTResquest = GPTRequest;
// 响应数据类型定义(当前未使用,预留用于未来API调用统计)
/** @deprecated 当前未使用,考虑移除或实现API统计功能时使用 */
export interface GPTResult {
id: string;
object: string;
created: number;
model: string;
usage: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
choices: {
message: {
role: string;
content: string;
2025-08-11 16:08:59 +08:00
};
finish_reason: string;
index: number;
}[];
}