421 lines
13 KiB
JavaScript
421 lines
13 KiB
JavaScript
// const aiService = require('./aiService'); // 二期规划:AI 服务暂时禁用
|
||
const logs = require('../logProxy');
|
||
|
||
/**
|
||
* 智能聊天管理模块
|
||
* 负责聊天内容生成、发送策略和效果监控
|
||
*/
|
||
class ChatManager {
|
||
constructor() {
|
||
this.chatHistory = new Map(); // 聊天历史记录
|
||
this.chatStrategies = new Map(); // 聊天策略配置
|
||
this.effectStats = new Map(); // 聊天效果统计
|
||
this.initDefaultStrategies();
|
||
}
|
||
|
||
/**
|
||
* 初始化默认聊天策略
|
||
*/
|
||
initDefaultStrategies() {
|
||
// 初次打招呼策略
|
||
this.chatStrategies.set('greeting', {
|
||
name: '初次打招呼',
|
||
description: '向HR发送初次打招呼消息',
|
||
template: 'greeting',
|
||
timing: 'immediate',
|
||
retryCount: 1,
|
||
retryInterval: 300000 // 5分钟
|
||
});
|
||
|
||
// 面试邀约策略
|
||
this.chatStrategies.set('interview', {
|
||
name: '面试邀约',
|
||
description: '发送面试邀约消息',
|
||
template: 'interview',
|
||
timing: 'after_greeting',
|
||
retryCount: 2,
|
||
retryInterval: 600000 // 10分钟
|
||
});
|
||
|
||
// 跟进沟通策略
|
||
this.chatStrategies.set('followup', {
|
||
name: '跟进沟通',
|
||
description: '跟进之前的沟通',
|
||
template: 'followup',
|
||
timing: 'after_interview',
|
||
retryCount: 1,
|
||
retryInterval: 86400000 // 24小时
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 生成聊天内容
|
||
* @param {string} sn_code - 设备SN码
|
||
* @param {object} jobInfo - 岗位信息
|
||
* @param {object} resumeInfo - 简历信息
|
||
* @param {string} chatType - 聊天类型
|
||
* @param {object} context - 聊天上下文
|
||
* @returns {Promise<object>} 聊天内容
|
||
*/
|
||
async generateChatContent(sn_code, jobInfo, resumeInfo, chatType = 'greeting', context = {}) {
|
||
console.log(`[聊天管理] 开始生成设备 ${sn_code} 的聊天内容,类型: ${chatType}`);
|
||
|
||
// 获取聊天策略
|
||
const strategy = this.chatStrategies.get(chatType);
|
||
if (!strategy) {
|
||
throw new Error(`未找到聊天类型 ${chatType} 的策略配置`);
|
||
}
|
||
|
||
// 二期规划:AI 生成聊天内容暂时禁用,使用默认模板
|
||
// const chatContent = await aiService.generateChatContent(jobInfo, resumeInfo, chatType);
|
||
// if (!chatContent.success) {
|
||
// console.error(`[聊天管理] AI生成聊天内容失败:`, chatContent.error);
|
||
// throw new Error(chatContent.error);
|
||
// }
|
||
|
||
console.log(`[聊天管理] AI生成已禁用(二期规划),使用默认聊天模板`);
|
||
const chatContent = this.generateDefaultChatContent(jobInfo, resumeInfo, chatType);
|
||
|
||
const result = {
|
||
sn_code: sn_code,
|
||
jobInfo: jobInfo,
|
||
chatType: chatType,
|
||
strategy: strategy,
|
||
content: chatContent.content,
|
||
context: context,
|
||
timestamp: Date.now()
|
||
};
|
||
|
||
// 记录聊天历史
|
||
this.recordChatHistory(sn_code, result);
|
||
|
||
console.log(`[聊天管理] 聊天内容生成成功:`, result);
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 发送聊天消息
|
||
* @param {string} sn_code - 设备SN码
|
||
* @param {object} mqttClient - MQTT客户端
|
||
* @param {object} chatData - 聊天数据
|
||
* @returns {Promise<object>} 发送结果
|
||
*/
|
||
async sendChatMessage(sn_code, mqttClient, chatData) {
|
||
console.log(`[聊天管理] 开始发送聊天消息到设备 ${sn_code}`);
|
||
|
||
// 构建发送指令
|
||
const sendData = {
|
||
platform: 'boss',
|
||
action: 'send_chat_message',
|
||
data: {
|
||
jobId: chatData.jobInfo.jobId,
|
||
companyId: chatData.jobInfo.companyId,
|
||
message: chatData.content,
|
||
chatType: chatData.chatType
|
||
}
|
||
};
|
||
|
||
// 发送MQTT指令
|
||
const response = await mqttClient.publishAndWait(sn_code, sendData);
|
||
|
||
if (!response || response.code !== 200) {
|
||
// 更新聊天状态
|
||
this.updateChatStatus(sn_code, chatData, 'failed', response);
|
||
|
||
console.error(`[聊天管理] 聊天消息发送失败:`, response);
|
||
throw new Error(response?.message || '聊天消息发送失败');
|
||
}
|
||
|
||
// 更新聊天状态
|
||
this.updateChatStatus(sn_code, chatData, 'sent', response);
|
||
|
||
// 记录效果统计
|
||
this.recordChatEffect(sn_code, chatData, 'sent');
|
||
|
||
console.log(`[聊天管理] 聊天消息发送成功:`, response);
|
||
return response;
|
||
}
|
||
|
||
/**
|
||
* 生成面试邀约
|
||
* @param {string} sn_code - 设备SN码
|
||
* @param {object} jobInfo - 岗位信息
|
||
* @param {object} chatHistory - 聊天历史
|
||
* @returns {Promise<object>} 面试邀约内容
|
||
*/
|
||
async generateInterviewInvitation(sn_code, jobInfo, chatHistory) {
|
||
console.log(`[聊天管理] 开始生成设备 ${sn_code} 的面试邀约`);
|
||
console.log(`[聊天管理] AI生成已禁用(二期规划),使用默认模板`);
|
||
|
||
// 二期规划:AI 生成面试邀约暂时禁用,使用默认模板
|
||
// const invitation = await aiService.generateInterviewInvitation(jobInfo, chatHistory);
|
||
// if (!invitation.success) {
|
||
// console.error(`[聊天管理] AI生成面试邀约失败:`, invitation.error);
|
||
// throw new Error(invitation.error);
|
||
// }
|
||
|
||
const invitation = this.generateDefaultInterviewInvitation(jobInfo);
|
||
|
||
const result = {
|
||
sn_code: sn_code,
|
||
jobInfo: jobInfo,
|
||
chatType: 'interview',
|
||
content: invitation.content,
|
||
timestamp: Date.now()
|
||
};
|
||
|
||
// 记录聊天历史
|
||
this.recordChatHistory(sn_code, result);
|
||
|
||
console.log(`[聊天管理] 面试邀约生成成功:`, result);
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 记录聊天历史
|
||
* @param {string} sn_code - 设备SN码
|
||
* @param {object} chatData - 聊天数据
|
||
*/
|
||
recordChatHistory(sn_code, chatData) {
|
||
if (!this.chatHistory.has(sn_code)) {
|
||
this.chatHistory.set(sn_code, []);
|
||
}
|
||
|
||
const history = this.chatHistory.get(sn_code);
|
||
history.push({
|
||
...chatData,
|
||
id: Date.now() + Math.random(),
|
||
status: 'generated'
|
||
});
|
||
|
||
// 限制历史记录数量
|
||
if (history.length > 100) {
|
||
history.splice(0, history.length - 100);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新聊天状态
|
||
* @param {string} sn_code - 设备SN码
|
||
* @param {object} chatData - 聊天数据
|
||
* @param {string} status - 新状态
|
||
* @param {object} response - 响应数据
|
||
*/
|
||
updateChatStatus(sn_code, chatData, status, response = {}) {
|
||
if (!this.chatHistory.has(sn_code)) {
|
||
return;
|
||
}
|
||
|
||
const history = this.chatHistory.get(sn_code);
|
||
const chatRecord = history.find(record =>
|
||
record.timestamp === chatData.timestamp &&
|
||
record.chatType === chatData.chatType
|
||
);
|
||
|
||
if (chatRecord) {
|
||
chatRecord.status = status;
|
||
chatRecord.response = response;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 记录聊天效果
|
||
* @param {string} sn_code - 设备SN码
|
||
* @param {object} chatData - 聊天数据
|
||
* @param {string} action - 动作类型
|
||
*/
|
||
recordChatEffect(sn_code, chatData, action) {
|
||
if (!this.effectStats.has(sn_code)) {
|
||
this.effectStats.set(sn_code, {
|
||
totalSent: 0,
|
||
totalReplied: 0,
|
||
totalInterview: 0,
|
||
replyRate: 0,
|
||
interviewRate: 0,
|
||
lastUpdate: Date.now()
|
||
});
|
||
}
|
||
|
||
const stats = this.effectStats.get(sn_code);
|
||
|
||
if (action === 'sent') {
|
||
stats.totalSent++;
|
||
} else if (action === 'replied') {
|
||
stats.totalReplied++;
|
||
} else if (action === 'interview') {
|
||
stats.totalInterview++;
|
||
}
|
||
|
||
// 计算比率
|
||
if (stats.totalSent > 0) {
|
||
stats.replyRate = (stats.totalReplied / stats.totalSent * 100).toFixed(2);
|
||
stats.interviewRate = (stats.totalInterview / stats.totalSent * 100).toFixed(2);
|
||
}
|
||
|
||
stats.lastUpdate = Date.now();
|
||
}
|
||
|
||
/**
|
||
* 获取聊天历史
|
||
* @param {string} sn_code - 设备SN码
|
||
* @param {object} filters - 过滤条件
|
||
* @returns {Array} 聊天历史
|
||
*/
|
||
getChatHistory(sn_code, filters = {}) {
|
||
if (!this.chatHistory.has(sn_code)) {
|
||
return [];
|
||
}
|
||
|
||
let history = this.chatHistory.get(sn_code);
|
||
|
||
// 应用过滤条件
|
||
if (filters.chatType) {
|
||
history = history.filter(record => record.chatType === filters.chatType);
|
||
}
|
||
|
||
if (filters.status) {
|
||
history = history.filter(record => record.status === filters.status);
|
||
}
|
||
|
||
if (filters.startTime) {
|
||
history = history.filter(record => record.timestamp >= filters.startTime);
|
||
}
|
||
|
||
if (filters.endTime) {
|
||
history = history.filter(record => record.timestamp <= filters.endTime);
|
||
}
|
||
|
||
// 按时间倒序排列
|
||
return history.sort((a, b) => b.timestamp - a.timestamp);
|
||
}
|
||
|
||
/**
|
||
* 获取聊天效果统计
|
||
* @param {string} sn_code - 设备SN码
|
||
* @returns {object} 效果统计
|
||
*/
|
||
getChatEffectStats(sn_code) {
|
||
if (!this.effectStats.has(sn_code)) {
|
||
return {
|
||
totalSent: 0,
|
||
totalReplied: 0,
|
||
totalInterview: 0,
|
||
replyRate: 0,
|
||
interviewRate: 0,
|
||
lastUpdate: Date.now()
|
||
};
|
||
}
|
||
|
||
return this.effectStats.get(sn_code);
|
||
}
|
||
|
||
/**
|
||
* 设置聊天策略
|
||
* @param {string} chatType - 聊天类型
|
||
* @param {object} strategy - 策略配置
|
||
*/
|
||
setChatStrategy(chatType, strategy) {
|
||
this.chatStrategies.set(chatType, {
|
||
...this.chatStrategies.get(chatType),
|
||
...strategy
|
||
});
|
||
|
||
console.log(`[聊天管理] 更新聊天策略 ${chatType}:`, strategy);
|
||
}
|
||
|
||
/**
|
||
* 清理过期数据
|
||
*/
|
||
cleanup() {
|
||
const now = Date.now();
|
||
const expireTime = 30 * 24 * 3600000; // 30天
|
||
|
||
// 清理过期的聊天历史
|
||
for (const [sn_code, history] of this.chatHistory.entries()) {
|
||
const filteredHistory = history.filter(record =>
|
||
now - record.timestamp < expireTime
|
||
);
|
||
|
||
if (filteredHistory.length === 0) {
|
||
this.chatHistory.delete(sn_code);
|
||
} else {
|
||
this.chatHistory.set(sn_code, filteredHistory);
|
||
}
|
||
}
|
||
|
||
// 清理过期的效果统计
|
||
for (const [sn_code, stats] of this.effectStats.entries()) {
|
||
if (now - stats.lastUpdate > expireTime) {
|
||
this.effectStats.delete(sn_code);
|
||
}
|
||
}
|
||
|
||
console.log(`[聊天管理] 数据清理完成`);
|
||
}
|
||
|
||
/**
|
||
* 生成默认聊天内容(替代 AI 生成)
|
||
* @param {object} jobInfo - 岗位信息
|
||
* @param {object} resumeInfo - 简历信息
|
||
* @param {string} chatType - 聊天类型
|
||
* @returns {object} 聊天内容
|
||
*/
|
||
generateDefaultChatContent(jobInfo, resumeInfo, chatType) {
|
||
const templates = {
|
||
greeting: '您好,我对这个岗位很感兴趣,希望能进一步了解。',
|
||
interview: '感谢您的回复,我很期待与您进一步沟通。',
|
||
followup: '您好,想了解一下这个岗位的最新进展。'
|
||
};
|
||
|
||
const content = templates[chatType] || templates.greeting;
|
||
|
||
return {
|
||
success: true,
|
||
content: content,
|
||
chatType: chatType
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 生成默认面试邀约(替代 AI 生成)
|
||
* @param {object} jobInfo - 岗位信息
|
||
* @returns {object} 面试邀约内容
|
||
*/
|
||
generateDefaultInterviewInvitation(jobInfo) {
|
||
return {
|
||
success: true,
|
||
content: '感谢您的邀请,我很期待与您面谈。请问方便的时间是什么时候?',
|
||
jobTitle: jobInfo.jobTitle || '该岗位',
|
||
companyName: jobInfo.companyName || '贵公司'
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取聊天列表
|
||
* @param {string} sn_code - 设备SN码
|
||
* @param {object} mqttClient - MQTT客户端
|
||
* @param {object} params - 参数
|
||
* @returns {Promise<object>} 聊天列表
|
||
*/
|
||
async get_chat_list(sn_code, mqttClient, params = {}) {
|
||
const { platform = 'boss', pageCount = 3 } = params;
|
||
console.log(`[聊天管理] 开始获取设备 ${sn_code} 的聊天列表`);
|
||
|
||
// 通过MQTT指令获取聊天列表
|
||
const response = await mqttClient.publishAndWait(sn_code, {
|
||
platform,
|
||
action: "get_chat_list",
|
||
data: { pageCount }
|
||
});
|
||
|
||
if (!response || response.code !== 200) {
|
||
console.error(`[聊天管理] 获取聊天列表失败:`, response);
|
||
throw new Error('获取聊天列表失败');
|
||
}
|
||
|
||
console.log(`[聊天管理] 成功获取聊天列表`);
|
||
return response.data;
|
||
}
|
||
}
|
||
|
||
module.exports = new ChatManager();
|