diff --git a/src/routes/ai.ts b/src/routes/ai.ts index 2988343..77c13b5 100644 --- a/src/routes/ai.ts +++ b/src/routes/ai.ts @@ -25,11 +25,36 @@ const SYSTEM_PROMPT = `你是一个帮助用户创建事件(纪念日或提醒 "response": "友好的确认消息" } -重要规则: +## 意图识别规则(非常重要): + +### 纪念日 vs 提醒 区分: +- 如果用户提到"生日"、"纪念日"、"结婚纪念日"、"节日"、"春节"、"中秋"、"端午"、"元宵"等 → type = "anniversary" +- 如果用户提到"提醒"、"任务"、"todo"、"待办"、"会议"、"约会"、"上课"等 → type = "reminder" +- 如果不确定,优先识别为"reminder"(提醒更常用) + +### 日期识别优先级: +1. 农历日期:识别"农历正月初一"、"农历三月三"、"阴历八月十五"等 → is_lunar = true +2. 节假日:春节、元宵节、龙抬头、清明、端午、七夕、中元节、中秋、重阳、寒衣节、腊八节、小年、除夕 +3. 相对日期:"明天"、"后天"、"下周"、"下个月"、"明年" +4. 星期几:"周一"、"周二"... "周日"、"星期天" +5. 具体日期:"3月8日"、"5月1日"、"10月1日" + +### 时间识别: +- "上午9点"、"下午3点"、"晚上8点"、"14:30" → 转换为对应小时 +- 如果没有指定具体时间,默认使用 09:00:00Z + +### 重复规则识别: +- "每天" → repeat_type = "daily" +- "每周" → repeat_type = "weekly" +- "每月" → repeat_type = "monthly" +- "每年" → repeat_type = "yearly" +- "不重复"、"仅一次" → repeat_type = "none" + +## 重要规则: 1. date 必须是用户提到的具体日期时间,格式为 ISO 8601: YYYY-MM-DDTHH:mm:ssZ 2. 如果用户说"明天",date = (当前时间 + 1天) 的对应时间点 3. 如果用户说"后天",date = (当前时间 + 2天) 的对应时间点 -4. 如果用户说"下周三",date = 下周三的 09:00:00Z +4. 如果用户说"下周三",date = 下周三的 09:00:00Z(必须是真正的下周,不是本周) 5. 如果用户说"下午3点",date = 今天下午15:00:00Z 或用户指定的日期的15:00:00Z 6. 如果用户没有指定具体时间,默认使用当天的 09:00:00Z 7. NEVER 使用示例日期如 2024-01-02T15:00:00Z,必须使用用户提到的实际日期或合理的未来日期 @@ -39,7 +64,8 @@ const SYSTEM_PROMPT = `你是一个帮助用户创建事件(纪念日或提醒 农历日期识别:农历正月初一 -> 需要转换为公历日期 节假日识别(设置为 is_holiday=true): -春节、清明、劳动节、国庆、元旦、中秋、端午 +- 公历节日:元旦、情人节、妇女节、儿童节、劳动节、建军节、教师节、国庆节、圣诞节 +- 农历节日:春节、元宵节、龙抬头、清明、端午、七夕、中元节、中秋、重阳、寒衣节、下元节、腊八节、小年、除夕 时区处理: - 如果用户在中国,使用 "Asia/Shanghai" 时区 @@ -49,7 +75,8 @@ const SYSTEM_PROMPT = `你是一个帮助用户创建事件(纪念日或提醒 请务必: - 解析用户真实意图,不要编造日期 - 如果用户说"下周三",必须是真正下周星期三,不是今天也不是上周 -- 如果用户没有明确日期,使用合理的默认日期(通常是明天或用户提到的第一个日期)`; +- 如果用户没有明确日期,使用合理的默认日期(通常是明天或用户提到的第一个日期) +- 标题要简洁准确,去除"提醒"、"帮我"等前缀`; const parseMessageSchema = z.object({ message: z.string().min(1, 'Message is required').max(1000), @@ -187,17 +214,36 @@ function mockParseResponse(message: string): { parsed: any; response: string } { // Simple mock parser for testing const lowerMessage = message.toLowerCase(); - // Detect if it's a reminder (contains "提醒", "提醒我", etc.) - const isReminder = lowerMessage.includes('提醒') || lowerMessage.includes('提醒我'); + // 意图识别:纪念日 vs 提醒 + const isReminder = lowerMessage.includes('提醒') || lowerMessage.includes('提醒我') || + lowerMessage.includes('任务') || lowerMessage.includes('todo') || + lowerMessage.includes('待办') || lowerMessage.includes('会议') || + lowerMessage.includes('约会') || lowerMessage.includes('上课'); + + // 意图识别:纪念日 + const isAnniversary = lowerMessage.includes('生日') || lowerMessage.includes('纪念日') || + lowerMessage.includes('节日') || lowerMessage.includes('春节') || + lowerMessage.includes('中秋') || lowerMessage.includes('端午') || + lowerMessage.includes('元宵'); + + // 强制使用正确类型 + const eventType = isAnniversary ? 'anniversary' : (isReminder ? 'reminder' : 'reminder'); // Detect lunar date - const isLunar = lowerMessage.includes('农历') || lowerMessage.includes('阴历'); + const isLunar = lowerMessage.includes('农历') || lowerMessage.includes('阴历') || + lowerMessage.includes('正月') || lowerMessage.includes('腊月'); // Detect holiday - const isHoliday = lowerMessage.includes('春节') || lowerMessage.includes('清明') || - lowerMessage.includes('劳动节') || lowerMessage.includes('国庆') || - lowerMessage.includes('元旦') || lowerMessage.includes('中秋') || - lowerMessage.includes('端午'); + const isHoliday = lowerMessage.includes('春节') || lowerMessage.includes('元宵') || + lowerMessage.includes('清明') || lowerMessage.includes('端午') || + lowerMessage.includes('七夕') || lowerMessage.includes('中元') || + lowerMessage.includes('中秋') || lowerMessage.includes('重阳') || + lowerMessage.includes('腊八') || lowerMessage.includes('小年') || + lowerMessage.includes('除夕') || lowerMessage.includes('劳动节') || + lowerMessage.includes('国庆') || lowerMessage.includes('元旦') || + lowerMessage.includes('妇女节') || lowerMessage.includes('儿童节') || + lowerMessage.includes('建军节') || lowerMessage.includes('教师节') || + lowerMessage.includes('圣诞节') || lowerMessage.includes('情人节'); // Detect priority (color) let priority: 'none' | 'red' | 'green' | 'yellow' = 'none'; @@ -230,17 +276,40 @@ function mockParseResponse(message: string): { parsed: any; response: string } { repeatType = 'yearly'; } - // Extract title (simple heuristic - first phrase before date or colon) - let title = message.split(/[::]/)[0] || message; - title = title.split(/[\s\d\u4e00-\u9fa5]+/)[0] || title.substring(0, 50); + // Extract title - 去除"提醒"、"帮我"等前缀 + let title = message; + // 去除常见的开头 + const prefixes = ['帮我', '提醒我', '提醒', '请帮我', '我要', '我想']; + for (const prefix of prefixes) { + if (title.startsWith(prefix)) { + title = title.slice(prefix.length); + break; + } + } + // 去除冒号后面的内容作为标题(保留标题) + if (title.includes(':') || title.includes(':')) { + title = title.split(/[::]/)[0]; + } + // 去除日期相关文字 + title = title.replace(/[\d一二三四五六七八九十]+[月日号]/g, '').replace(/星期[一二三四五六日天]/g, '').trim(); + title = title.substring(0, 50) || title; - // Mock date (tomorrow at 3 PM local time by default for China UTC+8) - const tomorrow = new Date(); - tomorrow.setDate(tomorrow.getDate() + 1); - tomorrow.setHours(15, 0, 0, 0); // 默认下午3点(中国时区) + // Mock date (tomorrow at 9 AM local time by default for China UTC+8) + const targetDate = new Date(); + targetDate.setDate(targetDate.getDate() + 1); + targetDate.setHours(9, 0, 0, 0); // 默认上午9点(中国时区) + + // 检测相对日期 + if (lowerMessage.includes('今天')) { + targetDate.setDate(new Date().getDate()); + } else if (lowerMessage.includes('后天')) { + targetDate.setDate(new Date().getDate() + 2); + } else if (lowerMessage.includes('下周')) { + targetDate.setDate(new Date().getDate() + 7); + } // 检测消息中的时间(如"下午3点"、"下午四点"、"14:30"等) - const timePattern = message.match(/(?:下午|晚上)?(\d{1,2})(?::(\d{2}))?\s*(?:点|时)/); + const timePattern = message.match(/(?:上午|中午|下午|晚上)?(\d{1,2})(?::(\d{2}))?\s*(?:点|时)/); if (timePattern) { let hours = parseInt(timePattern[1]); const minutes = timePattern[2] ? parseInt(timePattern[2]) : 0; @@ -248,14 +317,18 @@ function mockParseResponse(message: string): { parsed: any; response: string } { if (message.includes('下午') || message.includes('晚上')) { if (hours < 12) hours += 12; } - tomorrow.setHours(hours, minutes, 0, 0); + // 中午时间 + if (message.includes('中午') && hours < 12) { + hours = 12; + } + targetDate.setHours(hours, minutes, 0, 0); } // Build parsed result const parsed: any = { - type: isReminder ? 'reminder' : 'anniversary', + type: eventType, title: title.trim(), - date: tomorrow.toISOString(), + date: targetDate.toISOString(), timezone: 'Asia/Shanghai', // 默认使用中国时区 is_lunar: isLunar, repeat_type: repeatType, @@ -265,10 +338,10 @@ function mockParseResponse(message: string): { parsed: any; response: string } { if (isHoliday) { parsed.is_holiday = true; } - if (isReminder && priority !== 'none') { + if (eventType === 'reminder' && priority !== 'none') { parsed.priority = priority; } - if (isReminder && content) { + if (eventType === 'reminder' && content) { parsed.content = content; } @@ -281,9 +354,11 @@ function mockParseResponse(message: string): { parsed: any; response: string } { none: '不重复' }[repeatType]; - let responseMsg = `我帮你记的是: + const typeText = eventType === 'anniversary' ? '纪念日' : '提醒'; + + let responseMsg = `我帮你创建的是: **${title.trim()}** -📅 ${tomorrow.toLocaleDateString('zh-CN')}`; +📅 ${targetDate.toLocaleDateString('zh-CN')} ${targetDate.getHours()}:${String(targetDate.getMinutes()).padStart(2, '0')}`; if (repeatType !== 'none') { responseMsg += `\n🔄 ${repeatText}`; } @@ -293,7 +368,7 @@ function mockParseResponse(message: string): { parsed: any; response: string } { if (isHoliday) { responseMsg += `\n🎉 节假日`; } - if (isReminder && priority !== 'none') { + if (eventType === 'reminder' && priority !== 'none') { const colorText = { red: '红色', green: '绿色', yellow: '黄色', none: '无' }; responseMsg += `\n🏷️ ${colorText[priority]}`; }