fix: task/appt voice creation — non-greedy trigger strip, bare-date extraction, noon/midnight, book trigger, 900ms TTS mic gap

This commit is contained in:
2026-05-31 19:47:41 +00:00
parent 5b599b1617
commit cfdbd57bce
2 changed files with 60 additions and 22 deletions
+58 -20
View File
@@ -666,20 +666,31 @@ if (!$reply) {
// ── Add task ──────────────────────────────────────────────────────────
if (!$reply && preg_match('/\b(add task|remind me to|todo:|to do:|i need to|don.?t forget to|create task|new task|put.*task|add.*to.*list)\b/i', $message)) {
$title = preg_replace('/^.*(add task|remind me to|todo:|to do:|i need to|don.?t forget to|create task|new task|put.*task|add.*to.*list)\s*/i', '', $message);
// Strip trigger at START only (non-greedy anchor prevents stripping mid-phrase words)
$title = preg_replace('/^\s*(?:jarvis\s+)?(?:add task|remind me to|todo:|to do:|i need to|don.?t forget to|create task|new task|put\s+\w+\s+on\s+(?:my\s+)?(?:task\s+)?list|add\s+(?:this\s+)?to\s+(?:my\s+)?list)\s*/i', '', $message);
$title = trim($title, '. ');
$dueDate = null;
if (preg_match('/\b(?:by|on|due|before)\s+(.+)$/i', $title, $dm)) {
$ts = strtotime($dm[1]);
if ($ts !== false) {
// Extract date — "by Friday", "on Monday", "due tomorrow", OR bare date at end of phrase
$datePattern = '((?:next\s+\w+|this\s+(?:monday|tuesday|wednesday|thursday|friday)|tomorrow|today|monday|tuesday|wednesday|thursday|friday|saturday|sunday|\d{1,2}\/\d{1,2}(?:\/\d{2,4})?)(?:\s+at\s+\d{1,2}(?::\d{2})?\s*(?:am|pm)?)?)';
if (preg_match('/\s+(?:by|on|due|before|this|next)\s+' . $datePattern . '\s*$/i', $title, $dm)) {
$ts = strtotime(trim($dm[1]));
if ($ts !== false && $ts > time() - 86400) {
$dueDate = date('Y-m-d', $ts);
$title = trim(preg_replace('/\s+(?:by|on|due|before)\s+.+$/i', '', $title));
$title = trim(preg_replace('/\s+(?:by|on|due|before)\s+.*$/i', '', $title));
}
} elseif (preg_match('/\s+' . $datePattern . '\s*$/i', $title, $dm)) {
// Bare date at end: "call dentist tomorrow", "pay bills Monday"
$ts = strtotime(trim($dm[1]));
if ($ts !== false && $ts > time() - 86400 && $ts < time() + 365*86400) {
$dueDate = date('Y-m-d', $ts);
$title = trim(substr($title, 0, strrpos($title, $dm[0])));
}
}
$category = preg_match('/\b(work|meeting|project|client|office)\b/i', $title) ? 'work' : 'personal';
$category = preg_match('/\b(work|meeting|project|client|office|report|email)\b/i', $title) ? 'work' : 'personal';
$priority = 'normal';
if (preg_match('/\b(urgent|asap|emergency|critical)\b/i', $title)) $priority = 'urgent';
elseif (preg_match('/\b(important|high priority)\b/i', $title)) $priority = 'high';
$title = trim($title);
if ($title) {
JarvisDB::execute('INSERT INTO tasks (title,category,priority,due_date) VALUES (?,?,?,?)', [$title, $category, $priority, $dueDate]);
$duePart = $dueDate ? ', due ' . date('l, M j', strtotime($dueDate)) : '';
@@ -770,28 +781,55 @@ if (!$reply) {
}
// ── Add appointment ───────────────────────────────────────────────────
if (!$reply && preg_match('/\b(schedule|appointment|add.*calendar|book|set up.*meeting|add.*meeting)\b/i', $message)
&& !preg_match('/\b(my calendar|upcoming|list|show|what)\b/i', $message)) {
$raw = preg_replace('/^.*(schedule|appointment|add.*calendar|book|set up.*meeting|add.*meeting)\s*/i', '', $message);
if (!$reply && preg_match('/\b(schedule|add\s+(?:an?\s+)?appointment|book\s+(?:an?\s+)?|set\s+up\s+(?:an?\s+)?meeting|add\s+(?:an?\s+)?meeting|add\s+to\s+(?:my\s+)?calendar)\b/i', $message)
&& !preg_match('/\b(my calendar|upcoming|list|show|what|from email)\b/i', $message)) {
// Strip trigger at START only — prevents eating words like "dentist appointment"
$raw = preg_replace('/^\s*(?:jarvis\s+)?(?:schedule|add\s+(?:an?\s+)?appointment|book\s+(?:a?n?\s+)?|set\s+up\s+(?:an?\s+)?meeting|add\s+(?:an?\s+)?meeting|add\s+to\s+(?:my\s+)?calendar)\s*/i', '', $message);
$raw = trim($raw);
// Normalize natural time words
$raw = preg_replace('/\bnoon\b/i', '12:00 pm', $raw);
$raw = preg_replace('/\bmidnight\b/i', '12:00 am', $raw);
$raw = preg_replace('/\bmidday\b/i', '12:00 pm', $raw);
// Step 1: extract time ("at 2pm", "at 2:30pm", "2pm", "14:00")
$timeStr = null;
if (preg_match('/\bat\s+(\d{1,2}(?::\d{2})?\s*(?:am|pm))\b/i', $raw, $tm)) {
$timeStr = $tm[1];
$raw = trim(str_ireplace($tm[0], '', $raw));
} elseif (preg_match('/\b(\d{1,2}:\d{2}\s*(?:am|pm)?)\b/i', $raw, $tm)) {
$timeStr = $tm[1];
$raw = trim(str_ireplace($tm[0], '', $raw));
}
// Step 2: extract date (day names, "tomorrow", "next X", month+day)
$dateStr = null;
if (preg_match('/\b(tomorrow|today|next\s+\w+|this\s+(?:monday|tuesday|wednesday|thursday|friday|saturday|sunday)|monday|tuesday|wednesday|thursday|friday|saturday|sunday|(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\s+\d{1,2})\b/i', $raw, $dm)) {
$dateStr = $dm[1];
$raw = trim(str_ireplace($dm[0], '', $raw));
}
// Step 3: build datetime
$dtParsed = null;
$title = $raw;
if (preg_match('/\b(tomorrow|today|monday|tuesday|wednesday|thursday|friday|saturday|sunday|next\s+\w+|jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?).{0,30}\b(\d{1,2}(?::\d{2})?\s*(?:am|pm)?)\b/i', $raw, $dm)) {
$ts = strtotime($dm[0]);
if ($ts !== false && $ts > time()) { $dtParsed = date('Y-m-d H:i:s', $ts); $title = trim(str_replace($dm[0], '', $raw)); }
if ($dateStr || $timeStr) {
$dtInput = trim(($dateStr ?: 'today') . ' ' . ($timeStr ?: '09:00 am'));
$ts = strtotime($dtInput);
if ($ts !== false && $ts > time() - 3600) {
$dtParsed = date('Y-m-d H:i:s', $ts);
}
}
if (!$dtParsed && preg_match('/\b(tomorrow|today|monday|tuesday|wednesday|thursday|friday|saturday|sunday|next\s+\w+)\b/i', $raw, $dm)) {
$ts = strtotime($dm[0]);
if ($ts !== false && $ts >= strtotime($today)) { $dtParsed = date('Y-m-d 09:00:00', $ts); $title = trim(str_replace($dm[0], '', $raw)); }
}
$title = trim($title, ' .');
// Step 4: clean up title (remove leftover filler words)
$title = trim(preg_replace('/\s+/', ' ', preg_replace('/\b(on|at|the|a|an)\b\s*/i', ' ', $raw)));
$title = trim($title, ' .,');
if ($title && $dtParsed) {
$category = preg_match('/\b(work|meeting|office|client|project|call)\b/i', $title) ? 'work' : 'personal';
$category = preg_match('/\b(work|meeting|office|client|project|call|conference|interview)\b/i', $title) ? 'work' : 'personal';
JarvisDB::execute('INSERT INTO appointments (title,category,start_at) VALUES (?,?,?)', [$title, $category, $dtParsed]);
$reply = "Scheduled: \"{$title}\" on " . date('l, M j \a\t g:i A', strtotime($dtParsed)) . ", {$userAddr}.";
$source = 'planner:appt_add';
} elseif ($title) {
$reply = "I can add that, {$userAddr} — what date and time?";
$reply = "I can schedule that, {$userAddr} — what date and time?";
$source = 'planner:appt_need_time';
}
}