1
This commit is contained in:
@@ -57,41 +57,37 @@ module.exports = {
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// 确保目录存在
|
||||
if (!fs.existsSync(jsRootDir)) {
|
||||
fs.mkdirSync(jsRootDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 3. 如果文件已存在,直接返回本地文件(文件内容已是替换后的,无需再次注入)
|
||||
if (fs.existsSync(localFilePath)) {
|
||||
ctx.type = 'application/javascript; charset=utf-8';
|
||||
ctx.body = fs.createReadStream(localFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 文件不存在:从远程下载并保存(带钩子注入)
|
||||
const response = await axios.get(urlStr, {
|
||||
responseType: 'arraybuffer',
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
ctx.status = 502;
|
||||
ctx.body = { code: 502, message: '下载远程 JS 失败' };
|
||||
return;
|
||||
}
|
||||
|
||||
const patched = injectOnMessageArrivedHook(Buffer.from(response.data));
|
||||
|
||||
fs.writeFileSync(localFilePath, patched);
|
||||
|
||||
ctx.type = 'application/javascript; charset=utf-8';
|
||||
ctx.body = patched;
|
||||
} catch (error) {
|
||||
console.error('[static/boss] 处理失败:', error);
|
||||
ctx.status = 500;
|
||||
ctx.body = { code: 500, message: '静态资源代理失败', error: error.message };
|
||||
// 确保目录存在
|
||||
if (!fs.existsSync(jsRootDir)) {
|
||||
fs.mkdirSync(jsRootDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 3. 如果文件已存在,直接返回本地文件(文件内容已是替换后的,无需再次注入)
|
||||
if (fs.existsSync(localFilePath)) {
|
||||
ctx.type = 'application/javascript; charset=utf-8';
|
||||
ctx.body = fs.createReadStream(localFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 文件不存在:从远程下载并保存(带钩子注入)
|
||||
const response = await axios.get(urlStr, {
|
||||
responseType: 'arraybuffer',
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
ctx.status = 502;
|
||||
ctx.body = { code: 502, message: '下载远程 JS 失败' };
|
||||
return;
|
||||
}
|
||||
|
||||
const patched = injectOnMessageArrivedHook(Buffer.from(response.data));
|
||||
|
||||
fs.writeFileSync(localFilePath, patched);
|
||||
|
||||
ctx.type = 'application/javascript; charset=utf-8';
|
||||
ctx.body = patched;
|
||||
|
||||
},
|
||||
}
|
||||
@@ -135,12 +135,16 @@ class ChatManager {
|
||||
*/
|
||||
async get_chat_detail(sn_code, mqttClient, params = {}) {
|
||||
const { platform = 'boss', ...rest } = params;
|
||||
console.log(`[聊天管理] 开始获取设备 ${sn_code} 的沟通详情`);
|
||||
const friendId = rest.friendId != null ? Number(rest.friendId) : NaN;
|
||||
if (!Number.isFinite(friendId) || friendId <= 0) {
|
||||
throw new Error('缺少必要参数:friendId(需为有效正数)');
|
||||
}
|
||||
console.log(`[聊天管理] 开始获取设备 ${sn_code} 的沟通详情`, { friendId });
|
||||
|
||||
const response = await mqttClient.publishAndWait(sn_code, {
|
||||
platform,
|
||||
action: 'get_chat_detail',
|
||||
data: rest
|
||||
data: { ...rest, friendId }
|
||||
});
|
||||
|
||||
const ok = response && (response.code === 200 || response.code === 0);
|
||||
@@ -285,6 +289,16 @@ class ChatManager {
|
||||
|
||||
const jobInfo = detail.job || {};
|
||||
|
||||
// 用 messages 判定整条对话最后一条是否来自 HR,只有最后一条是 HR 才需要回复
|
||||
const last_in_messages = messages[messages.length - 1];
|
||||
const last_from_uid = last_in_messages && last_in_messages.from ? this._normalizeUid(last_in_messages.from.uid) : null;
|
||||
const hr_uid_str = this._normalizeUid(hr_uid);
|
||||
const is_last_from_hr = hr_uid_str && last_from_uid === hr_uid_str && !this._isSystemMessage(last_in_messages);
|
||||
if (!is_last_from_hr) {
|
||||
this._saveReplyIntentLog(options, '', jobInfo, '', '', false, '最后一条消息不是HR发的', null);
|
||||
return { replied: false, reason: '最后一条消息不是HR发的' };
|
||||
}
|
||||
|
||||
const hrList = this._filterHrReplyableMessages(messages, geek_uid);
|
||||
if (hrList.length === 0) {
|
||||
this._saveReplyIntentLog(options, '', jobInfo, '', '', false, '无HR可回复消息(已过滤系统与己方)', null);
|
||||
@@ -292,11 +306,6 @@ class ChatManager {
|
||||
}
|
||||
|
||||
const last = hrList[hrList.length - 1];
|
||||
if (!last.from || last.from.uid !== hr_uid) {
|
||||
this._saveReplyIntentLog(options, '', jobInfo, '', '', false, '最后一条可回复消息不是HR', null);
|
||||
return { replied: false, reason: '最后一条可回复消息不是HR' };
|
||||
}
|
||||
|
||||
const body = last.body || {};
|
||||
const hr_message_text =
|
||||
(typeof body.text === 'string' && body.text) ||
|
||||
|
||||
@@ -337,12 +337,20 @@ class MqttDispatcher {
|
||||
? firstMsg.body.text
|
||||
: null;
|
||||
|
||||
// 兼容 uid 为数字或 { low, high } 两种格式
|
||||
const toUidStr = (uid) => {
|
||||
if (uid == null) return null;
|
||||
if (typeof uid === 'number' && !Number.isNaN(uid)) return String(uid);
|
||||
if (typeof uid === 'object' && typeof uid.low === 'number') return String(uid.low);
|
||||
return null;
|
||||
};
|
||||
|
||||
const normalized = {
|
||||
sn_code,
|
||||
type: payload.type || null,
|
||||
version: payload.version || null,
|
||||
from_uid: fromUidObj && typeof fromUidObj.low === 'number' ? String(fromUidObj.low) : null,
|
||||
to_uid: toUidObj && typeof toUidObj.low === 'number' ? String(toUidObj.low) : null,
|
||||
from_uid: toUidStr(fromUidObj),
|
||||
to_uid: toUidStr(toUidObj),
|
||||
text,
|
||||
raw: payload
|
||||
};
|
||||
@@ -406,12 +414,16 @@ class MqttDispatcher {
|
||||
|
||||
// 调用现有 AI 自动回复流程(基于 get_chat_detail + getReplyContentFromDetail)
|
||||
try {
|
||||
if (normalized.from_uid && this.mqttClient) {
|
||||
const friendIdNum = normalized.from_uid != null ? Number(normalized.from_uid) : 0;
|
||||
const hasValidFriendId = friendIdNum > 0 && Number.isFinite(friendIdNum);
|
||||
if (hasValidFriendId && this.mqttClient) {
|
||||
const result = await chatManager.auto_reply_with_ai(sn_code, this.mqttClient, {
|
||||
friendId: Number(normalized.from_uid),
|
||||
friendId: friendIdNum,
|
||||
platform: 'boss'
|
||||
});
|
||||
console.log('[MQTT Boss 消息] AI 自动回复结果:', result);
|
||||
} else if (!hasValidFriendId && normalized.from_uid != null) {
|
||||
console.warn('[MQTT Boss 消息] 跳过 AI 回复:friendId 无效或为 0', { from_uid: normalized.from_uid, friendIdNum });
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[MQTT Boss 消息] AI 自动回复失败:', e.message);
|
||||
|
||||
@@ -284,15 +284,15 @@ action 仅允许以下五种之一:
|
||||
|
||||
规则:
|
||||
1. 若 HR 明确表示暂不匹配、感谢关注、有合适机会再沟通、与岗位不够匹配、婉拒、不考虑、不招了、已招到 等 → action 为 no_reply,reply_content 留空。
|
||||
2. 若 HR 明确要求发简历/投递/看简历 → action 为 send_resume,reply_content 可为简短附言或空。
|
||||
3. 若 HR 明确要求加微信/留微信/发微信 → action 为 exchange_wechat,reply_content 可为简短附言或空。
|
||||
4. 若 HR 明确要求留电话/发电话/联系方式 → action 为 exchange_phone,reply_content 可为简短附言或空。
|
||||
5. 若仅为普通聊天、打招呼 → action 为 text,reply_content 为一句自然回复(50字以内)。
|
||||
6. reply_content 必须为字符串,不要换行。
|
||||
2. 若 HR 明确要求发简历/投递/看简历 → action 为 send_resume,reply_content 简短附言或空(10字内)。
|
||||
3. 若 HR 明确要求加微信/留微信/发微信 → action 为 exchange_wechat,reply_content 简短附言或空(10字内)。
|
||||
4. 若 HR 明确要求留电话/发电话/联系方式 → action 为 exchange_phone,reply_content 简短附言或空(10字内)。
|
||||
5. 若仅为普通聊天、打招呼 → action 为 text,reply_content 为一两句简短回复(20字以内),语气平淡、不要过于热情。
|
||||
6. reply_content 必须为字符串,不要换行;整体风格:简洁、克制、不啰嗦。
|
||||
`.trim();
|
||||
|
||||
const result = await this.callAPI(prompt, {
|
||||
systemPrompt: '你是求职沟通助手。根据 HR 消息判断动作:no_reply(不需要回复)、text(仅文字)、send_resume(发简历)、exchange_wechat(换微信)、exchange_phone(换电话)。HR 婉拒/暂不匹配/感谢关注时用 no_reply。输出 JSON:{"action":"上述五选一","reply_content":"..."}。只返回合法 JSON。',
|
||||
systemPrompt: '你是求职沟通助手。根据 HR 消息判断动作:no_reply、text、send_resume、exchange_wechat、exchange_phone。HR 婉拒/暂不匹配时用 no_reply。回复内容务必简短(20字以内)、语气平淡、不要过于热情。输出 JSON:{"action":"五选一","reply_content":"..."}。只返回合法 JSON。',
|
||||
temperature: 0.3,
|
||||
maxTokens: 500,
|
||||
business_type: 'chat_reply_intent',
|
||||
@@ -308,7 +308,7 @@ action 仅允许以下五种之一:
|
||||
const reply_content = typeof parsed.reply_content === 'string' ? parsed.reply_content.trim() : '';
|
||||
return { action, reply_content };
|
||||
} catch (e) {
|
||||
return { action: 'text', reply_content: raw || '收到,谢谢您。' };
|
||||
return { action: 'text', reply_content: raw || '好的' };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user