Files
memind/schedule-intent.mjs
2026-07-07 12:10:21 +08:00

148 lines
5.6 KiB
JavaScript
Raw Permalink 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.
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);
}