ai-c/utils/proactiveMessage.js
2026-02-02 18:21:32 +08:00

271 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* AI角色主动推送消息工具模块
*
* 功能说明:
* 1. 跟进消息:发送给已聊过天但一段时间没互动的用户
* 2. 打招呼消息:发送给还没有和任何角色聊过天的新用户
*/
const api = require('./api')
const config = require('../config/index')
// 上次检查时间(避免频繁请求)
let lastCheckTime = 0
// 检查间隔(毫秒)- 默认5分钟
const CHECK_INTERVAL = 5 * 60 * 1000
/**
* 获取待接收的主动推送消息
* @returns {Promise<Array>} 消息列表
*/
async function getPendingMessages() {
try {
const token = wx.getStorageSync(config.STORAGE_KEYS.TOKEN)
if (!token) {
console.log('[proactiveMessage] 未登录,跳过获取推送消息')
return []
}
console.log('[proactiveMessage] 开始获取待推送消息...')
const res = await api.proactiveMessage.getPending()
console.log('[proactiveMessage] API响应:', JSON.stringify(res))
if (res.success && res.data) {
console.log('[proactiveMessage] 获取到消息数量:', res.data.length)
return res.data || []
}
return []
} catch (error) {
console.error('[proactiveMessage] 获取推送消息失败:', error)
return []
}
}
/**
* 检查并处理推送消息
* 在首页 onShow 或定时调用
* @param {object} options - 配置选项
* @param {boolean} options.force - 是否强制检查(忽略时间间隔)
* @param {function} options.onNewMessages - 收到新消息时的回调
* @returns {Promise<Array>} 消息列表
*/
async function checkAndShowMessages(options = {}) {
const { force = false, onNewMessages } = options
// 检查登录状态
const app = getApp()
if (!app || !app.globalData || !app.globalData.isLoggedIn) {
console.log('[proactiveMessage] 未登录,跳过检查')
return []
}
// 检查时间间隔(避免频繁请求)
const now = Date.now()
if (!force && lastCheckTime && (now - lastCheckTime < CHECK_INTERVAL)) {
console.log('[proactiveMessage] 距离上次检查不足5分钟跳过。上次:', lastCheckTime, '现在:', now)
return []
}
console.log('[proactiveMessage] 开始检查推送消息force:', force)
lastCheckTime = now
const messages = await getPendingMessages()
if (messages.length > 0) {
console.log('[proactiveMessage] 收到推送消息:', messages.length, '条')
console.log('[proactiveMessage] 消息详情:', JSON.stringify(messages))
// 更新会话列表的未读状态
messages.forEach(msg => {
updateConversationUnread(msg)
})
// 触发回调
if (typeof onNewMessages === 'function') {
onNewMessages(messages)
}
} else {
console.log('[proactiveMessage] 没有待推送消息')
}
return messages
}
/**
* 更新会话列表未读状态
* @param {object} msg - 推送消息对象
*/
function updateConversationUnread(msg) {
// 获取当前会话列表缓存
const cacheKey = 'proactive_messages_cache'
let cache = wx.getStorageSync(cacheKey) || {}
// 记录消息,避免重复处理
const msgKey = `${msg.character_id}_${msg.sent_at}`
if (cache[msgKey]) {
return // 已处理过
}
cache[msgKey] = {
character_id: msg.character_id,
character_name: msg.character_name,
content: msg.content,
message_type: msg.message_type,
sent_at: msg.sent_at,
processed_at: new Date().toISOString()
}
// 清理过期缓存保留最近24小时的记录
const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000
Object.keys(cache).forEach(key => {
const item = cache[key]
if (new Date(item.processed_at).getTime() < oneDayAgo) {
delete cache[key]
}
})
wx.setStorageSync(cacheKey, cache)
// 触发页面更新
triggerPageRefresh()
}
/**
* 触发页面刷新
* 通知当前页面刷新会话列表
*/
function triggerPageRefresh() {
const pages = getCurrentPages()
if (pages.length === 0) return
const currentPage = pages[pages.length - 1]
// 如果当前页面有刷新会话列表的方法,调用它
if (currentPage && typeof currentPage.loadConversations === 'function') {
currentPage.loadConversations()
}
// 如果当前页面有刷新未读数的方法,调用它
if (currentPage && typeof currentPage.loadUnreadCount === 'function') {
currentPage.loadUnreadCount()
}
}
/**
* 获取角色的未处理推送消息
* @param {string} characterId - 角色ID
* @returns {object|null} 消息对象
*/
function getCharacterPendingMessage(characterId) {
const cacheKey = 'proactive_messages_cache'
const cache = wx.getStorageSync(cacheKey) || {}
// 查找该角色的最新消息
let latestMsg = null
Object.values(cache).forEach(msg => {
if (msg.character_id === characterId) {
if (!latestMsg || new Date(msg.sent_at) > new Date(latestMsg.sent_at)) {
latestMsg = msg
}
}
})
return latestMsg
}
/**
* 清除角色的推送消息缓存
* 用户进入聊天后调用
* @param {string} characterId - 角色ID
*/
function clearCharacterMessages(characterId) {
const cacheKey = 'proactive_messages_cache'
let cache = wx.getStorageSync(cacheKey) || {}
// 删除该角色的所有消息
Object.keys(cache).forEach(key => {
if (cache[key].character_id === characterId) {
delete cache[key]
}
})
wx.setStorageSync(cacheKey, cache)
}
/**
* 标记角色的推送消息为已读
* 调用后端API标记已读同时清除本地缓存
* @param {string} characterId - 角色ID
*/
async function markAsRead(characterId) {
if (!characterId) {
console.log('[proactiveMessage] 没有角色ID跳过标记已读')
return
}
// 先清除本地缓存
clearCharacterMessages(characterId)
// 调用后端API标记已读
try {
const res = await api.proactiveMessage.markAsRead({
character_id: characterId
})
console.log('[proactiveMessage] 标记已读结果:', JSON.stringify(res))
} catch (err) {
console.log('[proactiveMessage] 标记已读失败:', err)
}
}
/**
* 按消息ID列表标记已读
* @param {Array<number>} messageIds - 消息ID列表
*/
async function markAsReadByIds(messageIds) {
if (!messageIds || messageIds.length === 0) {
return
}
try {
const res = await api.proactiveMessage.markAsRead({
message_ids: messageIds
})
console.log('[proactiveMessage] 按ID标记已读结果:', JSON.stringify(res))
} catch (err) {
console.log('[proactiveMessage] 按ID标记已读失败:', err)
}
}
/**
* 启动定时检查
* @param {number} interval - 检查间隔毫秒默认5分钟
* @returns {number} 定时器ID
*/
function startPeriodicCheck(interval = CHECK_INTERVAL) {
return setInterval(() => {
checkAndShowMessages()
}, interval)
}
/**
* 停止定时检查
* @param {number} timerId - 定时器ID
*/
function stopPeriodicCheck(timerId) {
if (timerId) {
clearInterval(timerId)
}
}
module.exports = {
getPendingMessages,
checkAndShowMessages,
getCharacterPendingMessage,
clearCharacterMessages,
markAsRead,
markAsReadByIds,
startPeriodicCheck,
stopPeriodicCheck,
CHECK_INTERVAL
}