// 首先加载 polyfills 以确保兼容性 import "../utils/polyfills"; import { GoogleGenAI, HarmBlockThreshold, HarmCategory } from "@google/genai"; import { RoleConfigLoader } from "./RoleConfigLoader"; import { ChatHistoryManager } from "../manager/ChatHistoryManager"; import { ApiConfig } from "./ApiConfigLoader"; import { ErrorHandler, ErrorType } from "../utils/ErrorHandler"; import { EmotionAIService } from "./EmotionAIService"; 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"; import { TipsPanel } from "../ui/panels/TipsPanel"; import LanguageUtils from "../../Main/Common/LanguageUtils"; /** * AI聊天服务类 * * 基于Google Gemini API实现的多角色聊天系统,支持: * - 多个独立的聊天实例管理 * - 每个角色拥有独立的对话上下文和历史记录 * - 本地聊天历史存储和加载 * - 动态角色配置和切换 * * @example * ```typescript * const chatService = ChatAIService.Instance; * chatService.setCurrentRole(10001); * const response = await chatService.sendMessage(10001, "Hello"); * ``` * * @author AI Chat System * @version 2.0.0 */ export class ChatAIService { private static _instance: ChatAIService; private ai: GoogleGenAI; private chatInstances: Map = new Map(); private currentRoleId: number | null = null; private constructor() { const config = ApiConfig.Instance.getAIConfig(); // 验证配置 if (!ApiConfig.Instance.validateConfig()) { ErrorHandler.Instance.handleError( new Error("AI配置验证失败"), ErrorType.CONFIG_ERROR, { config }, true ); TipsPanel.show(LanguageUtils.getText("chat_error_code_2002")); throw new Error("AI服务初始化失败:配置无效"); } try { 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, { 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 */ async createOrGetChat(roleId: number): Promise { if (!this.chatInstances.has(roleId)) { const systemInstruction = RoleConfigLoader.getRoleInstruction(roleId); const config = ApiConfig.Instance.getAIConfig(); // 使用 ChatHistoryManager 的统一加载方法(优先本地,无则获取服务端) let savedHistory = await ChatHistoryManager.Instance.loadChatHistory( roleId ); let chat; if (savedHistory && savedHistory.length > 0) { chat = this.ai.chats.create({ model: config.model, config: { temperature: config.temperature, systemInstruction: systemInstruction, safetySettings: [ { 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, }); console.log( `Loaded ${savedHistory.length} history messages for role ${roleId}` ); } else { chat = this.ai.chats.create({ model: config.model, config: { temperature: config.temperature, systemInstruction: systemInstruction, safetySettings: [ { 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, }, ], }, }); console.log(`Created new chat instance for role ${roleId}`); } this.chatInstances.set(roleId, chat); } return this.chatInstances.get(roleId); } /** * 设置当前活动的角色ID * @param roleId 角色ID */ public async setCurrentRole(roleId: number): Promise { this.currentRoleId = roleId; // 预创建聊天实例 await 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} AI的回复消息,如果发生错误返回null * * @example * ```typescript * const response = await chatService.sendMessage(10001, "你好"); * console.log(response); // AI的回复 * ``` */ public async sendMessage(roleId: number, message: string): Promise { // 输入验证 if (!roleId || roleId <= 0) { ErrorHandler.Instance.handleValidationError( "roleId", "角色ID必须是正整数", roleId ); return null; } if (!message || message.trim() === "") { ErrorHandler.Instance.handleValidationError( "message", "消息内容不能为空", message ); return null; } try { const chat = await this.createOrGetChat(roleId); const response = await chat.sendMessage({ message: message.trim(), }); if (response && response.text) { console.log(`Response from role ${roleId}:`, response.text); try { // 保存用户消息 ChatHistoryManager.Instance.appendMessage(roleId, { role: "user", parts: [{ text: message }], }); // 保存AI回复 ChatHistoryManager.Instance.appendMessage(roleId, { role: "model", parts: [{ text: response.text }], }); // 上报聊天数据到服务器 let msgList: proto.cs.IChatMsg[] = []; let userData = { isAi: false, msg: message, msgId: new Date().getTime().toString(), }; msgList.push(userData); let aiData = { isAi: true, msg: response.text, msgId: (new Date().getTime() + 1).toString(), }; msgList.push(aiData); this.reqChatMsg(roleId, msgList); // 异步更新情绪状态(非阻塞) EmotionAIService.Instance.updateEmotionFromChat( roleId, message, response.text ).catch((emotionError) => { console.warn( `Failed to update emotion for role ${roleId}:`, emotionError ); // 情绪更新失败不影响聊天功能 }); } catch (storageError) { ErrorHandler.Instance.handleError( storageError as Error, ErrorType.STORAGE_ERROR, { roleId, message: message.substring(0, 100) }, false ); // 即使存储失败,也返回AI回复 } return response.text; } else { TipsPanel.show(response.promptFeedback.blockReason); const warningMsg = `AI返回了空响应 (角色ID: ${roleId})`; ErrorHandler.Instance.handleError( new Error(warningMsg), ErrorType.API_ERROR, { roleId, chat, response }, true ); return null; } } catch (error) { ErrorHandler.Instance.handleApiError(error, "sendMessage", { roleId, message: message, }); TipsPanel.show("api调用失败,请使用vpn并重新启动"); return null; } } /** * 清除指定角色的聊天历史 * @param roleId 角色ID */ public clearChatHistory(roleId: number): void { if (this.chatInstances.has(roleId)) { this.chatInstances.delete(roleId); } // 清除情绪聊天历史 EmotionAIService.Instance.clearEmotionHistory(roleId); // 清除本地存储的历史 ChatHistoryManager.Instance.clearHistory(roleId); console.log(`Cleared chat history for role ${roleId}`); } /** * 清除所有聊天历史 */ public clearAllChatHistory(): void { this.chatInstances.clear(); // 清除所有情绪聊天历史 EmotionAIService.Instance.clearAllEmotionHistory(); // 清除本地存储的所有历史 ChatHistoryManager.Instance.clearAllHistory(); console.log("Cleared all chat histories"); } /** * 获取当前活跃的聊天实例数量 */ public getActiveChatCount(): number { return this.chatInstances.size; } /** * 兼容旧接口的Post方法 * @deprecated 请使用sendMessage方法,此方法将在下个版本中移除 */ public async Post(data: GPTRequest): Promise { console.warn("Post方法已废弃,请使用sendMessage方法"); const roleId = this.currentRoleId || 10001; // 默认使用第一个角色 const message = data.messages[0]?.content || ""; return this.sendMessage(roleId, message); } /** * 上报聊天数据到服务器 */ async reqChatMsg(girlId: number, msgList: proto.cs.IChatMsg[]) { // 请求数据 const reqData = { girlId, msgs: msgList, }; 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(DataId.Girl); const category = girlData.getGrilCategoryById(girlId); girlData.setChatTotalCount( category.toString(), girlId, resData.chatTotalCount ); girlData.setChatRemainCount( category.toString(), girlId, resData.chatRemainCount ); } } /** * 获取聊天数据 */ async reqGetChatMsg(girlId: number, page: number, limit: number) { // 请求数据 const reqData = { GirlId: girlId, page, limit, }; console.log("获取聊天数据的请求数据:", reqData); let res = await ChatService.I.reqGetChatMsg(reqData); console.log("获取聊天数据的响应数据:", res); if (res && res.code === proto.cs.EnmRetCode.SUCCESS) { const resData = res.data; // 保存数据 const girlData = DataManager.I.getDataById(DataId.Girl); const category = girlData.getGrilCategoryById(girlId); girlData.setChatRecord(category.toString(), girlId, resData.msgs); girlData.setChatTotalCount( category.toString(), girlId, resData.chatTotalCount ); girlData.setChatRemainCount( category.toString(), girlId, resData.chatRemainCount ); } } } // 请求数据类型定义 export interface GPTRequest { model: string; messages: { role: string; content: string }[]; temperature: number; id: string; } // 兼容旧名称(已废弃,建议使用 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; }; finish_reason: string; index: number; }[]; }