diff --git a/src/routes/ai.ts b/src/routes/ai.ts index 77c13b5..563f54b 100644 --- a/src/routes/ai.ts +++ b/src/routes/ai.ts @@ -16,11 +16,14 @@ const SYSTEM_PROMPT = `你是一个帮助用户创建事件(纪念日或提醒 { "parsed": { "type": "anniversary 或 reminder", - "title": "事件标题", + "title": "事件标题(简洁准确,去除"提醒"、"帮我"等前缀)", + "content": "详细内容(仅提醒类型,如会议主题、待办事项等)", "date": "2026-02-13T15:00:00Z", "timezone": "Asia/Shanghai", "is_lunar": true 或 false, - "repeat_type": "daily, weekly, monthly, yearly 或 none" + "repeat_type": "daily, weekly, monthly, yearly 或 none", + "priority": "none, red, green 或 yellow(仅提醒,red=重要紧急,green=绿色,yellow=黄色)", + "reminder_times": ["提前提醒时间点数组,如 2026-02-12T09:00:00Z"](仅提醒) }, "response": "友好的确认消息" } @@ -32,6 +35,18 @@ const SYSTEM_PROMPT = `你是一个帮助用户创建事件(纪念日或提醒 - 如果用户提到"提醒"、"任务"、"todo"、"待办"、"会议"、"约会"、"上课"等 → type = "reminder" - 如果不确定,优先识别为"reminder"(提醒更常用) +### 标题提取规则: +- 标题应该简洁,只包含事件名称 +- 去除"提醒我"、"帮我"、"请帮我"等开头 +- 去除日期、时间等描述性文字 +- 例如:"提醒我明天上午9点开会" → title = "开会" +- 例如:"提醒我春节回家" → title = "春节回家" + +### 内容提取规则(仅提醒类型): +- content 应该包含事件的详细内容 +- 提取日期时间之后剩余的描述性文字 +- 例如:"提醒我明天上午9点开会讨论项目进度" → content = "讨论项目进度" + ### 日期识别优先级: 1. 农历日期:识别"农历正月初一"、"农历三月三"、"阴历八月十五"等 → is_lunar = true 2. 节假日:春节、元宵节、龙抬头、清明、端午、七夕、中元节、中秋、重阳、寒衣节、腊八节、小年、除夕 @@ -50,6 +65,17 @@ const SYSTEM_PROMPT = `你是一个帮助用户创建事件(纪念日或提醒 - "每年" → repeat_type = "yearly" - "不重复"、"仅一次" → repeat_type = "none" +### 优先级识别(仅提醒): +- "重要"、"紧急"、"红色" → priority = "red" +- "绿色" → priority = "green" +- "黄色" → priority = "yellow" +- 无优先级描述 → priority = "none" + +### 提前提醒识别: +- "提前1天提醒" → reminder_times = [eventDate - 1天] +- "提前2小时提醒" → reminder_times = [eventDate - 2小时] +- "提前30分钟提醒" → reminder_times = [eventDate - 30分钟] + ## 重要规则: 1. date 必须是用户提到的具体日期时间,格式为 ISO 8601: YYYY-MM-DDTHH:mm:ssZ 2. 如果用户说"明天",date = (当前时间 + 1天) 的对应时间点 @@ -255,15 +281,6 @@ function mockParseResponse(message: string): { parsed: any; response: string } { priority = 'yellow'; } - // Detect content (after colon or special markers) - let content = ''; - if (message.includes(':') || message.includes(':')) { - const parts = message.split(/[::]/); - if (parts.length > 1) { - content = parts[1].trim(); - } - } - // Detect repeat type let repeatType: 'daily' | 'weekly' | 'monthly' | 'yearly' | 'none' = 'none'; if (lowerMessage.includes('每天') || lowerMessage.includes('每日')) { @@ -276,23 +293,88 @@ function mockParseResponse(message: string): { parsed: any; response: string } { repeatType = 'yearly'; } - // Extract title - 去除"提醒"、"帮我"等前缀 + // Detect reminder times (提前提醒) + let reminderTimes: string[] = []; + // 提前X天提醒 + const daysMatch = lowerMessage.match(/提前(\d+)天/); + if (daysMatch) { + const days = parseInt(daysMatch[1]); + const reminderDate = new Date(targetDate); + reminderDate.setDate(reminderDate.getDate() - days); + reminderTimes.push(reminderDate.toISOString()); + } + // 提前X小时提醒 + const hoursMatch = lowerMessage.match(/提前(\d+)小时?/); + if (hoursMatch) { + const hours = parseInt(hoursMatch[1]); + const reminderDate = new Date(targetDate); + reminderDate.setHours(reminderDate.getHours() - hours); + reminderTimes.push(reminderDate.toISOString()); + } + // 提前X分钟提醒 + const minutesMatch = lowerMessage.match(/提前(\d+)分钟/); + if (minutesMatch) { + const minutes = parseInt(minutesMatch[1]); + const reminderDate = new Date(targetDate); + reminderDate.setMinutes(reminderDate.getMinutes() - minutes); + reminderTimes.push(reminderDate.toISOString()); + } + // 默认提醒(不提前) + if (reminderTimes.length === 0) { + reminderTimes = []; + } + + // Extract title - 智能提取标题 let title = message; + let extractedContent = ''; + // 去除常见的开头 - const prefixes = ['帮我', '提醒我', '提醒', '请帮我', '我要', '我想']; + const prefixes = ['帮我', '提醒我', '提醒', '请帮我', '我要', '我想', '帮我提醒']; for (const prefix of prefixes) { - if (title.startsWith(prefix)) { + if (title.toLowerCase().startsWith(prefix)) { title = title.slice(prefix.length); break; } } - // 去除冒号后面的内容作为标题(保留标题) - if (title.includes(':') || title.includes(':')) { - title = title.split(/[::]/)[0]; + + // 处理"提醒XXX提醒时间YYY"这种格式 - 提取标题和内容 + // 查找最后一个"提醒"或"在"后面的内容作为内容 + const reminderMatch = message.match(/(?:提醒|在|于)\s*(.+)$/); + if (reminderMatch && reminderMatch[1]) { + extractedContent = reminderMatch[1].trim(); } - // 去除日期相关文字 - title = title.replace(/[\d一二三四五六七八九十]+[月日号]/g, '').replace(/星期[一二三四五六日天]/g, '').trim(); - title = title.substring(0, 50) || title; + + // 去除冒号后面的内容 + if (title.includes(':') || title.includes(':')) { + const colonParts = title.split(/[::]/); + title = colonParts[0].trim(); + if (!extractedContent && colonParts.length > 1) { + extractedContent = colonParts.slice(1).join(':').trim(); + } + } + + // 去除日期和时间相关文字 + // 去除"明天"、"后天"、"下周"等相对日期 + title = title.replace(/(?:明天|后天|今天|下周|下个月|明年|今年|去年)/g, ''); + // 去除具体日期如"3月8日"、"2024年5月1日" + title = title.replace(/\d{1,4}[年/-]\d{1,2}[月/-]\d{1,2}[日号]?/g, ''); + title = title.replace(/[\d一二三四五六七八九十]+[月日号]/g, ''); + // 去除星期几 + title = title.replace(/星期[一二三四五六日天]/g, ''); + title = title.replace(/周一|周二|周三|周四|周五|周六|周日/g, ''); + // 去除时间如"上午9点"、"下午3点"、"14:30" + title = title.replace(/(?:上午|中午|下午|晚上)?\s*\d{1,2}(?::\d{2})?\s*(?:点|时)/g, ''); + // 去除"在"、"于"等介词 + title = title.replace(/^[在于是于]\s*/g, ''); + // 去除多余空格 + title = title.trim(); + + // 如果标题为空或太短,使用原始消息 + if (title.length < 2) { + title = message.substring(0, 30); + } + // 限制标题长度 + title = title.substring(0, 50); // Mock date (tomorrow at 9 AM local time by default for China UTC+8) const targetDate = new Date(); @@ -341,8 +423,11 @@ function mockParseResponse(message: string): { parsed: any; response: string } { if (eventType === 'reminder' && priority !== 'none') { parsed.priority = priority; } - if (eventType === 'reminder' && content) { - parsed.content = content; + if (eventType === 'reminder' && extractedContent) { + parsed.content = extractedContent; + } + if (eventType === 'reminder' && reminderTimes.length > 0) { + parsed.reminder_times = reminderTimes; } // Generate response