import { isScheduleIntent, parseScheduleIntent } from '../../schedule-intent.mjs'; function normalizeHour(value) { const hour = Number(value); return Number.isInteger(hour) && hour >= 0 && hour <= 23 ? hour : null; } function normalizeMinute(value) { const minute = Number(value); return Number.isInteger(minute) && minute >= 0 && minute <= 59 ? minute : null; } function normalizeThresholdCents(value) { const parsed = Number(value); if (!Number.isFinite(parsed) || parsed < 0) return null; return Math.round(parsed * 100); } function parseJsonReply(reply) { const text = String(reply ?? '').trim(); if (!text) return null; const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i); const candidate = fenced?.[1] ?? text; try { return JSON.parse(candidate); } catch { return null; } } function normalizeLlmScheduleIntent(payload) { const action = String(payload?.action ?? 'none').trim(); if (!action) return { action: 'none' }; if (action === 'create_todo') { const title = String(payload?.title ?? '').trim(); return title ? { action, title } : { action, needsClarification: ['todo_title'] }; } if (action === 'create_daily_todo_digest') { const hour = normalizeHour(payload?.hour); const minute = normalizeMinute(payload?.minute ?? 0); if (hour == null || minute == null) { return { action, needsClarification: ['digest_time'] }; } return { action, hour, minute }; } if (action === 'create_balance_alert') { const thresholdCents = normalizeThresholdCents(payload?.thresholdYuan); return thresholdCents == null ? { action, needsClarification: ['threshold'] } : { action, thresholdCents }; } if (action === 'query_schedule' || action === 'schedule_agent') { return { action }; } return { action: 'none' }; } async function parseScheduleIntentWithLlm({ text, llmProviderService, logger = console }) { if (!llmProviderService || typeof llmProviderService.createChatCompletion !== 'function') { return { action: 'none' }; } const result = await llmProviderService.createChatCompletion({ temperature: 0, messages: [ { role: 'system', content: [ '你是公众号提醒助手,只做意图识别。', '请严格输出 JSON,不要输出解释。', '允许 action: create_todo, create_daily_todo_digest, create_balance_alert, query_schedule, schedule_agent, none。', 'create_todo 需要 title。', 'create_daily_todo_digest 需要 hour(0-23) 和 minute(0-59)。', 'create_balance_alert 需要 thresholdYuan 数字。', '如果用户在说具体时间提醒、一次性闹钟、某天某时提醒,返回 schedule_agent。', '不确定时返回 none。', ].join('\n'), }, { role: 'user', content: text, }, ], }); if (!result?.ok) { logger?.warn?.('[wechat-schedule-llm] parse skipped:', result?.message ?? 'unknown'); return { action: 'none' }; } return normalizeLlmScheduleIntent(parseJsonReply(result.reply)); } async function resolveScheduleIntent({ text, wechatScheduleLlmConfigService, llmProviderService, logger, }) { const ruleIntent = parseScheduleIntent(text); if (isScheduleIntent(ruleIntent)) return ruleIntent; if (!wechatScheduleLlmConfigService || !llmProviderService) return ruleIntent; let enabled = false; try { enabled = await wechatScheduleLlmConfigService.isScheduleLlmEnabled(); } catch (err) { logger?.warn?.( '[wechat-schedule-llm] config load failed:', err instanceof Error ? err.message : err, ); } if (!enabled) return ruleIntent; return parseScheduleIntentWithLlm({ text, llmProviderService, logger }); } export async function handleWechatScheduleIntent({ intent, user, scheduleService, wechatScheduleLlmConfigService = null, llmProviderService = null, logger = console, }) { if (!scheduleService) return null; const scheduleIntent = await resolveScheduleIntent({ text: intent.agentText, wechatScheduleLlmConfigService, llmProviderService, logger, }); if (!isScheduleIntent(scheduleIntent) || scheduleIntent.action === 'schedule_agent') return null; const timezone = process.env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai'; if (scheduleIntent.action === 'create_todo') { if (scheduleIntent.needsClarification?.includes('todo_title')) { return '可以。你想让我记哪一条待办?例如“帮我记一下 跟段吃饭”。'; } const item = await scheduleService.createItem({ userId: user.userId, kind: 'task', title: scheduleIntent.title, timezone, sourceChannel: 'wechat', sourceMessageId: intent.msgId || null, sourceText: intent.agentText, metadata: { source: 'wechat_mp', }, }); return `已记录到待办列表:${item.title},未设置提醒。`; } if (scheduleIntent.action === 'create_daily_todo_digest') { if (scheduleIntent.needsClarification?.includes('digest_time')) { return '可以。你想每天几点收到当天待办记录?比如“每天早上 7 点发给我”。'; } const subscription = await scheduleService.createDailyTodoDigest({ userId: user.userId, hour: scheduleIntent.hour, minute: scheduleIntent.minute, timezone, channel: 'wechat', sourceChannel: 'wechat', sourceMessageId: intent.msgId || null, sourceText: intent.agentText, }); const minuteText = subscription.minute === 0 ? '' : `${String(subscription.minute).padStart(2, '0')}分`; return `已设置:我会每天早上 ${subscription.hour}点${minuteText} 通过服务号把当天待办记录发给你。`; } if (scheduleIntent.action === 'create_balance_alert') { if (scheduleIntent.needsClarification?.includes('threshold')) { return '可以。你想在余额低于多少时提醒我?例如“余额低于 20 元提醒我”。'; } const subscription = await scheduleService.createBalanceLowAlert({ userId: user.userId, thresholdCents: scheduleIntent.thresholdCents, channel: 'wechat', sourceChannel: 'wechat', sourceMessageId: intent.msgId || null, sourceText: intent.agentText, }); return `已设置:当余额低于 ${(subscription.thresholdCents / 100).toFixed(2)} 元时,我会通过服务号提醒你。`; } if (scheduleIntent.action === 'query_schedule') { return scheduleService.buildTodoDigestText({ userId: user.userId, timezone, }); } return null; }