Chat相关表现&逻辑分离,新增ChatController
新增情绪机器人逻辑, 新增情绪切换视频逻辑
This commit is contained in:
@@ -0,0 +1,418 @@
|
||||
// 首先加载 polyfills 以确保兼容性
|
||||
import "../utils/polyfills";
|
||||
import { GoogleGenAI } from "@google/genai";
|
||||
import { RoleConfigLoader } from "./RoleConfigLoader";
|
||||
import { ChatHistoryManager } from "../manager/ChatHistoryManager";
|
||||
import { ApiConfig } from "./ApiConfigLoader";
|
||||
import { ErrorHandler, ErrorType } from "../utils/ErrorHandler";
|
||||
import { VideoEmotion } from "../../schema/schema";
|
||||
import ConfigManager from "../manager/ConfigManager";
|
||||
import LanguageUtils, { LanguageType } from "../../Main/Common/LanguageUtils";
|
||||
import Utils from "../../Main/Common/Utils";
|
||||
import { InnerMsgCode } from "../../Main/Config/InnerMsgCode";
|
||||
|
||||
/**
|
||||
* AI情绪分析服务类
|
||||
*
|
||||
* 基于Google Gemini API实现的情绪分析系统,支持:
|
||||
* - 基于聊天历史的情绪状态分析
|
||||
* - 多角色独立的情绪聊天实例管理
|
||||
* - 返回标准的VideoEmotion枚举值
|
||||
* - 自动初始化历史情绪分析
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const emotionService = EmotionAIService.Instance;
|
||||
* const emotion = await emotionService.analyzeEmotionalState(10001);
|
||||
* console.log(VideoEmotion[emotion]); // "calm_down"
|
||||
* ```
|
||||
*
|
||||
* @author AI Chat System
|
||||
* @version 1.0.0
|
||||
*/
|
||||
export class EmotionAIService {
|
||||
private static _instance: EmotionAIService;
|
||||
private emotionAI: GoogleGenAI;
|
||||
private emotionChatInstances: Map<number, any> = new Map();
|
||||
private currentEmotions: Map<number, VideoEmotion> = new Map();
|
||||
|
||||
private constructor() {
|
||||
const emotionConfig = ApiConfig.Instance.getEmotionAIConfig();
|
||||
|
||||
try {
|
||||
this.emotionAI = new GoogleGenAI({ apiKey: emotionConfig.apiKey });
|
||||
} catch (error) {
|
||||
ErrorHandler.Instance.handleError(
|
||||
error as Error,
|
||||
ErrorType.API_ERROR,
|
||||
{ config: { ...emotionConfig, apiKey: "***" } }, // 隐藏API密钥
|
||||
true
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取EmotionAIService的单例实例
|
||||
*
|
||||
* @returns {EmotionAIService} 情绪分析服务实例
|
||||
* @static
|
||||
*/
|
||||
public static get Instance(): EmotionAIService {
|
||||
if (!this._instance) {
|
||||
this._instance = new EmotionAIService();
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或获取指定角色的情绪聊天实例
|
||||
* @param roleId 角色ID
|
||||
*/
|
||||
private createOrGetEmotionChat(roleId: number): any {
|
||||
if (!this.emotionChatInstances.has(roleId)) {
|
||||
const systemInstruction = RoleConfigLoader.getEmotionInstruction(roleId);
|
||||
const config = ApiConfig.Instance.getEmotionAIConfig();
|
||||
|
||||
const chat = this.emotionAI.chats.create({
|
||||
model: config.model,
|
||||
config: {
|
||||
temperature: config.temperature,
|
||||
systemInstruction: systemInstruction,
|
||||
},
|
||||
});
|
||||
|
||||
this.emotionChatInstances.set(roleId, chat);
|
||||
console.log(`Created new emotion chat instance for role ${roleId}`);
|
||||
|
||||
// 异步分析历史情绪状态
|
||||
this.initializeEmotionAnalysis(roleId);
|
||||
}
|
||||
return this.emotionChatInstances.get(roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化情绪分析(检查是否有历史记录并进行分析)
|
||||
* @param roleId 角色ID
|
||||
*/
|
||||
private async initializeEmotionAnalysis(roleId: number): Promise<void> {
|
||||
try {
|
||||
// 检查是否有聊天历史
|
||||
const messageCount = ChatHistoryManager.Instance.getMessageCount(roleId);
|
||||
|
||||
let emotionalState: VideoEmotion;
|
||||
|
||||
if (messageCount > 0) {
|
||||
console.log(
|
||||
`Analyzing emotional state for role ${roleId} based on ${messageCount} messages`
|
||||
);
|
||||
|
||||
// 分析历史情绪状态
|
||||
emotionalState = await this.analyzeEmotionalState(roleId);
|
||||
|
||||
console.log(
|
||||
`Initial emotional state for role ${roleId}: ${VideoEmotion[emotionalState]}`
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`No history found for role ${roleId}, using default emotion: calm_down`
|
||||
);
|
||||
emotionalState = VideoEmotion.calm_down;
|
||||
}
|
||||
|
||||
// 发送情绪初始化完成事件
|
||||
Utils.sendInnerMsg(InnerMsgCode.Chat_EmotionInitialized, {
|
||||
roleId: roleId,
|
||||
emotion: emotionalState
|
||||
});
|
||||
|
||||
console.log(`Emotion initialization completed for role ${roleId}: ${VideoEmotion[emotionalState]}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to initialize emotion analysis for role ${roleId}:`,
|
||||
error
|
||||
);
|
||||
|
||||
// 即使发生错误,也发送默认情绪状态
|
||||
Utils.sendInnerMsg(InnerMsgCode.Chat_EmotionInitialized, {
|
||||
roleId: roleId,
|
||||
emotion: VideoEmotion.calm_down
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化聊天历史用于情绪分析
|
||||
* @param messages 聊天消息数组
|
||||
* @returns 格式化后的分析提示
|
||||
*/
|
||||
private formatHistoryForEmotionAnalysis(messages: any[]): string {
|
||||
if (messages.length === 0) {
|
||||
return "没有聊天历史,请返回默认情绪状态:calm_down";
|
||||
}
|
||||
|
||||
let conversationText =
|
||||
"请分析以下对话的情绪状态,返回对应的VideoEmotion枚举值:\n\n";
|
||||
|
||||
messages.forEach((message, index) => {
|
||||
const speaker = message.role === "user" ? "用户" : "AI";
|
||||
const text = message.parts[0]?.text || "";
|
||||
conversationText += `${speaker}: ${text}\n`;
|
||||
});
|
||||
|
||||
conversationText +=
|
||||
"\n请返回以下枚举值之一:calm_down, arousal, desire, passion, orgasm";
|
||||
|
||||
return conversationText;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定角色的情绪AI发送消息并获取分析回复,返回VideoEmotion枚举值
|
||||
* @param roleId 角色ID
|
||||
* @param message 用户发送的消息内容
|
||||
* @returns Promise<VideoEmotion> 情绪AI的分析结果,如果发生错误返回默认值calm_down
|
||||
*/
|
||||
public async sendEmotionMessage(
|
||||
roleId: number,
|
||||
message: string
|
||||
): Promise<VideoEmotion> {
|
||||
// 输入验证
|
||||
if (!roleId || roleId <= 0) {
|
||||
ErrorHandler.Instance.handleValidationError(
|
||||
"roleId",
|
||||
"角色ID必须是正整数",
|
||||
roleId
|
||||
);
|
||||
return VideoEmotion.calm_down;
|
||||
}
|
||||
|
||||
if (!message || message.trim() === "") {
|
||||
ErrorHandler.Instance.handleValidationError(
|
||||
"message",
|
||||
"消息内容不能为空",
|
||||
message
|
||||
);
|
||||
return VideoEmotion.calm_down;
|
||||
}
|
||||
|
||||
try {
|
||||
const emotionChat = this.createOrGetEmotionChat(roleId);
|
||||
const response = await emotionChat.sendMessage({
|
||||
message: message.trim(),
|
||||
});
|
||||
|
||||
if (response && response.text) {
|
||||
console.log(`Emotion analysis from role ${roleId}:`, response.text);
|
||||
return this.parseEmotionResponse(response.text);
|
||||
} else {
|
||||
const warningMsg = `情绪AI返回了空响应 (角色ID: ${roleId})`;
|
||||
ErrorHandler.Instance.handleError(
|
||||
new Error(warningMsg),
|
||||
ErrorType.API_ERROR,
|
||||
{ roleId, message, response },
|
||||
true
|
||||
);
|
||||
return VideoEmotion.calm_down;
|
||||
}
|
||||
} catch (error) {
|
||||
ErrorHandler.Instance.handleApiError(error, "sendEmotionMessage", {
|
||||
roleId,
|
||||
message: message.substring(0, 100) + "...",
|
||||
});
|
||||
return VideoEmotion.calm_down;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析指定角色的情绪状态(基于最近10条聊天记录)
|
||||
* @param roleId 角色ID
|
||||
* @returns Promise<VideoEmotion> 情绪分析结果
|
||||
*/
|
||||
public async analyzeEmotionalState(roleId: number): Promise<VideoEmotion> {
|
||||
try {
|
||||
// 获取最近10条消息
|
||||
const recentMessages = ChatHistoryManager.Instance.getRecentMessages(
|
||||
roleId,
|
||||
10
|
||||
);
|
||||
|
||||
// 格式化历史记录用于分析
|
||||
const analysisPrompt =
|
||||
this.formatHistoryForEmotionAnalysis(recentMessages);
|
||||
|
||||
// 发送给情绪AI进行分析
|
||||
return await this.sendEmotionMessage(roleId, analysisPrompt);
|
||||
} catch (error) {
|
||||
ErrorHandler.Instance.handleError(
|
||||
error as Error,
|
||||
ErrorType.API_ERROR,
|
||||
{ roleId },
|
||||
false
|
||||
);
|
||||
return VideoEmotion.calm_down;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析情绪AI返回的文本为VideoEmotion枚举值
|
||||
* @param responseText AI返回的文本
|
||||
* @returns VideoEmotion枚举值
|
||||
*/
|
||||
private parseEmotionResponse(responseText: string): VideoEmotion {
|
||||
const text = responseText.toLowerCase().trim();
|
||||
|
||||
if (text.includes("arousal")) {
|
||||
return VideoEmotion.arousal;
|
||||
} else if (text.includes("desire")) {
|
||||
return VideoEmotion.desire;
|
||||
} else if (text.includes("passion")) {
|
||||
return VideoEmotion.passion;
|
||||
} else if (text.includes("orgasm")) {
|
||||
return VideoEmotion.orgasm;
|
||||
} else {
|
||||
return VideoEmotion.calm_down; // 默认值
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保指定角色的情绪聊天实例已创建
|
||||
* @param roleId 角色ID
|
||||
*/
|
||||
public ensureEmotionChatExists(roleId: number): void {
|
||||
this.createOrGetEmotionChat(roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定角色的情绪聊天历史
|
||||
* @param roleId 角色ID
|
||||
*/
|
||||
public clearEmotionHistory(roleId: number): void {
|
||||
if (this.emotionChatInstances.has(roleId)) {
|
||||
this.emotionChatInstances.delete(roleId);
|
||||
}
|
||||
console.log(`Cleared emotion chat history for role ${roleId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有情绪聊天历史
|
||||
*/
|
||||
public clearAllEmotionHistory(): void {
|
||||
this.emotionChatInstances.clear();
|
||||
console.log("Cleared all emotion chat histories");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前活跃的情绪聊天实例数量
|
||||
*/
|
||||
public getActiveEmotionChatCount(): number {
|
||||
return this.emotionChatInstances.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定角色的当前情绪状态
|
||||
* @param roleId 角色ID
|
||||
* @returns VideoEmotion 当前情绪状态
|
||||
*/
|
||||
public getCurrentEmotion(roleId: number): VideoEmotion {
|
||||
return this.currentEmotions.get(roleId) || VideoEmotion.calm_down;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置指定角色的情绪状态
|
||||
* @param roleId 角色ID
|
||||
* @param emotion 情绪状态
|
||||
*/
|
||||
private setCurrentEmotion(roleId: number, emotion: VideoEmotion): void {
|
||||
this.currentEmotions.set(roleId, emotion);
|
||||
console.log(`Updated emotion for role ${roleId}: ${VideoEmotion[emotion]}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色的英文名
|
||||
* @param roleId 角色ID
|
||||
* @returns 角色英文名,如果未找到返回"AI"
|
||||
*/
|
||||
private getRoleEnglishName(roleId: number): string {
|
||||
try {
|
||||
if (ConfigManager.tables && ConfigManager.tables.TbGirls) {
|
||||
const girlData = ConfigManager.tables.TbGirls.get(roleId);
|
||||
if (girlData && girlData.nameKey) {
|
||||
return (
|
||||
LanguageUtils.getTextByLanguage(
|
||||
girlData.nameKey,
|
||||
LanguageType.EN
|
||||
) || "AI"
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Failed to get role name for ${roleId}:`, error);
|
||||
}
|
||||
return "AI";
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据聊天对话更新情绪状态
|
||||
* @param roleId 角色ID
|
||||
* @param userMessage 用户消息
|
||||
* @param aiResponse AI回复
|
||||
* @returns Promise<VideoEmotion> 更新后的情绪状态
|
||||
*/
|
||||
public async updateEmotionFromChat(
|
||||
roleId: number,
|
||||
userMessage: string,
|
||||
aiResponse: string
|
||||
): Promise<VideoEmotion> {
|
||||
try {
|
||||
// 构建情绪分析提示,包含当前情绪上下文
|
||||
const currentEmotion = this.getCurrentEmotion(roleId);
|
||||
const analysisPrompt = this.formatChatForEmotionAnalysis(
|
||||
roleId,
|
||||
userMessage,
|
||||
aiResponse,
|
||||
currentEmotion
|
||||
);
|
||||
|
||||
// 发送给情绪AI进行分析
|
||||
const newEmotion = await this.sendEmotionMessage(roleId, analysisPrompt);
|
||||
|
||||
// 更新并保存情绪状态
|
||||
this.setCurrentEmotion(roleId, newEmotion);
|
||||
|
||||
return newEmotion;
|
||||
} catch (error) {
|
||||
ErrorHandler.Instance.handleError(
|
||||
error as Error,
|
||||
ErrorType.API_ERROR,
|
||||
{ roleId, userMessage: userMessage.substring(0, 50) },
|
||||
false
|
||||
);
|
||||
return this.getCurrentEmotion(roleId); // 返回当前情绪状态
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化聊天对话用于情绪分析
|
||||
* @param roleId 角色ID
|
||||
* @param userMessage 用户消息
|
||||
* @param aiResponse AI回复
|
||||
* @param currentEmotion 当前情绪状态
|
||||
* @returns 格式化后的分析提示
|
||||
*/
|
||||
private formatChatForEmotionAnalysis(
|
||||
roleId: number,
|
||||
userMessage: string,
|
||||
aiResponse: string,
|
||||
currentEmotion: VideoEmotion
|
||||
): string {
|
||||
const roleName = this.getRoleEnglishName(roleId);
|
||||
|
||||
let analysisText = `当前情绪状态: ${VideoEmotion[currentEmotion]}\n\n`;
|
||||
analysisText += `请分析以下最新对话的情绪变化,返回对应的VideoEmotion枚举值:\n\n`;
|
||||
analysisText += `用户: ${userMessage}\n`;
|
||||
analysisText += `${roleName}: ${aiResponse}\n\n`;
|
||||
analysisText += `请基于对话内容和当前情绪状态,返回以下枚举值之一:calm_down, arousal, desire, passion, orgasm`;
|
||||
|
||||
return analysisText;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user