function normalizeText(text) { return String(text ?? '').replace(/\s+/g, '').trim(); } function chineseHourToNumber(value) { const raw = String(value ?? '').trim(); if (/^\d{1,2}$/.test(raw)) return Number(raw); const map = { 零: 0, 一: 1, 二: 2, 两: 2, 三: 3, 四: 4, 五: 5, 六: 6, 七: 7, 八: 8, 九: 9, 十: 10, }; if (raw === '十') return 10; if (raw.startsWith('十')) return 10 + (map[raw.slice(1)] ?? 0); if (raw.endsWith('十')) return (map[raw[0]] ?? 0) * 10; if (raw.includes('十')) { const [tens, ones] = raw.split('十'); return (map[tens] ?? 1) * 10 + (map[ones] ?? 0); } return map[raw] ?? null; } function parseHourMinute(text) { const match = text.match(/(?:早上|上午|清晨|每天早上|每天上午)?([0-9]{1,2}|[零一二两三四五六七八九十]{1,3})(?:点|:|:)(半|[0-9]{1,2}分?)?/); if (!match) return null; const hour = chineseHourToNumber(match[1]); if (hour === null || hour < 0 || hour > 23) return null; let minute = 0; if (match[2] === '半') minute = 30; else if (match[2]) minute = Number(String(match[2]).replace('分', '')); if (!Number.isFinite(minute) || minute < 0 || minute > 59) return null; return { hour, minute }; } function extractTodoTitle(text) { const original = String(text ?? '').trim(); if (!original) return null; const quoted = original.match(/[「『【((“"']([^」』】))”"']{1,80})[」』】))”"']/); if (quoted?.[1]) { const title = quoted[1].trim(); if (title) return title; } const trailingCommand = original.match( /^(.{1,80}?)(?:帮我)?(?:设|设置|设下|记)(?:一个|下一个)?(?:待办|代办|带办|代拜)(?:吧|一下)?$/u, ); if (trailingCommand?.[1]) { const title = trailingCommand[1].trim(); if (title) return title; } const cleaned = original .replace( /^.*?(?:不用提醒|先记一下|帮我记一下|帮我记|记一下|添加待办|添加任务|记个待办|记个任务|设置一个待办|设置一个代办|设置一个带办|设置一个代拜|设置下一个代拜|设下一个代拜|帮我设置一个待办|帮我设置一个代办|帮我设置一个带办|帮我设置一个代拜|待办|代办|带办|代拜|任务)(?:[::,,、\s]+)?/u, '', ) .trim(); return cleaned || null; } function wantsDailyTodoDigest(compact) { const daily = /每天|每日|天天/.test(compact); if (!daily) return false; const send = /发|发送|推送|提醒|通知|给我/.test(compact); if (!send) return false; const digest = /(待办|todo|任务).*(记录|列表|清单|安排|摘要|汇总)/.test(compact) || /(今天|今日|当天).*(待办|todo|任务)/.test(compact) || /(待办|todo|任务).*(今天|今日|当天)/.test(compact) || /一天的待办/.test(compact); return digest; } export function parseScheduleIntent(text) { const compact = normalizeText(text); if (!compact) return { action: 'none' }; if (wantsDailyTodoDigest(compact)) { const time = parseHourMinute(compact); if (!time) { return { action: 'create_daily_todo_digest', needsClarification: ['digest_time'], }; } return { action: 'create_daily_todo_digest', hour: time.hour, minute: time.minute, }; } const wantsBalanceAlert = /(余额|钱包|账户).*(不足|低于|提醒|预警|通知)|余额不足|低余额|余额预警/.test(compact); if (wantsBalanceAlert) { const thresholdMatch = compact.match(/(?:低于|少于|不足|小于)(\d{1,6})(?:元|块|rmb|人民币|cny)?/i); const thresholdYuan = thresholdMatch ? Number(thresholdMatch[1]) : null; const thresholdCents = thresholdYuan == null ? null : Math.max(0, Math.round(thresholdYuan * 100)); return thresholdCents == null ? { action: 'create_balance_alert', needsClarification: ['threshold'] } : { action: 'create_balance_alert', thresholdCents }; } if (/看看|查看|看下|列出/.test(compact) && /(待办|日程|行程|计划|任务)/.test(compact)) { return { action: 'query_schedule' }; } const wantsTodoRecord = /(不用提醒|先记一下|帮我记一下|帮我记|记一下|添加待办|添加任务|记个待办|记个任务|设置一个待办|设置一个代办|设置一个带办|设置一个代拜|设置下一个代拜|设下一个代拜|帮我设置一个待办|帮我设置一个代办|帮我设置一个带办|帮我设置一个代拜|待办|代办|带办|代拜|任务)/.test( compact, ); if (wantsTodoRecord) { const explicitNoReminder = /(不用提醒|不提醒|先记一下)/.test(compact); const hasScheduleTime = /(提醒|闹钟|叫我|今天|今晚|明天|后天|早上|上午|中午|下午|晚上|[0-9零一二两三四五六七八九十]{1,3}(点|:|:))/.test( compact, ); if (!explicitNoReminder && hasScheduleTime) { return { action: 'schedule_agent' }; } const title = extractTodoTitle(text); if (!title) { return { action: 'create_todo', needsClarification: ['todo_title'] }; } return { action: 'create_todo', title }; } return { action: 'none' }; } export function isScheduleIntent(intent) { return intent?.action && intent.action !== 'none'; } export function shouldUseScheduleAssistant(text) { const compact = normalizeText(text); if (!compact) return false; if (isScheduleIntent(parseScheduleIntent(text))) return true; const scheduleKeywords = /(提醒|待办|代办|带办|代拜|日程|行程|安排|计划|闹钟)/; const timeKeywords = /(今天|今晚|明天|后天|早上|上午|中午|下午|晚上|\d{1,2}[点::])/; return scheduleKeywords.test(compact) && timeKeywords.test(compact); }