Compare commits
5 Commits
c36a56e0a4
...
0c670de0dd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c670de0dd | ||
|
|
dc98407edc | ||
|
|
d862203140 | ||
|
|
1ff0693901 | ||
|
|
beedafc7d1 |
2
.env
2
.env
@ -14,7 +14,7 @@ DATABASE_URL=file:./prisma/dev.db
|
|||||||
# DATABASE_URL=postgresql://qia_admin:your-password@postgres.ap-shanghai.myqcloud.com:5432/qia
|
# DATABASE_URL=postgresql://qia_admin:your-password@postgres.ap-shanghai.myqcloud.com:5432/qia
|
||||||
|
|
||||||
# DeepSeek AI
|
# DeepSeek AI
|
||||||
DEEPSEEK_API_KEY=sk-7e34702637f74020b62cdd62d3f48559
|
DEEPSEEK_API_KEY=sk-3e2019e98c6f406b86ba31c7b820fb51
|
||||||
DEEPSEEK_API_URL=https://api.deepseek.com/chat/completions
|
DEEPSEEK_API_URL=https://api.deepseek.com/chat/completions
|
||||||
|
|
||||||
# CORS (支持多个开发端口)
|
# CORS (支持多个开发端口)
|
||||||
|
|||||||
310
src/routes/ai.ts
310
src/routes/ai.ts
@ -11,25 +11,110 @@ const SYSTEM_PROMPT = `你是一个帮助用户创建事件(纪念日或提醒
|
|||||||
|
|
||||||
当前时间:${new Date().toISOString()}
|
当前时间:${new Date().toISOString()}
|
||||||
|
|
||||||
任务:从自然语言中解析用户输入,并严格按照以下JSON格式返回:
|
⚠️⚠️⚠️ 关键规则 - 必须在返回的JSON中遵守 ⚠️⚠️⚠️
|
||||||
|
1. 下午3点 = 15:00,不是03:00!
|
||||||
|
2. 下午1点 = 13:00,下午2点 = 14:00,下午3点 = 15:00,下午4点 = 16:00
|
||||||
|
3. 晚上8点 = 20:00,晚上9点 = 21:00
|
||||||
|
4. "X点后"意思是"X点之后",不是"X小时后"!
|
||||||
|
- "下午3点后" = 今天或明天的 15:00
|
||||||
|
- "晚上8点后" = 今天或明天的 20:00
|
||||||
|
5. 当前时间之后的"X点",如果还没到就是今天,如果已经过了就是明天
|
||||||
|
|
||||||
{
|
任务:从自然语言中解析用户输入,并直接返回以下JSON格式(不要有其他文字解释):
|
||||||
"parsed": {
|
|
||||||
"type": "anniversary 或 reminder",
|
|
||||||
"title": "事件标题",
|
|
||||||
"date": "2026-02-13T15:00:00Z",
|
|
||||||
"timezone": "Asia/Shanghai",
|
|
||||||
"is_lunar": true 或 false,
|
|
||||||
"repeat_type": "daily, weekly, monthly, yearly 或 none"
|
|
||||||
},
|
|
||||||
"response": "友好的确认消息"
|
|
||||||
}
|
|
||||||
|
|
||||||
重要规则:
|
{"type": "anniversary", "title": "事件标题", "content": "详细内容", "date": "2026-02-13T09:00:00Z", "timezone": "Asia/Shanghai", "is_lunar": false, "repeat_type": "yearly", "priority": "none", "reminder_times": []}
|
||||||
|
|
||||||
|
重要字段说明:
|
||||||
|
- type: "anniversary"(纪念日) 或 "reminder"(提醒)
|
||||||
|
- title: 简洁的事件标题,去除"提醒"、"帮我"等前缀
|
||||||
|
- content: 仅提醒类型需要,事件的详细内容
|
||||||
|
- date: ISO 8601 格式的日期时间
|
||||||
|
- timezone: "Asia/Shanghai"
|
||||||
|
- is_lunar: 是否农历日期
|
||||||
|
- repeat_type: "daily"(每天), "weekly"(每周), "monthly"(每月), "yearly"(每年), "none"(不重复)
|
||||||
|
- priority: "none", "red", "green", "yellow"
|
||||||
|
- reminder_times: 提前提醒时间数组
|
||||||
|
|
||||||
|
标题提取示例:
|
||||||
|
- "提醒我明天上午9点开会" → title = "开会"
|
||||||
|
- "提醒我春节回家" → title = "春节回家"
|
||||||
|
- "帮我记一下妈妈的生日" → title = "妈妈的生日"
|
||||||
|
|
||||||
|
时间解析示例(极其重要):
|
||||||
|
- "上午9点" → date 中的小时 = 09
|
||||||
|
- "下午3点" → date 中的小时 = 15(下午3点=15点!不是3点!)
|
||||||
|
- "晚上8点" → date 中的小时 = 20
|
||||||
|
- "下午3点后" → date = 今天 15:00(如果当前时间<15:00)或明天15:00(如果当前时间>=15:00)
|
||||||
|
- "晚上8点后" → date = 今天 20:00 或明天 20:00
|
||||||
|
|
||||||
|
## 意图识别规则(非常重要):
|
||||||
|
|
||||||
|
### 纪念日 vs 提醒 区分:
|
||||||
|
- 如果用户提到"生日"、"纪念日"、"结婚纪念日"、"节日"、"春节"、"中秋"、"端午"、"元宵"等 → type = "anniversary"
|
||||||
|
- 如果用户提到"提醒"、"任务"、"todo"、"待办"、"会议"、"约会"、"上课"等 → type = "reminder"
|
||||||
|
- 如果不确定,优先识别为"reminder"(提醒更常用)
|
||||||
|
|
||||||
|
### 标题提取规则:
|
||||||
|
- 标题应该简洁,只包含事件名称
|
||||||
|
- 去除"提醒我"、"帮我"、"请帮我"等开头
|
||||||
|
- 去除日期、时间等描述性文字
|
||||||
|
- 例如:"提醒我明天上午9点开会" → title = "开会"
|
||||||
|
- 例如:"提醒我春节回家" → title = "春节回家"
|
||||||
|
|
||||||
|
### 内容提取规则(仅提醒类型):
|
||||||
|
- content 应该包含事件的详细内容
|
||||||
|
- 提取日期时间之后剩余的描述性文字
|
||||||
|
- 例如:"提醒我明天上午9点开会讨论项目进度" → content = "讨论项目进度"
|
||||||
|
|
||||||
|
### 日期识别优先级:
|
||||||
|
1. 农历日期:识别"农历正月初一"、"农历三月三"、"阴历八月十五"等 → is_lunar = true
|
||||||
|
2. 节假日:春节、元宵节、龙抬头、清明、端午、七夕、中元节、中秋、重阳、寒衣节、腊八节、小年、除夕
|
||||||
|
3. 相对日期:"明天"、"后天"、"下周"、"下个月"、"明年"
|
||||||
|
4. 星期几:"周一"、"周二"... "周日"、"星期天"
|
||||||
|
5. 具体日期:"3月8日"、"5月1日"、"10月1日"
|
||||||
|
|
||||||
|
### 时间识别(非常重要):
|
||||||
|
- "上午9点" → 09:00:00Z
|
||||||
|
- "下午3点" → 15:00:00Z(绝对不能误解为 03:00!)
|
||||||
|
- "晚上8点" → 20:00:00Z
|
||||||
|
- "14:30" → 14:30:00Z
|
||||||
|
- "上午X点" = X:00(X=1-12)
|
||||||
|
- "下午X点" = X+12:00(下午3点=15点,下午1点=13点)
|
||||||
|
- "晚上X点" = X+12:00(晚上8点=20点,晚上10点=22点)
|
||||||
|
|
||||||
|
### "X点后"识别(非常重要):
|
||||||
|
- "8点后"、"9点后"、"10点后" → 这是"几点以后"的意思,不是"几小时后"
|
||||||
|
- "下午3点后" → 先识别"下午3点"=15:00,然后"后"表示"之后"
|
||||||
|
- "晚上8点后" → 先识别"晚上8点"=20:00,然后"后"表示"之后"
|
||||||
|
- 处理流程:
|
||||||
|
1. 先识别时间(上午/下午/晚上 + X点)
|
||||||
|
2. 转换为24小时制
|
||||||
|
3. 判断"后":如果还没到那个时间就是今天,否则是明天
|
||||||
|
- 绝对禁止将"X点后"理解为"X小时后"!
|
||||||
|
|
||||||
|
### 重复规则识别:
|
||||||
|
- "每天" → repeat_type = "daily"
|
||||||
|
- "每周" → repeat_type = "weekly"
|
||||||
|
- "每月" → repeat_type = "monthly"
|
||||||
|
- "每年" → 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
|
1. date 必须是用户提到的具体日期时间,格式为 ISO 8601: YYYY-MM-DDTHH:mm:ssZ
|
||||||
2. 如果用户说"明天",date = (当前时间 + 1天) 的对应时间点
|
2. 如果用户说"明天",date = (当前时间 + 1天) 的对应时间点
|
||||||
3. 如果用户说"后天",date = (当前时间 + 2天) 的对应时间点
|
3. 如果用户说"后天",date = (当前时间 + 2天) 的对应时间点
|
||||||
4. 如果用户说"下周三",date = 下周三的 09:00:00Z
|
4. 如果用户说"下周三",date = 下周三的 09:00:00Z(必须是真正的下周,不是本周)
|
||||||
5. 如果用户说"下午3点",date = 今天下午15:00:00Z 或用户指定的日期的15:00:00Z
|
5. 如果用户说"下午3点",date = 今天下午15:00:00Z 或用户指定的日期的15:00:00Z
|
||||||
6. 如果用户没有指定具体时间,默认使用当天的 09:00:00Z
|
6. 如果用户没有指定具体时间,默认使用当天的 09:00:00Z
|
||||||
7. NEVER 使用示例日期如 2024-01-02T15:00:00Z,必须使用用户提到的实际日期或合理的未来日期
|
7. NEVER 使用示例日期如 2024-01-02T15:00:00Z,必须使用用户提到的实际日期或合理的未来日期
|
||||||
@ -39,7 +124,8 @@ const SYSTEM_PROMPT = `你是一个帮助用户创建事件(纪念日或提醒
|
|||||||
农历日期识别:农历正月初一 -> 需要转换为公历日期
|
农历日期识别:农历正月初一 -> 需要转换为公历日期
|
||||||
|
|
||||||
节假日识别(设置为 is_holiday=true):
|
节假日识别(设置为 is_holiday=true):
|
||||||
春节、清明、劳动节、国庆、元旦、中秋、端午
|
- 公历节日:元旦、情人节、妇女节、儿童节、劳动节、建军节、教师节、国庆节、圣诞节
|
||||||
|
- 农历节日:春节、元宵节、龙抬头、清明、端午、七夕、中元节、中秋、重阳、寒衣节、下元节、腊八节、小年、除夕
|
||||||
|
|
||||||
时区处理:
|
时区处理:
|
||||||
- 如果用户在中国,使用 "Asia/Shanghai" 时区
|
- 如果用户在中国,使用 "Asia/Shanghai" 时区
|
||||||
@ -49,7 +135,9 @@ const SYSTEM_PROMPT = `你是一个帮助用户创建事件(纪念日或提醒
|
|||||||
请务必:
|
请务必:
|
||||||
- 解析用户真实意图,不要编造日期
|
- 解析用户真实意图,不要编造日期
|
||||||
- 如果用户说"下周三",必须是真正下周星期三,不是今天也不是上周
|
- 如果用户说"下周三",必须是真正下周星期三,不是今天也不是上周
|
||||||
- 如果用户没有明确日期,使用合理的默认日期(通常是明天或用户提到的第一个日期)`;
|
- 如果用户没有明确日期,使用合理的默认日期(通常是明天或用户提到的第一个日期)
|
||||||
|
- 标题要简洁准确,去除"提醒"、"帮我"等前缀
|
||||||
|
- 绝对禁止将"X点后"理解为"X小时后"!例如"8点后"意思是"8点",不是"8小时以后"`;
|
||||||
|
|
||||||
const parseMessageSchema = z.object({
|
const parseMessageSchema = z.object({
|
||||||
message: z.string().min(1, 'Message is required').max(1000),
|
message: z.string().min(1, 'Message is required').max(1000),
|
||||||
@ -107,8 +195,7 @@ async function callDeepSeek(message: string): Promise<{
|
|||||||
const apiUrl = process.env.DEEPSEEK_API_URL || 'https://api.deepseek.com/chat/completions';
|
const apiUrl = process.env.DEEPSEEK_API_URL || 'https://api.deepseek.com/chat/completions';
|
||||||
|
|
||||||
if (!apiKey || apiKey === 'sk-xxx') {
|
if (!apiKey || apiKey === 'sk-xxx') {
|
||||||
// Mock response for development
|
throw new Error('DeepSeek API Key 未配置');
|
||||||
return mockParseResponse(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -147,14 +234,19 @@ async function callDeepSeek(message: string): Promise<{
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const content = data.choices[0]?.message?.content || '';
|
const content = data.choices[0]?.message?.content || '';
|
||||||
|
|
||||||
|
// Log the raw response for debugging
|
||||||
|
console.log('[AI] Raw response:', content);
|
||||||
|
|
||||||
// Extract JSON from response
|
// Extract JSON from response
|
||||||
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
||||||
if (jsonMatch) {
|
if (jsonMatch) {
|
||||||
try {
|
try {
|
||||||
const rawParsed = JSON.parse(jsonMatch[0]);
|
const rawParsed = JSON.parse(jsonMatch[0]);
|
||||||
|
console.log('[AI] Parsed JSON:', rawParsed);
|
||||||
const parsed = rawParsed.parsed || rawParsed;
|
const parsed = rawParsed.parsed || rawParsed;
|
||||||
// Validate parsed data with Zod schema
|
// Validate parsed data with Zod schema
|
||||||
const validated = parsedEventSchema.parse(parsed);
|
const validated = parsedEventSchema.parse(parsed);
|
||||||
|
console.log('[AI] Validated:', validated);
|
||||||
// 构建友好的用户消息
|
// 构建友好的用户消息
|
||||||
const typeText = validated.type === 'anniversary' ? '纪念日' : '提醒';
|
const typeText = validated.type === 'anniversary' ? '纪念日' : '提醒';
|
||||||
const dateObj = new Date(validated.date);
|
const dateObj = new Date(validated.date);
|
||||||
@ -170,15 +262,16 @@ async function callDeepSeek(message: string): Promise<{
|
|||||||
response: `已为你创建${typeText}「${validated.title}」,日期:${dateText}${repeatText}`,
|
response: `已为你创建${typeText}「${validated.title}」,日期:${dateText}${repeatText}`,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Schema validation failed, use mock response
|
console.error('[AI] Parse error:', e);
|
||||||
return mockParseResponse(message);
|
throw new Error(`AI 解析失败: ${e instanceof Error ? e.message : '未知错误'}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mockParseResponse(message);
|
console.warn('[AI] No JSON found in response');
|
||||||
|
throw new Error('AI 响应格式错误,未能解析出 JSON');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('DeepSeek API error:', error);
|
console.error('DeepSeek API error:', error);
|
||||||
return mockParseResponse(message);
|
throw new Error(`DeepSeek API 调用失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,17 +280,36 @@ function mockParseResponse(message: string): { parsed: any; response: string } {
|
|||||||
// Simple mock parser for testing
|
// Simple mock parser for testing
|
||||||
const lowerMessage = message.toLowerCase();
|
const lowerMessage = message.toLowerCase();
|
||||||
|
|
||||||
// Detect if it's a reminder (contains "提醒", "提醒我", etc.)
|
// 意图识别:纪念日 vs 提醒
|
||||||
const isReminder = lowerMessage.includes('提醒') || lowerMessage.includes('提醒我');
|
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
|
// Detect lunar date
|
||||||
const isLunar = lowerMessage.includes('农历') || lowerMessage.includes('阴历');
|
const isLunar = lowerMessage.includes('农历') || lowerMessage.includes('阴历') ||
|
||||||
|
lowerMessage.includes('正月') || lowerMessage.includes('腊月');
|
||||||
|
|
||||||
// Detect holiday
|
// Detect holiday
|
||||||
const isHoliday = 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('妇女节') || lowerMessage.includes('儿童节') ||
|
||||||
|
lowerMessage.includes('建军节') || lowerMessage.includes('教师节') ||
|
||||||
|
lowerMessage.includes('圣诞节') || lowerMessage.includes('情人节');
|
||||||
|
|
||||||
// Detect priority (color)
|
// Detect priority (color)
|
||||||
let priority: 'none' | 'red' | 'green' | 'yellow' = 'none';
|
let priority: 'none' | 'red' | 'green' | 'yellow' = 'none';
|
||||||
@ -209,15 +321,6 @@ function mockParseResponse(message: string): { parsed: any; response: string } {
|
|||||||
priority = 'yellow';
|
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
|
// Detect repeat type
|
||||||
let repeatType: 'daily' | 'weekly' | 'monthly' | 'yearly' | 'none' = 'none';
|
let repeatType: 'daily' | 'weekly' | 'monthly' | 'yearly' | 'none' = 'none';
|
||||||
if (lowerMessage.includes('每天') || lowerMessage.includes('每日')) {
|
if (lowerMessage.includes('每天') || lowerMessage.includes('每日')) {
|
||||||
@ -230,17 +333,105 @@ function mockParseResponse(message: string): { parsed: any; response: string } {
|
|||||||
repeatType = 'yearly';
|
repeatType = 'yearly';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract title (simple heuristic - first phrase before date or colon)
|
// Detect reminder times (提前提醒)
|
||||||
let title = message.split(/[::]/)[0] || message;
|
let reminderTimes: string[] = [];
|
||||||
title = title.split(/[\s\d\u4e00-\u9fa5]+/)[0] || title.substring(0, 50);
|
// 提前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 = [];
|
||||||
|
}
|
||||||
|
|
||||||
// Mock date (tomorrow at 3 PM local time by default for China UTC+8)
|
// Extract title - 智能提取标题
|
||||||
const tomorrow = new Date();
|
let title = message;
|
||||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
let extractedContent = '';
|
||||||
tomorrow.setHours(15, 0, 0, 0); // 默认下午3点(中国时区)
|
|
||||||
|
// 去除常见的开头
|
||||||
|
const prefixes = ['帮我', '提醒我', '提醒', '请帮我', '我要', '我想', '帮我提醒'];
|
||||||
|
for (const prefix of prefixes) {
|
||||||
|
if (title.toLowerCase().startsWith(prefix)) {
|
||||||
|
title = title.slice(prefix.length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理"提醒XXX提醒时间YYY"这种格式 - 提取标题和内容
|
||||||
|
// 查找最后一个"提醒"或"在"后面的内容作为内容
|
||||||
|
const reminderMatch = message.match(/(?:提醒|在|于)\s*(.+)$/);
|
||||||
|
if (reminderMatch && reminderMatch[1]) {
|
||||||
|
extractedContent = reminderMatch[1].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去除冒号后面的内容
|
||||||
|
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();
|
||||||
|
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"等)
|
// 检测消息中的时间(如"下午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) {
|
if (timePattern) {
|
||||||
let hours = parseInt(timePattern[1]);
|
let hours = parseInt(timePattern[1]);
|
||||||
const minutes = timePattern[2] ? parseInt(timePattern[2]) : 0;
|
const minutes = timePattern[2] ? parseInt(timePattern[2]) : 0;
|
||||||
@ -248,14 +439,18 @@ function mockParseResponse(message: string): { parsed: any; response: string } {
|
|||||||
if (message.includes('下午') || message.includes('晚上')) {
|
if (message.includes('下午') || message.includes('晚上')) {
|
||||||
if (hours < 12) hours += 12;
|
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
|
// Build parsed result
|
||||||
const parsed: any = {
|
const parsed: any = {
|
||||||
type: isReminder ? 'reminder' : 'anniversary',
|
type: eventType,
|
||||||
title: title.trim(),
|
title: title.trim(),
|
||||||
date: tomorrow.toISOString(),
|
date: targetDate.toISOString(),
|
||||||
timezone: 'Asia/Shanghai', // 默认使用中国时区
|
timezone: 'Asia/Shanghai', // 默认使用中国时区
|
||||||
is_lunar: isLunar,
|
is_lunar: isLunar,
|
||||||
repeat_type: repeatType,
|
repeat_type: repeatType,
|
||||||
@ -265,11 +460,14 @@ function mockParseResponse(message: string): { parsed: any; response: string } {
|
|||||||
if (isHoliday) {
|
if (isHoliday) {
|
||||||
parsed.is_holiday = true;
|
parsed.is_holiday = true;
|
||||||
}
|
}
|
||||||
if (isReminder && priority !== 'none') {
|
if (eventType === 'reminder' && priority !== 'none') {
|
||||||
parsed.priority = priority;
|
parsed.priority = priority;
|
||||||
}
|
}
|
||||||
if (isReminder && content) {
|
if (eventType === 'reminder' && extractedContent) {
|
||||||
parsed.content = content;
|
parsed.content = extractedContent;
|
||||||
|
}
|
||||||
|
if (eventType === 'reminder' && reminderTimes.length > 0) {
|
||||||
|
parsed.reminder_times = reminderTimes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate response
|
// Generate response
|
||||||
@ -281,9 +479,11 @@ function mockParseResponse(message: string): { parsed: any; response: string } {
|
|||||||
none: '不重复'
|
none: '不重复'
|
||||||
}[repeatType];
|
}[repeatType];
|
||||||
|
|
||||||
let responseMsg = `我帮你记的是:
|
const typeText = eventType === 'anniversary' ? '纪念日' : '提醒';
|
||||||
|
|
||||||
|
let responseMsg = `我帮你创建的是:
|
||||||
**${title.trim()}**
|
**${title.trim()}**
|
||||||
📅 ${tomorrow.toLocaleDateString('zh-CN')}`;
|
📅 ${targetDate.toLocaleDateString('zh-CN')} ${targetDate.getHours()}:${String(targetDate.getMinutes()).padStart(2, '0')}`;
|
||||||
if (repeatType !== 'none') {
|
if (repeatType !== 'none') {
|
||||||
responseMsg += `\n🔄 ${repeatText}`;
|
responseMsg += `\n🔄 ${repeatText}`;
|
||||||
}
|
}
|
||||||
@ -293,7 +493,7 @@ function mockParseResponse(message: string): { parsed: any; response: string } {
|
|||||||
if (isHoliday) {
|
if (isHoliday) {
|
||||||
responseMsg += `\n🎉 节假日`;
|
responseMsg += `\n🎉 节假日`;
|
||||||
}
|
}
|
||||||
if (isReminder && priority !== 'none') {
|
if (eventType === 'reminder' && priority !== 'none') {
|
||||||
const colorText = { red: '红色', green: '绿色', yellow: '黄色', none: '无' };
|
const colorText = { red: '红色', green: '绿色', yellow: '黄色', none: '无' };
|
||||||
responseMsg += `\n🏷️ ${colorText[priority]}`;
|
responseMsg += `\n🏷️ ${colorText[priority]}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user